pax_global_header00006660000000000000000000000064144542162560014523gustar00rootroot0000000000000052 comment=9204d9ebb83ae21f63f032de74e92a001e1ffa9f mysql_fdw-REL-2_9_1/000077500000000000000000000000001445421625600143235ustar00rootroot00000000000000mysql_fdw-REL-2_9_1/.gitattributes000066400000000000000000000005221445421625600172150ustar00rootroot00000000000000* 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_9_1/.gitignore000066400000000000000000000000561445421625600163140ustar00rootroot00000000000000# Generated subdirectories /results/ *.o *.so mysql_fdw-REL-2_9_1/CONTRIBUTING.md000066400000000000000000000053441445421625600165620ustar00rootroot00000000000000Contributing 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_9_1/LICENSE000066400000000000000000000020151445421625600153260ustar00rootroot00000000000000MySQL Foreign Data Wrapper for PostgreSQL Copyright (c) 2011-2023, 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_9_1/META.json000066400000000000000000000022031445421625600157410ustar00rootroot00000000000000{ "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_9_1/Makefile000066400000000000000000000027371445421625600157740ustar00rootroot00000000000000# mysql_fdw/Makefile # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # Portions Copyright (c) 2004-2023, EnterpriseDB Corporation. # MODULE_big = mysql_fdw OBJS = connection.o option.o deparse.o mysql_query.o mysql_fdw.o mysql_pushability.o EXTENSION = mysql_fdw DATA = mysql_fdw--1.0.sql mysql_fdw--1.1.sql mysql_fdw--1.0--1.1.sql mysql_fdw--1.2.sql mysql_fdw--1.1--1.2.sql mysql_fdw_pushdown.config REGRESS = server_options connection_validation dml select pushdown join_pushdown aggregate_pushdown limit_offset_pushdown misc 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), 11 12 13 14 15 16)) $(error PostgreSQL 11, 12, 13, 14, 15, or 16 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_9_1/README.md000066400000000000000000000256211445421625600156100ustar00rootroot00000000000000MySQL 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 11, 12, 13, 14, 15, and 16. 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. ### ORDER BY push-down mysql_fdw now also supports order by push-down. If possible, push order by clause to the remote server so that we get the ordered result set from the foreign server itself. It might help us to have an efficient merge join. NULLs behavior is opposite on the MySQL server. Thus to get an equivalent result, we add the "expression IS NULL" clause at the beginning of each of the ORDER BY expressions. ### LIMIT OFFSET push-down mysql_fdw now also supports limit offset push-down. Wherever possible, perform LIMIT and OFFSET operations on the remote server. This reduces network traffic between local PostgreSQL and remote MySQL servers. ALL/NULL options are not supported on the MySQL server, and thus they are not pushed down. Also, OFFSET without LIMIT is not supported on the MySQL server hence queries having that construct are not pushed. 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`. * `sql_mode`: Set MySQL sql_mode for established connection. Default is `ANSI_QUOTES`. * `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`. * `character_set`: The character set to use for MySQL connection. Default is `auto` which means autodetect based on the operating system setting. Before the introduction of the character_set option, the character set was set similar to the PostgreSQL database encoding. To get this older behavior set the character_set to special value `PGDatabaseEncoding`. 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. The following parameters can be set on IMPORT FOREIGN SCHEMA command: * `import_default`: This option controls whether column DEFAULT expressions are included in the definitions of foreign tables imported from a foreign server. The default is `false`. * `import_not_null`: This option controls whether column NOT NULL constraints are included in the definitions of foreign tables imported from a foreign server. The default is `true`. * `import_enum_as_text`: This option can be used to map MySQL ENUM type to TEXT type in the definitions of foreign tables, otherwise emit a warning for type to be created. The default is `false`. * `import_generated`: This option controls whether GENERATED column expressions are included in the definitions of foreign tables imported from a foreign server or not. The default is `true`. The IMPORT will fail altogether if an imported generated expression uses a function or operator that does not exist on PostgreSQL. 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-2023, 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_9_1/connection.c000066400000000000000000000172761445421625600166430ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * connection.c * Connection management functions for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, EnterpriseDB Corporation. * * IDENTIFICATION * connection.c * *------------------------------------------------------------------------- */ #include "postgres.h" #if PG_VERSION_NUM >= 130000 #include "common/hashfn.h" #endif #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) { entry->conn = mysql_fdw_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)); entry->mapping_hashvalue = GetSysCacheHashValue1(USERMAPPINGOID, ObjectIdGetDatum(user->umid)); } 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_fdw_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, opt->character_set); #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_9_1/deparse.c000066400000000000000000002032001445421625600161070ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * deparse.c * Query deparser for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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 "mysql_pushability.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" /* * 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 */ bool is_not_distinct_op; /* True in case of IS NOT DISTINCT clause */ } 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_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); 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); static void mysql_append_orderby_clause(List *pathkeys, bool has_final_sort, deparse_expr_cxt *context); static void mysql_append_limit_clause(deparse_expr_cxt *context); static void mysql_append_orderby_suffix(Expr *em_expr, const char *sortby_dir, Oid sortcoltype, bool nulls_first, deparse_expr_cxt *context); /* * 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, '`')); } 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. * * pathkeys is the list of pathkeys to order the result by. * * 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 *pathkeys, bool has_final_sort, bool has_limit, List **retrieved_attrs, List **params_list) { deparse_expr_cxt context; List *quals; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) rel->fdw_private; /* * We handle relations for foreign tables and joins between those and * upper relations. */ Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); /* Fill portions of context common to base relation */ context.buf = buf; context.root = root; context.foreignrel = rel; context.params_list = params_list; context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; context.is_not_distinct_op = false; /* 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 (IS_UPPER_REL(rel)) { MySQLFdwRelationInfo *ofpinfo; ofpinfo = (MySQLFdwRelationInfo *) fpinfo->outerrel->fdw_private; quals = ofpinfo->remote_conds; } else quals = remote_conds; /* Construct FROM and WHERE clauses */ mysql_deparse_from_expr(quals, &context); 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); } } /* Add ORDER BY clause if we found any useful pathkeys */ if (pathkeys) mysql_append_orderby_clause(pathkeys, has_final_sort, &context); /* Add LIMIT clause if necessary */ if (has_limit) mysql_append_limit_clause(&context); } /* * 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 (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { /* * 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 */ Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); /* 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 */ void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, bool doNothing) { ListCell *lc; #if PG_VERSION_NUM >= 140000 TupleDesc tupdesc = RelationGetDescr(rel); #endif appendStringInfo(buf, "INSERT %sINTO ", doNothing ? "IGNORE " : ""); 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; #if PG_VERSION_NUM >= 140000 if (TupleDescAttr(tupdesc, lfirst_int(lc) - 1)->attgenerated) { appendStringInfoString(buf, "DEFAULT"); continue; } #endif 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) colname = get_attname(rte->relid, varattno, false); 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_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; #if PG_VERSION_NUM >= 150000 struct pg_itm tt, *itm = &tt; #endif #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 PG_VERSION_NUM >= 150000 interval2itm(*DatumGetIntervalP(datum), itm); tm.tm_sec = itm->tm_sec; tm.tm_min = itm->tm_min; tm.tm_hour = itm->tm_hour; tm.tm_mday = itm->tm_mday; tm.tm_mon = itm->tm_mon; tm.tm_year = itm->tm_year; fsec = itm->tm_usec; #else if (interval2tm(*DatumGetIntervalP(datum), &tm, &fsec) != 0) elog(ERROR, "could not convert interval to tm"); #endif 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; #if PG_VERSION_NUM >= 140000 TupleDesc tupdesc = RelationGetDescr(rel); #endif 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); #if PG_VERSION_NUM >= 140000 if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated) { appendStringInfoString(buf, " = DEFAULT"); continue; } #endif 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 [NOT] DISTINCT FROM into MySQL equivalent form. */ static void mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool is_not_distinct_op = context->is_not_distinct_op; Assert(list_length(node->args) == 2); /* * In MySQL, <=> operator is equal to IS NOT DISTINCT FROM clause, so for * IS DISTINCT FROM clause, we add NOT before <=> operator. */ if (!is_not_distinct_op) appendStringInfoString(buf, "(NOT "); /* Reset the value of is_not_distinct_op for recursive calls. */ context->is_not_distinct_op = false; appendStringInfoChar(buf, '('); deparseExpr(linitial(node->args), context); appendStringInfoString(buf, " <=> "); deparseExpr(lsecond(node->args), context); appendStringInfoChar(buf, ')'); if (!is_not_distinct_op) appendStringInfoString(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; Expr *arg; switch (node->boolop) { case AND_EXPR: op = "AND"; break; case OR_EXPR: op = "OR"; break; case NOT_EXPR: /* * Per transformAExprDistinct(), in the case of IS NOT DISTINCT * clause, it adds NOT on top of DistinctExpr. However, in MySQL, * <=> is equivalent to IS NOT DISTINCT FROM clause, so do not * append NOT here if it is a DistinctExpr. */ arg = (Expr *) lfirst(list_head(node->args)); if (!IsA(arg, DistinctExpr)) { appendStringInfoString(buf, "(NOT "); deparseExpr(arg, context); appendStringInfoChar(buf, ')'); return; } /* Mark that we are deparsing a IS NOT DISTINCT FROM clause. */ context->is_not_distinct_op = true; } 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, ')'); } /* * 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. */ bool mysql_is_builtin(Oid oid) { #if PG_VERSION_NUM >= 150000 return (oid < FirstGenbkiObjectId); #else return (oid < FirstBootstrapObjectId); #endif } /* * 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; if (!mysql_check_remote_pushability(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; if (!mysql_check_remote_pushability(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; if (!mysql_check_remote_pushability(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_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; /* 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; if (!mysql_check_remote_pushability(agg->aggfnoid)) 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; /* VARIADIC not supported on MySQL. */ if (agg->aggvariadic) 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 && !mysql_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; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) (baserel->fdw_private); /* * 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 (IS_UPPER_REL(baserel)) glob_cxt.relids = fpinfo->outerrel->relids; else 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 (IS_JOIN_REL(foreignrel)) { 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; context.is_not_distinct_op = false; 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; /* Only basic, non-split aggregation accepted. */ Assert(node->aggsplit == AGGSPLIT_SIMPLE); /* 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; deparseExpr((Expr *) n, context); } } appendStringInfoChar(buf, ')'); } /* * mysql_append_groupby_clause * Deparse GROUP BY clause. */ 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; } /* * 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; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) (baserel->fdw_private); if (IS_UPPER_REL(baserel)) relids = fpinfo->outerrel->relids; else 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_append_orderby_clause * Deparse ORDER BY clause defined by the given pathkeys. * * The clause should use Vars from context->scanrel if !has_final_sort, or from * context->foreignrel's targetlist if has_final_sort. * * We find a suitable pathkey expression (some earlier step should have * verified that there is one) and deparse it. */ void mysql_append_orderby_clause(List *pathkeys, bool has_final_sort, deparse_expr_cxt *context) { ListCell *lcell; char *delim = " "; StringInfo buf = context->buf; appendStringInfo(buf, " ORDER BY"); foreach(lcell, pathkeys) { PathKey *pathkey = lfirst(lcell); EquivalenceMember *em; Expr *em_expr; char *sortby_dir = NULL; if (has_final_sort) { /* * By construction, context->foreignrel is the input relation to * the final sort. */ em = mysql_find_em_for_rel_target(context->root, pathkey->pk_eclass, context->foreignrel); } else em = mysql_find_em_for_rel(context->root, pathkey->pk_eclass, context->scanrel); /* * We don't expect any error here; it would mean that shippability * wasn't verified earlier. For the same reason, we don't recheck * shippability of the sort operator. */ if (em == NULL) elog(ERROR, "could not find pathkey item to sort"); em_expr = em->em_expr; sortby_dir = mysql_get_sortby_direction_string(em, pathkey); appendStringInfoString(buf, delim); mysql_append_orderby_suffix(em_expr, sortby_dir, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); delim = ", "; } } /* * mysql_append_limit_clause * Deparse LIMIT OFFSET clause. */ static void mysql_append_limit_clause(deparse_expr_cxt *context) { PlannerInfo *root = context->root; StringInfo buf = context->buf; if (root->parse->limitCount) { Const *c = (Const *) root->parse->limitOffset; appendStringInfoString(buf, " LIMIT "); deparseExpr((Expr *) root->parse->limitCount, context); if (c && !c->constisnull) { appendStringInfoString(buf, " OFFSET "); deparseExpr((Expr *) c, context); } } } #if PG_VERSION_NUM >= 140000 /* * mysql_deparse_truncate_sql * Construct a simple "TRUNCATE " statement. */ void mysql_deparse_truncate_sql(StringInfo buf, Relation rel) { appendStringInfoString(buf, "TRUNCATE "); mysql_deparse_relation(buf, rel); } #endif /* * mysql_append_orderby_suffix * Append the ASC/DESC and NULLS FIRST/LAST parts of an ORDER BY clause. */ static void mysql_append_orderby_suffix(Expr *em_expr, const char *sortby_dir, Oid sortcoltype, bool nulls_first, deparse_expr_cxt *context) { StringInfo buf = context->buf; Assert(sortby_dir != NULL); deparseExpr(em_expr, context); /* * Since MySQL doesn't have NULLS FIRST/LAST clause, we use IS NOT NULL or * IS NULL instead. */ if (nulls_first) appendStringInfoString(buf, " IS NOT NULL"); else appendStringInfoString(buf, " IS NULL"); /* Add delimiter */ appendStringInfoString(buf, ", "); deparseExpr(em_expr, context); /* Add the ASC/DESC */ appendStringInfo(buf, " %s", sortby_dir); } /* * mysql_is_foreign_pathkey * Returns true if it's safe to push down the sort expression described by * 'pathkey' to the foreign server. */ bool mysql_is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey) { EquivalenceMember *em = NULL; EquivalenceClass *pathkey_ec = pathkey->pk_eclass; /* * mysql_is_foreign_expr would detect volatile expressions as well, but * checking ec_has_volatile here saves some cycles. */ if (pathkey_ec->ec_has_volatile) return false; /* can push if a suitable EC member exists */ em = mysql_find_em_for_rel(root, pathkey_ec, baserel); if (mysql_get_sortby_direction_string(em, pathkey) == NULL) return false; return true; } mysql_fdw-REL-2_9_1/expected/000077500000000000000000000000001445421625600161245ustar00rootroot00000000000000mysql_fdw-REL-2_9_1/expected/aggregate_pushdown.out000066400000000000000000001544601445421625600225440ustar00rootroot00000000000000\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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), 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 ORDER BY sum(`c1`) IS NULL, sum(`c1`) ASC, avg(`c1`) IS NULL, avg(`c1`) ASC (4 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 ----------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY (`c2` + 2) IS NULL, (`c2` + 2) ASC (4 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) -> 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 ORDER BY `c2` IS NULL, `c2` ASC, sum(`c1`) IS NULL, sum(`c1`) ASC (6 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 --------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY `c2` IS NULL, `c2` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY `c2` IS NULL, `c2` ASC (4 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 exp(max(c1)) = exp(2) ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: c3, (count(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((exp(max(`c1`)) = 7.38905609893065)) ORDER BY `c3` IS NULL, `c3` ASC, count(`c1`) IS NULL, count(`c1`) ASC (4 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING exp(max(c1)) = exp(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)) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) ORDER BY `c2` IS NULL, `c2` ASC (9 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY sum(DISTINCT (r1.`c1` % 5)) IS NULL, sum(DISTINCT (r1.`c1` % 5)) ASC (4 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) -> 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))) ORDER BY (r2.`c1` % 3) IS NULL, (r2.`c1` % 3) ASC (10 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)))) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (7 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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) ORDER BY `c2` IS NULL, `c2` ASC (6 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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))) ORDER BY avg(r1.`c1`) IS NULL, avg(r1.`c1`) ASC, sum(r2.`c1`) IS NULL, sum(r2.`c1`) ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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 (12 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group 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)) ORDER BY `c2` IS NULL, `c2` ASC (7 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: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) ORDER BY `c2` IS NULL, `c2` ASC (7 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" -> MixedAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c3 Group Key: fdw132_t1.c2 -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate 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)) ORDER BY `c2` IS NULL, `c2` ASC (6 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 ---------------------------------------------------------------------------------------------------------- GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (6 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- FDW-129: Limit and offset pushdown with Aggregate pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 1 OFFSET 1 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; min | c1 -----+---- 2 | 2 (1 row) -- Limit 0, Offset 0 with aggregates. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 0 OFFSET 0 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; min | c1 -----+---- (0 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; min | c1 -----+---- 11 | 11 (1 row) -- Limit ALL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; min | c1 -----+---- 11 | 11 (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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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) -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (11 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 ------------------------------------------------------------------------------------------------------------------------------- Finalize GroupAggregate Output: fprt1.c2, avg(fprt1.c1), max(fprt1.c1), count(*) Group Key: fprt1.c2 Filter: (sum(fprt1.c1) < 700) -> Merge Append Sort Key: fprt1.c2 -> Partial GroupAggregate Output: fprt1.c2, PARTIAL avg(fprt1.c1), PARTIAL max(fprt1.c1), PARTIAL count(*), PARTIAL sum(fprt1.c1) Group Key: fprt1.c2 -> Foreign Scan on public.ftprt1_p1 fprt1 Output: fprt1.c2, fprt1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` ORDER BY `c2` IS NULL, `c2` ASC -> Partial GroupAggregate Output: fprt1_1.c2, PARTIAL avg(fprt1_1.c1), PARTIAL max(fprt1_1.c1), PARTIAL count(*), PARTIAL sum(fprt1_1.c1) Group Key: fprt1_1.c2 -> Foreign Scan on public.ftprt1_p2 fprt1_1 Output: fprt1_1.c2, fprt1_1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (18 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_9_1/expected/aggregate_pushdown_1.out000066400000000000000000001543021445421625600227570ustar00rootroot00000000000000\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 exp(max(c1)) = exp(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)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((exp(max(`c1`)) = 7.38905609893065)) (7 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING exp(max(c1)) = exp(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)) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) ORDER BY `c2` IS NULL, `c2` ASC (9 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) -> 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))) ORDER BY (r2.`c1` % 3) IS NULL, (r2.`c1` % 3) ASC (10 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)))) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (7 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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) ORDER BY `c2` IS NULL, `c2` ASC (6 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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 ------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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 (12 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group 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)) ORDER BY `c2` IS NULL, `c2` ASC (7 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: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) ORDER BY `c2` IS NULL, `c2` ASC (7 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" -> MixedAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c3 Group Key: fdw132_t1.c2 -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate 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)) ORDER BY `c2` IS NULL, `c2` ASC (6 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 ---------------------------------------------------------------------------------------------------------- GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (6 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- FDW-129: Limit and offset pushdown with Aggregate pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> GroupAggregate Output: min(c1), c1 Group Key: fdw132_t1.c1 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (8 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; min | c1 -----+---- 2 | 2 (1 row) -- Limit 0, Offset 0 with aggregates. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> GroupAggregate Output: min(c1), c1 Group Key: fdw132_t1.c1 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (8 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; min | c1 -----+---- (0 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> GroupAggregate Output: min(c1), c1 Group Key: fdw132_t1.c1 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (8 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; min | c1 -----+---- 11 | 11 (1 row) -- Limit ALL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> GroupAggregate Output: min(c1), c1 Group Key: fdw132_t1.c1 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (8 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; min | c1 -----+---- 11 | 11 (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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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) -> Merge Append Sort Key: t1.c1 -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (12 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 --------------------------------------------------------------------------------------------------------------------------------------- Finalize GroupAggregate Output: ftprt1_p1.c2, avg(ftprt1_p1.c1), max(ftprt1_p1.c1), count(*) Group Key: ftprt1_p1.c2 Filter: (sum(ftprt1_p1.c1) < 700) -> Merge Append Sort Key: ftprt1_p1.c2 -> Partial GroupAggregate Output: ftprt1_p1.c2, PARTIAL avg(ftprt1_p1.c1), PARTIAL max(ftprt1_p1.c1), PARTIAL count(*), PARTIAL sum(ftprt1_p1.c1) Group Key: ftprt1_p1.c2 -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c2, ftprt1_p1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` ORDER BY `c2` IS NULL, `c2` ASC -> Partial GroupAggregate Output: ftprt1_p2.c2, PARTIAL avg(ftprt1_p2.c1), PARTIAL max(ftprt1_p2.c1), PARTIAL count(*), PARTIAL sum(ftprt1_p2.c1) Group Key: ftprt1_p2.c2 -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c2, ftprt1_p2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (18 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_9_1/expected/aggregate_pushdown_2.out000066400000000000000000001552641445421625600227700ustar00rootroot00000000000000\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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), 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 ORDER BY sum(`c1`) IS NULL, sum(`c1`) ASC, avg(`c1`) IS NULL, avg(`c1`) ASC (4 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 ----------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY (`c2` + 2) IS NULL, (`c2` + 2) ASC (4 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) -> 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 ORDER BY `c2` IS NULL, `c2` ASC, sum(`c1`) IS NULL, sum(`c1`) ASC (6 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 --------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY `c2` IS NULL, `c2` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY `c2` IS NULL, `c2` ASC (4 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 exp(max(c1)) = exp(2) ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: c3, (count(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((exp(max(`c1`)) = 7.38905609893065)) ORDER BY `c3` IS NULL, `c3` ASC, count(`c1`) IS NULL, count(`c1`) ASC (4 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING exp(max(c1)) = exp(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)) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) ORDER BY `c2` IS NULL, `c2` ASC, `c1` IS NULL, `c1` ASC (9 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)) ORDER BY `c1` IS NOT NULL, `c1` DESC (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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY sum(DISTINCT (r1.`c1` % 5)) IS NULL, sum(DISTINCT (r1.`c1` % 5)) ASC (4 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) -> 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))) ORDER BY (r2.`c1` % 3) IS NULL, (r2.`c1` % 3) ASC, (r1.`c1` % 5) IS NULL, (r1.`c1` % 5) ASC (10 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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) ORDER BY (`c1` % 3) IS NULL, (`c1` % 3) ASC (5 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)))) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (7 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.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) ORDER BY `c2` IS NULL, `c2` ASC (6 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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))) ORDER BY avg(r1.`c1`) IS NULL, avg(r1.`c1`) ASC, sum(r2.`c1`) IS NULL, sum(r2.`c1`) ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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 (12 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group 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)) ORDER BY `c2` IS NULL, `c2` ASC (7 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: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) ORDER BY `c2` IS NULL, `c2` ASC (7 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" -> MixedAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c3 Group Key: fdw132_t1.c2 -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate 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)) ORDER BY `c2` IS NULL, `c2` ASC (6 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 ---------------------------------------------------------------------------------------------------------- GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (6 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- FDW-129: Limit and offset pushdown with Aggregate pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 1 OFFSET 1 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; min | c1 -----+---- 2 | 2 (1 row) -- Limit 0, Offset 0 with aggregates. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 0 OFFSET 0 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; min | c1 -----+---- (0 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; min | c1 -----+---- 11 | 11 (1 row) -- Limit ALL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; min | c1 -----+---- 11 | 11 (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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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) -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (11 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 ------------------------------------------------------------------------------------------------------------------------------- Finalize GroupAggregate Output: fprt1.c2, avg(fprt1.c1), max(fprt1.c1), count(*) Group Key: fprt1.c2 Filter: (sum(fprt1.c1) < 700) -> Merge Append Sort Key: fprt1.c2 -> Partial GroupAggregate Output: fprt1.c2, PARTIAL avg(fprt1.c1), PARTIAL max(fprt1.c1), PARTIAL count(*), PARTIAL sum(fprt1.c1) Group Key: fprt1.c2 -> Foreign Scan on public.ftprt1_p1 fprt1 Output: fprt1.c2, fprt1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` ORDER BY `c2` IS NULL, `c2` ASC -> Partial GroupAggregate Output: fprt1_1.c2, PARTIAL avg(fprt1_1.c1), PARTIAL max(fprt1_1.c1), PARTIAL count(*), PARTIAL sum(fprt1_1.c1) Group Key: fprt1_1.c2 -> Foreign Scan on public.ftprt1_p2 fprt1_1 Output: fprt1_1.c2, fprt1_1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (18 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_9_1/expected/aggregate_pushdown_4.out000066400000000000000000001546461445421625600227750ustar00rootroot00000000000000\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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), 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 ORDER BY sum(`c1`) IS NULL, sum(`c1`) ASC, avg(`c1`) IS NULL, avg(`c1`) ASC (4 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 ----------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY (`c2` + 2) IS NULL, (`c2` + 2) ASC (4 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) -> 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 ORDER BY `c2` IS NULL, `c2` ASC, sum(`c1`) IS NULL, sum(`c1`) ASC (6 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 --------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY `c2` IS NULL, `c2` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY `c2` IS NULL, `c2` ASC (4 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 exp(max(c1)) = exp(2) ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: c3, (count(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((exp(max(`c1`)) = 7.38905609893065)) ORDER BY `c3` IS NULL, `c3` ASC, count(`c1`) IS NULL, count(`c1`) ASC (4 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING exp(max(c1)) = exp(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)) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) ORDER BY `c2` IS NULL, `c2` ASC (9 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 ORDER BY sum(DISTINCT (r1.`c1` % 5)) IS NULL, sum(DISTINCT (r1.`c1` % 5)) ASC (4 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) -> 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))) ORDER BY (r2.`c1` % 3) IS NULL, (r2.`c1` % 3) ASC (10 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)))) -> GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (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) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (7 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 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) ORDER BY `c2` IS NULL, `c2` ASC (6 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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))) ORDER BY avg(r1.`c1`) IS NULL, avg(r1.`c1`) ASC, sum(r2.`c1`) IS NULL, sum(r2.`c1`) ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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 (12 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group 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)) ORDER BY `c2` IS NULL, `c2` ASC (7 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: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) ORDER BY `c2` IS NULL, `c2` ASC (7 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" -> MixedAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c3 Group Key: fdw132_t1.c2 -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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 ----------------------------------------------------------------------------------------------------------------------------- GroupAggregate 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)) ORDER BY `c2` IS NULL, `c2` ASC (6 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 ---------------------------------------------------------------------------------------------------------- GroupAggregate 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` ORDER BY `c2` IS NULL, `c2` ASC (6 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- FDW-129: Limit and offset pushdown with Aggregate pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 1 OFFSET 1 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; min | c1 -----+---- 2 | 2 (1 row) -- Limit 0, Offset 0 with aggregates. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC LIMIT 0 OFFSET 0 (4 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; min | c1 -----+---- (0 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; min | c1 -----+---- 11 | 11 (1 row) -- Limit ALL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Limit Output: (min(c1)), c1 -> Foreign Scan Output: (min(c1)), c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT min(`c1`), `c1` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 ORDER BY `c1` IS NULL, `c1` ASC (6 rows) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; min | c1 -----+---- 11 | 11 (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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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) -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (11 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 --------------------------------------------------------------------------------------------------------------------------------------- Finalize GroupAggregate Output: ftprt1_p1.c2, avg(ftprt1_p1.c1), max(ftprt1_p1.c1), count(*) Group Key: ftprt1_p1.c2 Filter: (sum(ftprt1_p1.c1) < 700) -> Merge Append Sort Key: ftprt1_p1.c2 -> Partial GroupAggregate Output: ftprt1_p1.c2, PARTIAL avg(ftprt1_p1.c1), PARTIAL max(ftprt1_p1.c1), PARTIAL count(*), PARTIAL sum(ftprt1_p1.c1) Group Key: ftprt1_p1.c2 -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c2, ftprt1_p1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` ORDER BY `c2` IS NULL, `c2` ASC -> Partial GroupAggregate Output: ftprt1_p2.c2, PARTIAL avg(ftprt1_p2.c1), PARTIAL max(ftprt1_p2.c1), PARTIAL count(*), PARTIAL sum(ftprt1_p2.c1) Group Key: ftprt1_p2.c2 -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c2, ftprt1_p2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (18 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_9_1/expected/connection_validation.out000066400000000000000000000060211445421625600232250ustar00rootroot00000000000000\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_9_1/expected/dml.out000066400000000000000000000273431445421625600174420ustar00rootroot00000000000000\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 (dbname 'mysql_fdw_regress1', 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 table does not exists. INSERT INTO fdw126_ft6 VALUES(1, 'One'); ERROR: failed to execute the MySQL query: Table 'mysql_fdw_regress1.mysql_fdw_regress1.student' doesn't exist -- 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 -- FDW-518: Should honor ON CONFLICT DO NOTHING clause. SELECT * FROM f_mysql_test ORDER BY 1; a | b ---+--- 1 | 1 (1 row) -- Should not throw an error while inserting duplicate value as we are using -- ON CONFLICT DO NOTHING clause. INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT DO NOTHING; SELECT * FROM f_mysql_test ORDER BY 1; a | b ---+--- 1 | 1 (1 row) -- Should throw an error INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT (a, b) DO NOTHING; ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT DO UPDATE SET b = 10; ERROR: ON CONFLICT DO UPDATE requires inference specification or constraint name LINE 1: INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT DO UPDATE S... ^ HINT: For example, ON CONFLICT (column_name). INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT (a) DO UPDATE SET b = 10; ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification -- FDW-601: database and table name should be quoted correctly in case of -- INSERT/UPDATE/DELETE. CREATE FOREIGN TABLE fdw601(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'fdw-601'); INSERT INTO fdw601 VALUES(1,1), (2,2); UPDATE fdw601 SET b = 3 WHERE b = 2; DELETE FROM fdw601 WHERE b = 1; SELECT * FROM fdw601 ORDER BY 1; a | b ---+--- 2 | 3 (1 row) DELETE FROM fdw601; -- 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 FOREIGN TABLE fdw601; 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_9_1/expected/join_pushdown.out000066400000000000000000001740431445421625600215540ustar00rootroot00000000000000\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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 LIMIT 2 OFFSET 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 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 LIMIT 2 OFFSET 2; c1 | c1 ----+---- 11 | (1 row) -- 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (5 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` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c1` IS NULL, r2.`c1` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r1.`c3` IS NULL, r1.`c3` ASC (4 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 -> Merge Full Join Output: t1.c1, t2.c1 Merge 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1, t2.c2, t2.c3, t2.c4 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c3` IS NULL, r2.`c3` ASC, r2.`c1` IS NULL, r2.`c1` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (11 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; 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 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Limit Output: 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (13 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 2; c1 ---- 1 2 (2 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 round(5.4) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 5 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT round(5.4) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 (5 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 8 OFFSET round(2.2); 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 8 OFFSET round(2.2); c1 | c1 | c1 ----+----+---- 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (8 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 ---------------------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` ORDER BY `c1` IS NULL, `c1` ASC (11 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 ---------------------------------------------------------------------------------------------------------------- Incremental Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 Presorted Key: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (16 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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; -- not pushed down, different view owners QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 Presorted Key: fdw139_t1.c1 -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (15 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r9.`c1` IS NULL, r9.`c1` ASC, r9.`c2` IS NULL, r9.`c2` ASC (4 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; 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; -- not pushed down, view owner not current user QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 Presorted Key: fdw139_t1.c1 -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (15 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC, r2.`c2` IS NULL, r2.`c2` ASC (4 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; 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC (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) -- FDW-129: Limit and offset pushdown with join pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 (2 rows) -- Limit as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Limit as ALL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Offset as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 3 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 (3 rows) -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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'); CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2); 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge Append Sort Key: t1.c1, t3.c3 -> 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)) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r10.`c3` IS NULL, r10.`c3` ASC -> 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)) ORDER BY r7.`c1` IS NULL, r7.`c1` ASC, r11.`c3` IS NULL, r11.`c3` ASC (10 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 ---------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Merge Append Sort Key: t1.c3, t1.c1 -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC (20 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Merge Append Sort Key: t1.c1, t1.c2 -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC, r4.`c2` IS NULL, r4.`c2` ASC -> 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)) ORDER BY r5.`c1` IS NULL, r5.`c1` ASC, r5.`c2` IS NULL, r5.`c2` ASC (10 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 ----------------------------------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fprt1.c1, 't1_phv'::text, fprt2.c2, ('t2_phv'::text) Sort Key: fprt1.c1, fprt2.c2 Presorted Key: fprt1.c1 -> Merge Append Sort Key: fprt1.c1 -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (28 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_9_1/expected/join_pushdown_1.out000066400000000000000000001735241445421625600217770ustar00rootroot00000000000000\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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 LIMIT 2 OFFSET 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 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 LIMIT 2 OFFSET 2; c1 | c1 ----+---- 11 | (1 row) -- 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (5 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` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c1` IS NULL, r2.`c1` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r1.`c3` IS NULL, r1.`c3` ASC (4 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 -> Merge Full Join Output: t1.c1, t2.c1 Merge 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1, t2.c2, t2.c3, t2.c4 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c3` IS NULL, r2.`c3` ASC, r2.`c1` IS NULL, r2.`c1` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (11 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; 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 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Limit Output: 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (13 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 2; c1 ---- 1 2 (2 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 round(5.4) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 5 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT round(5.4) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 (5 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 8 OFFSET round(2.2); 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 8 OFFSET round(2.2); c1 | c1 | c1 ----+----+---- 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (8 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 ---------------------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` ORDER BY `c1` IS NULL, `c1` ASC (11 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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; -- not pushed down, different view owners QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (14 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r9.`c1` IS NULL, r9.`c1` ASC, r9.`c2` IS NULL, r9.`c2` ASC (4 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; 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; -- not pushed down, view owner not current user QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (14 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC, r2.`c2` IS NULL, r2.`c2` ASC (4 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; 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC (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) -- FDW-129: Limit and offset pushdown with join pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 (2 rows) -- Limit as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Limit as ALL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Offset as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 3 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 (3 rows) -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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'); CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2); 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge Append Sort Key: t1.c1, t3.c3 -> 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)) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r10.`c3` IS NULL, r10.`c3` ASC -> 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)) ORDER BY r7.`c1` IS NULL, r7.`c1` ASC, r11.`c3` IS NULL, r11.`c3` ASC (10 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 ---------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Merge Append Sort Key: t1.c3, t1.c1 -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC (20 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Merge Append Sort Key: t1.c1, t1.c2 -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC, r4.`c2` IS NULL, r4.`c2` ASC -> 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)) ORDER BY r5.`c1` IS NULL, r5.`c1` ASC, r5.`c2` IS NULL, r5.`c2` ASC (10 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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_9_1/expected/join_pushdown_2.out000066400000000000000000002020021445421625600217600ustar00rootroot00000000000000\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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 LIMIT 2 OFFSET 2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 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 LIMIT 2 OFFSET 2; c1 | c1 ----+---- 11 | (1 row) -- 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (5 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` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c1` IS NULL, r2.`c1` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r1.`c3` IS NULL, r1.`c3` ASC (4 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 -> Merge Full Join Output: t1.c1, t2.c1 Merge 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1, t2.c2, t2.c3, t2.c4 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (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. 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.* -> 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (17 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. 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.* -> 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (17 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. 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.* -> 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (17 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. 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.* -> 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (17 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (11 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; 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 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Limit Output: 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (13 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 2; c1 ---- 1 2 (2 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 round(5.4) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT round(5.4) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 (5 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 8 OFFSET round(2.2); 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 8 OFFSET round(2.2); c1 | c1 | c1 ----+----+---- 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (8 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 ---------------------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` ORDER BY `c1` IS NULL, `c1` ASC (11 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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; -- not pushed down, different view owners QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (14 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r9.`c1` IS NULL, r9.`c1` ASC, r9.`c2` IS NULL, r9.`c2` ASC (4 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; 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; -- not pushed down, view owner not current user QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (14 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC, r2.`c2` IS NULL, r2.`c2` ASC (4 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; 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC (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) -- FDW-129: Limit and offset pushdown with join pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 (2 rows) -- Limit as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Limit as ALL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Offset as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 (3 rows) -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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'); CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2); 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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge Append Sort Key: t1.c1, t3.c3 -> 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)) ORDER BY r7.`c1` IS NULL, r7.`c1` ASC, r13.`c3` IS NULL, r13.`c3` ASC -> 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)) ORDER BY r8.`c1` IS NULL, r8.`c1` ASC, r14.`c3` IS NULL, r14.`c3` ASC (10 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 ---------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Merge Append Sort Key: t1.c3, t1.c1 -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC (20 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Merge Append Sort Key: t1.c1, t1.c2 -> 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)) ORDER BY r5.`c1` IS NULL, r5.`c1` ASC, r5.`c2` IS NULL, r5.`c2` ASC -> 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)) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r6.`c2` IS NULL, r6.`c2` ASC (10 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (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_9_1/expected/join_pushdown_3.out000066400000000000000000001740431445421625600217760ustar00rootroot00000000000000\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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 LIMIT 2 OFFSET 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 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 LIMIT 2 OFFSET 2; c1 | c1 ----+---- 11 | (1 row) -- 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (5 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` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c1` IS NULL, r2.`c1` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r1.`c3` IS NULL, r1.`c3` ASC (4 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 -> Merge Full Join Output: t1.c1, t2.c1 Merge 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1, t2.c2, t2.c3, t2.c4 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` ORDER BY `c1` IS NULL, `c1` ASC (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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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. 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r2.`c3` IS NULL, r2.`c3` ASC, r2.`c1` IS NULL, r2.`c1` ASC (4 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (4 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; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (11 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; 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 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Limit Output: 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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` ORDER BY `c2` IS NULL, `c2` ASC (13 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 2; c1 ---- 1 2 (2 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 round(5.4) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 5 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT round(5.4) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 (5 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 8 OFFSET round(2.2); 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 8 OFFSET round(2.2); c1 | c1 | c1 ----+----+---- 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (8 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 ---------------------------------------------------------------------------------------------------------------------------------- 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` ORDER BY `c1` IS NULL, `c1` ASC (11 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 ---------------------------------------------------------------------------------------------------------------- Incremental Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 Presorted Key: t1.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` ORDER BY `c1` IS NULL, `c1` ASC -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` ORDER BY `c1` IS NULL, `c1` ASC (16 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. 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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`)))) ORDER BY r1.`c3` IS NULL, r1.`c3` ASC, r1.`c1` IS NULL, r1.`c1` ASC (5 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; -- not pushed down, different view owners QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 Presorted Key: fdw139_t1.c1 -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (15 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 r4.`c1`, r5.`c2`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 LEFT JOIN `mysql_fdw_regress`.`test2` r5 ON (((r4.`c1` = r5.`c1`)))) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC, r5.`c1` IS NULL, r5.`c1` ASC, r5.`c2` IS NULL, r5.`c2` ASC (4 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; 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; -- not pushed down, view owner not current user QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 Presorted Key: fdw139_t1.c1 -> 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` ORDER BY `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c1` IS NULL, `c1` ASC (15 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; 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; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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 r4.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r4 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r4.`c1` = r2.`c1`)))) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC, r2.`c2` IS NULL, r2.`c2` ASC (4 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; 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC (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) -- FDW-129: Limit and offset pushdown with join pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 2 OFFSET 2 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; c1 | c1 ----+---- 1 | 12 2 | 1 (2 rows) -- Limit as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Limit as ALL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC (6 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; c1 | c1 ----+---- 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (8 rows) -- Offset as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY r1.`c1` IS NULL, r1.`c1` ASC, r2.`c1` IS NULL, r2.`c1` ASC LIMIT 3 (4 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 (3 rows) -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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'); CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2); 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Merge Append Sort Key: t1.c1, t3.c3 -> 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)) ORDER BY r6.`c1` IS NULL, r6.`c1` ASC, r10.`c3` IS NULL, r10.`c3` ASC -> 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)) ORDER BY r7.`c1` IS NULL, r7.`c1` ASC, r11.`c3` IS NULL, r11.`c3` ASC (10 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 ---------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Merge Append Sort Key: t1.c3, t1.c1 -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c3` IS NULL, `c3` ASC, `c1` IS NULL, `c1` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC -> 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` ORDER BY `c2` IS NULL, `c2` ASC (20 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Merge Append Sort Key: t1.c1, t1.c2 -> 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)) ORDER BY r4.`c1` IS NULL, r4.`c1` ASC, r4.`c2` IS NULL, r4.`c2` ASC -> 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)) ORDER BY r5.`c1` IS NULL, r5.`c1` ASC, r5.`c2` IS NULL, r5.`c2` ASC (10 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 ----------------------------------------------------------------------------------------------------------------------------------------------- Incremental Sort Output: fprt1.c1, 't1_phv'::text, fprt2.c2, ('t2_phv'::text) Sort Key: fprt1.c1, fprt2.c2 Presorted Key: fprt1.c1 -> Merge Append Sort Key: fprt1.c1 -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC -> 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)) ORDER BY `c1` IS NULL, `c1` ASC -> 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)) ORDER BY `c2` IS NULL, `c2` ASC (28 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_9_1/expected/limit_offset_pushdown.out000066400000000000000000000262051445421625600232750ustar00rootroot00000000000000\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 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 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'); INSERT INTO f_test_tbl2 VALUES(50, 'IT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(60, 'DB SERVER', 'PUNE'); SELECT * FROM f_test_tbl2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- LIMIT/OFFSET pushdown. -- Limit with Offset should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 3 OFFSET 2 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; c1 | c2 | c3 ----+-------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE (3 rows) -- Only Limit should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 3 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI (3 rows) -- Expression in Limit clause. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 3 OFFSET 2 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; c1 | c2 | c3 ----+-------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE (3 rows) -- Only Offset without Limit should not get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; c1 | c2 | c3 ----+-----------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (4 rows) -- Limit ALL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; QUERY PLAN -------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; QUERY PLAN -------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; c1 | c2 | c3 ----+-----------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (4 rows) -- Limit 0 and Offset 0 EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 0 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; c1 | c2 | c3 ----+----+---- (0 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 0 OFFSET 0 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; c1 | c2 | c3 ----+----+---- (0 rows) -- Offset NULL. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT 3 (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI (3 rows) -- Limit with placeholder. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC LIMIT ? InitPlan 1 (returns $0) -> Foreign Scan Output: (count(*)) Relations: Aggregate on (mysql_fdw_regress.f_test_tbl2) Remote query: SELECT count(*) FROM `mysql_fdw_regress`.`test_tbl2` (8 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- Limit with expression, should not pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 InitPlan 1 (returns $0) -> Foreign Scan Output: (count(*)) Relations: Aggregate on (mysql_fdw_regress.f_test_tbl2) Remote query: SELECT count(*) FROM `mysql_fdw_regress`.`test_tbl2` -> Foreign Scan on public.f_test_tbl2 Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (10 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR (4 rows) DELETE FROM f_test_tbl2; 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_9_1/expected/limit_offset_pushdown_1.out000066400000000000000000000265161445421625600235220ustar00rootroot00000000000000\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 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 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'); INSERT INTO f_test_tbl2 VALUES(50, 'IT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(60, 'DB SERVER', 'PUNE'); SELECT * FROM f_test_tbl2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- LIMIT/OFFSET pushdown. -- Limit with Offset should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; c1 | c2 | c3 ----+-------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE (3 rows) -- Only Limit should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI (3 rows) -- Expression in Limit clause. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; c1 | c2 | c3 ----+-------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE (3 rows) -- Only Offset without Limit should not get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; c1 | c2 | c3 ----+-----------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (4 rows) -- Limit ALL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; QUERY PLAN -------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- Limit NULL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; QUERY PLAN -------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; c1 | c2 | c3 ----+-----------+-------- 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (4 rows) -- Limit 0 and Offset 0 EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; c1 | c2 | c3 ----+----+---- (0 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; c1 | c2 | c3 ----+----+---- (0 rows) -- Offset NULL. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: c1, c2, c3 -> Foreign Scan on public.f_test_tbl2 Output: c1, c2, c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (5 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI (3 rows) -- Limit with placeholder. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 InitPlan 1 (returns $0) -> Foreign Scan Output: (count(*)) Relations: Aggregate on (mysql_fdw_regress.f_test_tbl2) Remote query: SELECT count(*) FROM `mysql_fdw_regress`.`test_tbl2` -> Foreign Scan on public.f_test_tbl2 Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (10 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR 50 | IT | PUNE 60 | DB SERVER | PUNE (6 rows) -- Limit with expression, should not pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 InitPlan 1 (returns $0) -> Foreign Scan Output: (count(*)) Relations: Aggregate on (mysql_fdw_regress.f_test_tbl2) Remote query: SELECT count(*) FROM `mysql_fdw_regress`.`test_tbl2` -> Foreign Scan on public.f_test_tbl2 Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl2` ORDER BY `c1` IS NULL, `c1` ASC (10 rows) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR (4 rows) DELETE FROM f_test_tbl2; 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_9_1/expected/misc.out000066400000000000000000000266221445421625600176200ustar00rootroot00000000000000\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 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 foreign tables and insert data. CREATE FOREIGN TABLE fdw519_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 fdw519_ft2(c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE FOREIGN TABLE fdw519_ft3 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9), c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO fdw519_ft3 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO fdw519_ft3 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); -- Check truncatable option with invalid values. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'abc'); ERROR: truncatable requires a Boolean value ALTER FOREIGN TABLE fdw519_ft1 OPTIONS (ADD truncatable 'abc'); ERROR: truncatable requires a Boolean value -- Default behavior, should truncate. TRUNCATE fdw519_ft1; SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- (0 rows) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); -- Set truncatable to false -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'false'); -- Truncate the table. TRUNCATE fdw519_ft1; ERROR: foreign table "fdw519_ft1" does not allow truncates SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Set truncatable to true -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1; SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- (0 rows) -- truncatable to true on Server but false on table level. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (ADD truncatable 'true'); SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) TRUNCATE fdw519_ft2; SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----+---- (0 rows) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); -- truncatable to true on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ALTER TABLE fdw519_ft1 OPTIONS (ADD truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2; ERROR: foreign table "fdw519_ft1" does not allow truncates SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) -- truncatable to false on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2; ERROR: foreign table "fdw519_ft1" does not allow truncates SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) -- Truncate from different servers. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ALTER SERVER mysql_svr1 OPTIONS (ADD truncatable 'true'); ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2, fdw519_ft3; SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- (0 rows) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----+---- (0 rows) SELECT * FROM fdw519_ft3 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ----+----+----+----+----+----+----+---- (0 rows) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Truncate with CASCADE is not supported. TRUNCATE fdw519_ft1 CASCADE; ERROR: CASCADE option in TRUNCATE is not supported by this FDW SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Default is RESTRICT, so it is allowed. TRUNCATE fdw519_ft1 RESTRICT; SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- (0 rows) -- Should throw an error if primary key is referenced by foreign key. CREATE FOREIGN TABLE fdw519_ft4(stu_id varchar(10), stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student1'); CREATE FOREIGN TABLE fdw519_ft5(dept_id int, stu_id varchar(10)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'dept'); TRUNCATE fdw519_ft4; ERROR: failed to execute the MySQL query: Cannot truncate a table referenced in a foreign key constraint (`mysql_fdw_regress1`.`dept`, CONSTRAINT `dept_ibfk_1`) -- FDW-520: Support generated columns in IMPORT FOREIGN SCHEMA command. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); \d fdw520 Foreign table "public.fdw520" Column | Type | Collation | Nullable | Default | FDW options ---------+---------+-----------+----------+----------------------------------------------+------------- c1 | integer | | not null | | c `"" 2 | integer | | | | c3 | integer | | | generated always as ("c `"""" 2" * 2) stored | c4 | integer | | not null | generated always as ("c `"""" 2" * 4) stored | Server: mysql_svr FDW options: (dbname 'mysql_fdw_regress', table_name 'fdw520') -- Generated column refers to another generated column, should throw an error: IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520_1) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); ERROR: cannot use generated column "c3" in column generation expression LINE 5: c4 int GENERATED ALWAYS AS ("c3" * 4) STORED NOT NULL ^ DETAIL: A generated column cannot reference another generated column. QUERY: CREATE FOREIGN TABLE fdw520_1 ( c1 int NOT NULL, c2 int, c3 int GENERATED ALWAYS AS ("c2" * 2) STORED, c4 int GENERATED ALWAYS AS ("c3" * 4) STORED NOT NULL ) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'fdw520_1'); CONTEXT: importing foreign table "fdw520_1" -- import_generated as false. DROP FOREIGN TABLE fdw520; IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'false'); \d fdw520 Foreign table "public.fdw520" Column | Type | Collation | Nullable | Default | FDW options ---------+---------+-----------+----------+---------+------------- c1 | integer | | not null | | c `"" 2 | integer | | | | c3 | integer | | | | c4 | integer | | not null | | Server: mysql_svr FDW options: (dbname 'mysql_fdw_regress', table_name 'fdw520') -- Without import_generated option, default is true. DROP FOREIGN TABLE fdw520; IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public; \d fdw520 Foreign table "public.fdw520" Column | Type | Collation | Nullable | Default | FDW options ---------+---------+-----------+----------+----------------------------------------------+------------- c1 | integer | | not null | | c `"" 2 | integer | | | | c3 | integer | | | generated always as ("c `"""" 2" * 2) stored | c4 | integer | | not null | generated always as ("c `"""" 2" * 4) stored | Server: mysql_svr FDW options: (dbname 'mysql_fdw_regress', table_name 'fdw520') -- FDW-521: Insert and update operations on table having generated columns. INSERT INTO fdw520(c1, "c `"""" 2") VALUES(1, 2); INSERT INTO fdw520(c1, "c `"""" 2", c3, c4) VALUES(2, 4, DEFAULT, DEFAULT); -- Should fail. INSERT INTO fdw520 VALUES(1, 2, 3, 4); ERROR: cannot insert a non-DEFAULT value into column "c3" DETAIL: Column "c3" is a generated column. SELECT * FROM fdw520 ORDER BY 1; c1 | c `"" 2 | c3 | c4 ----+---------+----+---- 1 | 2 | 4 | 8 2 | 4 | 8 | 16 (2 rows) UPDATE fdw520 SET "c `"""" 2" = 20 WHERE c1 = 2; SELECT * FROM fdw520 ORDER BY 1; c1 | c `"" 2 | c3 | c4 ----+---------+----+---- 1 | 2 | 4 | 8 2 | 20 | 40 | 80 (2 rows) -- Should fail. UPDATE fdw520 SET c4 = 20 WHERE c1 = 2; ERROR: column "c4" can only be updated to DEFAULT DETAIL: Column "c4" is a generated column. UPDATE fdw520 SET c3 = 20 WHERE c1 = 2; ERROR: column "c3" can only be updated to DEFAULT DETAIL: Column "c3" is a generated column. -- Cleanup DELETE FROM fdw519_ft1; DELETE FROM fdw519_ft2; DELETE FROM fdw519_ft3; DELETE FROM fdw520; DROP FOREIGN TABLE fdw519_ft1; DROP FOREIGN TABLE fdw519_ft2; DROP FOREIGN TABLE fdw519_ft3; DROP FOREIGN TABLE fdw519_ft4; DROP FOREIGN TABLE fdw519_ft5; DROP FOREIGN TABLE fdw520; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/expected/misc_1.out000066400000000000000000000301301445421625600200250ustar00rootroot00000000000000\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 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 foreign tables and insert data. CREATE FOREIGN TABLE fdw519_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 fdw519_ft2(c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE FOREIGN TABLE fdw519_ft3 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9), c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO fdw519_ft3 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO fdw519_ft3 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); -- Check truncatable option with invalid values. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'abc'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: host, port, init_command, secure_auth, use_remote_estimate, fetch_size, reconnect, character_set, sql_mode, ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher ALTER FOREIGN TABLE fdw519_ft1 OPTIONS (ADD truncatable 'abc'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: dbname, table_name, max_blob_size, fetch_size -- Default behavior, should truncate. TRUNCATE fdw519_ft1; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); ERROR: failed to execute the MySQL query: Duplicate entry '1' for key 'student.PRIMARY' -- Set truncatable to false -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'false'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: host, port, init_command, secure_auth, use_remote_estimate, fetch_size, reconnect, character_set, sql_mode, ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher -- Truncate the table. TRUNCATE fdw519_ft1; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Set truncatable to true -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found TRUNCATE fdw519_ft1; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- truncatable to true on Server but false on table level. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ERROR: option "truncatable" not found ALTER TABLE fdw519_ft2 OPTIONS (ADD truncatable 'true'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: dbname, table_name, max_blob_size, fetch_size SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) TRUNCATE fdw519_ft2; ERROR: "fdw519_ft2" is not a table SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); ERROR: failed to execute the MySQL query: Duplicate entry '1' for key 'student.PRIMARY' INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); ERROR: failed to execute the MySQL query: Duplicate entry '10' for key 'test_tbl2.PRIMARY' INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); ERROR: failed to execute the MySQL query: Duplicate entry '20' for key 'test_tbl2.PRIMARY' -- truncatable to true on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found ALTER TABLE fdw519_ft1 OPTIONS (ADD truncatable 'false'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: dbname, table_name, max_blob_size, fetch_size ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found TRUNCATE fdw519_ft1, fdw519_ft2; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) -- truncatable to false on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ERROR: option "truncatable" not found ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'false'); ERROR: option "truncatable" not found ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found TRUNCATE fdw519_ft1, fdw519_ft2; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) -- Truncate from different servers. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found ALTER SERVER mysql_svr1 OPTIONS (ADD truncatable 'true'); ERROR: invalid option "truncatable" HINT: Valid options in this context are: host, port, init_command, secure_auth, use_remote_estimate, fetch_size, reconnect, character_set, sql_mode, ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'true'); ERROR: option "truncatable" not found TRUNCATE fdw519_ft1, fdw519_ft2, fdw519_ft3; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) SELECT * FROM fdw519_ft2 ORDER BY 1; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE (2 rows) SELECT * FROM fdw519_ft3 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+-----------+------+------------+------------+-----+---- 100 | EMP1 | ADMIN | 1300 | 12-17-1980 | 800.23000 | | 20 200 | EMP2 | SALESMAN | 600 | 02-20-1981 | 1600.00000 | 300 | 30 (2 rows) INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); ERROR: failed to execute the MySQL query: Duplicate entry '1' for key 'student.PRIMARY' SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Truncate with CASCADE is not supported. TRUNCATE fdw519_ft1 CASCADE; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Default is RESTRICT, so it is allowed. TRUNCATE fdw519_ft1 RESTRICT; ERROR: "fdw519_ft1" is not a table SELECT * FROM fdw519_ft1 ORDER BY 1; stu_id | stu_name | stu_dept --------+----------+---------- 1 | One | 101 (1 row) -- Should throw an error if primary key is referenced by foreign key. CREATE FOREIGN TABLE fdw519_ft4(stu_id varchar(10), stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student1'); CREATE FOREIGN TABLE fdw519_ft5(dept_id int, stu_id varchar(10)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'dept'); TRUNCATE fdw519_ft4; ERROR: "fdw519_ft4" is not a table -- FDW-520: Support generated columns in IMPORT FOREIGN SCHEMA command. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); ERROR: invalid option "import_generated" \d fdw520 -- Generated column refers to another generated column, should throw an error: IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520_1) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); ERROR: invalid option "import_generated" -- import_generated as false. DROP FOREIGN TABLE fdw520; ERROR: foreign table "fdw520" does not exist IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'false'); ERROR: invalid option "import_generated" \d fdw520 -- Without import_generated option, default is true. DROP FOREIGN TABLE fdw520; ERROR: foreign table "fdw520" does not exist IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public; \d fdw520 Foreign table "public.fdw520" Column | Type | Collation | Nullable | Default | FDW options ---------+---------+-----------+----------+---------+------------- c1 | integer | | not null | | c `"" 2 | integer | | | | c3 | integer | | | | c4 | integer | | not null | | Server: mysql_svr FDW options: (dbname 'mysql_fdw_regress', table_name 'fdw520') -- FDW-521: Insert and update operations on table having generated columns. INSERT INTO fdw520(c1, "c `"""" 2") VALUES(1, 2); ERROR: failed to prepare the MySQL query: The value specified for generated column 'c3' in table 'fdw520' is not allowed. INSERT INTO fdw520(c1, "c `"""" 2", c3, c4) VALUES(2, 4, DEFAULT, DEFAULT); ERROR: failed to prepare the MySQL query: The value specified for generated column 'c3' in table 'fdw520' is not allowed. -- Should fail. INSERT INTO fdw520 VALUES(1, 2, 3, 4); ERROR: failed to prepare the MySQL query: The value specified for generated column 'c3' in table 'fdw520' is not allowed. SELECT * FROM fdw520 ORDER BY 1; c1 | c `"" 2 | c3 | c4 ----+---------+----+---- (0 rows) UPDATE fdw520 SET "c `"""" 2" = 20 WHERE c1 = 2; SELECT * FROM fdw520 ORDER BY 1; c1 | c `"" 2 | c3 | c4 ----+---------+----+---- (0 rows) -- Should fail. UPDATE fdw520 SET c4 = 20 WHERE c1 = 2; ERROR: failed to prepare the MySQL query: The value specified for generated column 'c4' in table 'fdw520' is not allowed. UPDATE fdw520 SET c3 = 20 WHERE c1 = 2; ERROR: failed to prepare the MySQL query: The value specified for generated column 'c3' in table 'fdw520' is not allowed. -- Cleanup DELETE FROM fdw519_ft1; DELETE FROM fdw519_ft2; DELETE FROM fdw519_ft3; DELETE FROM fdw520; DROP FOREIGN TABLE fdw519_ft1; DROP FOREIGN TABLE fdw519_ft2; DROP FOREIGN TABLE fdw519_ft3; DROP FOREIGN TABLE fdw519_ft4; DROP FOREIGN TABLE fdw519_ft5; DROP FOREIGN TABLE fdw520; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/expected/pushdown.out000066400000000000000000000664331445421625600205400ustar00rootroot00000000000000\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 ----------------------------------------------------------------------------------------------------------------------------------------------------- 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')) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ------------------------------------------------------------------------------------------------------------------------------------------ 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)) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ----------------------------------------------------------------------------------------------------------------------------------------------- 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)) ORDER BY `c1` IS NULL, `c1` ASC (3 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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')) ORDER BY `c1` IS NULL, `c1` ASC (3 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------- 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')) ORDER BY `c1` IS NULL, `c1` ASC (3 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------- 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')) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------- 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')) ORDER BY `c1` IS NULL, `c1` ASC (3 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 ------------------------------------------------------------------------------------------------------------------------------------------------------- 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%')) ORDER BY `c1` IS NULL, `c1` ASC (3 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) -- FDW-516: IS [NOT] DISTINCT FROM clause should deparse correctly. CREATE FOREIGN TABLE f_distinct_test (id int, c1 int, c2 int, c3 text, c4 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'distinct_test'); INSERT INTO f_distinct_test VALUES (1, 1, 1, 'abc', 'abc'), (2, 2, NULL, 'abc', 'NULL'), (3, NULL, NULL, 'NULL', 'NULL'), (4, 3, 4, 'abc', 'pqr'), (5, 4, 5, 'abc', 'abc'), (6, 5, 5, 'abc', 'pqr'); SELECT * FROM f_distinct_test ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 2 | 2 | | abc | NULL 3 | | | NULL | NULL 4 | 3 | 4 | abc | pqr 5 | 4 | 5 | abc | abc 6 | 5 | 5 | abc | pqr (6 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT (`c1` <=> `c2`))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+------ 2 | 2 | | abc | NULL 4 | 3 | 4 | abc | pqr 5 | 4 | 5 | abc | abc (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) ORDER BY id; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE (((`c1` <=> `c2`))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 3 | | | NULL | NULL 6 | 5 | 5 | abc | pqr (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c3) IS DISTINCT FROM (c4) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT (`c3` <=> `c4`))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c3) IS DISTINCT FROM (c4) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+------ 2 | 2 | | abc | NULL 4 | 3 | 4 | abc | pqr 6 | 5 | 5 | abc | pqr (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE (((`c3` <=> `c4`))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 3 | | | NULL | NULL 5 | 4 | 5 | abc | abc (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) and (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT (`c1` <=> `c2`))) AND (((`c3` <=> `c4`))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) and (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+----- 5 | 4 | 5 | abc | abc (1 row) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) or (c3) IS DISTINCT FROM (c4) ORDER BY id; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((((`c1` <=> `c2`)) OR (NOT (`c3` <=> `c4`)))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) or (c3) IS DISTINCT FROM (c4) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 2 | 2 | | abc | NULL 3 | | | NULL | NULL 4 | 3 | 4 | abc | pqr 6 | 5 | 5 | abc | pqr (5 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT (((`c1` <=> `c2`)) <=> ((`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+----- 5 | 4 | 5 | abc | abc 6 | 5 | 5 | abc | pqr (2 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((((NOT (`c1` <=> `c2`)) <=> (NOT (`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 2 | 2 | | abc | NULL 3 | | | NULL | NULL 4 | 3 | 4 | abc | pqr (4 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT (((`c1` <=> `c2`)) <=> (NOT (`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 2 | 2 | | abc | NULL 3 | | | NULL | NULL 4 | 3 | 4 | abc | pqr (4 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((((NOT (`c1` <=> `c2`)) <=> ((`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+----- 5 | 4 | 5 | abc | abc 6 | 5 | 5 | abc | pqr (2 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE ((NOT ((NOT (`c1` <=> `c2`)) <=> ((`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+------+------ 1 | 1 | 1 | abc | abc 2 | 2 | | abc | NULL 3 | | | NULL | NULL 4 | 3 | 4 | abc | pqr (4 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_distinct_test Output: id, c1, c2, c3, c4 Remote query: SELECT `id`, `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`distinct_test` WHERE (((((`c1` <=> `c2`)) <=> (NOT (`c3` <=> `c4`))))) ORDER BY `id` IS NULL, `id` ASC (3 rows) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; id | c1 | c2 | c3 | c4 ----+----+----+-----+----- 5 | 4 | 5 | abc | abc 6 | 5 | 5 | abc | pqr (2 rows) -- FDW-562: Test ORDER BY with user defined operators. -- Create the operator family required for the test. CREATE OPERATOR PUBLIC.<^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4EQ ); CREATE OPERATOR PUBLIC.=^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4LT ); CREATE OPERATOR PUBLIC.>^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4GT ); CREATE OPERATOR FAMILY my_op_family USING btree; CREATE FUNCTION MY_OP_CMP(A INT, B INT) RETURNS INT AS $$ BEGIN RETURN BTINT4CMP(A, B); END $$ LANGUAGE PLPGSQL; CREATE OPERATOR CLASS my_op_class FOR TYPE INT USING btree FAMILY my_op_family AS OPERATOR 1 PUBLIC.<^, OPERATOR 3 PUBLIC.=^, OPERATOR 5 PUBLIC.>^, FUNCTION 1 my_op_cmp(INT, INT); -- FDW-562: Test ORDER BY with user defined operators. -- User defined operators are not pushed down. EXPLAIN (COSTS FALSE, VERBOSE) SELECT * FROM f_test_tbl1 ORDER BY c1 USING OPERATOR(public.<^); QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: c1, c2, c3, c4, c5, c6, c7, c8 Sort Key: f_test_tbl1.c1 USING <^ -> 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` (6 rows) EXPLAIN (COSTS FALSE, VERBOSE) SELECT MIN(c1) FROM f_test_tbl1 ORDER BY 1 USING OPERATOR(public.<^); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: ($0) Sort Key: ($0) USING <^ InitPlan 1 (returns $0) -> Limit Output: f_test_tbl1.c1 -> Foreign Scan on public.f_test_tbl1 Output: f_test_tbl1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c1` IS NOT NULL)) ORDER BY `c1` IS NULL, `c1` ASC -> Result Output: $0 (11 rows) -- Cleanup DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DELETE FROM f_distinct_test; DROP OPERATOR CLASS my_op_class USING btree; DROP FUNCTION my_op_cmp(a INT, b INT); DROP OPERATOR FAMILY my_op_family USING btree; DROP OPERATOR public.>^(INT, INT); DROP OPERATOR public.=^(INT, INT); DROP OPERATOR public.<^(INT, INT); DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP FOREIGN TABLE f_distinct_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/expected/select.out000066400000000000000000002144761445421625600201520ustar00rootroot00000000000000\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 ------------------- 20901 (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 ----------------------------------------------------------------------------------------------------------- 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` ORDER BY `a` IS NULL, `a` ASC -> 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`)) (8 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-217: IMPORT FOREIGN SCHEMA command with 'import_enum_as_text' option as -- true should map MySQL ENUM type to TEXT type in PG. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (enum_t1, enum_t2) FROM SERVER mysql_svr INTO public OPTIONS (import_enum_as_text 'true'); 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 | text enum_t2 | text (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 --------------------------------------------------------------------------------------------------------------------------- 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` = ?)) ORDER BY `c1` IS NULL, `c1` ASC 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 (10 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 -> 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` ORDER BY `c1` IS NULL, `c1` ASC (6 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) -- FDW-417: Updating a NOT NULL column with NULL should throw an error -- if we set sql_mode to STRICT_ALL_TABLES. ALTER SERVER mysql_svr OPTIONS (sql_mode 'ANSI_QUOTES,STRICT_ALL_TABLES'); UPDATE f_mysql_test SET b = NULL WHERE a = 1; ERROR: failed to execute the MySQL query: Column 'b' cannot be null SELECT * FROM f_mysql_test ORDER BY 1; a | b ---+--- 1 | 1 (1 row) ALTER SERVER mysql_svr OPTIONS (DROP sql_mode); UPDATE f_mysql_test SET b = NULL WHERE a = 1; SELECT * FROM f_mysql_test ORDER BY 1; a | b ---+--- 1 | 0 (1 row) UPDATE f_mysql_test SET b = 1 WHERE a = 1; -- We should get a run-time error when sql_mode is set to invalid value. ALTER SERVER mysql_svr OPTIONS (sql_mode 'ABCD'); SELECT * FROM f_mysql_test ORDER BY 1; ERROR: failed to execute the MySQL query: Variable 'sql_mode' can't be set to the value of 'ABCD' ALTER SERVER mysql_svr OPTIONS (DROP sql_mode); -- FDW-426: The numeric value should display correctly per precision and scale -- defined. CREATE FOREIGN TABLE f_test6(c1 numeric(6,2)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; c1 ------- 25.25 (1 row) -- Number with the required precision. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(6,4)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; c1 --------- 25.2525 (1 row) -- Number only with precision. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(6)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; c1 ---- 25 (1 row) -- Number with improper precision and scale, should throw an error. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(3,2)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; ERROR: numeric field overflow DETAIL: A field with precision 3, scale 2 must round to an absolute value less than 10^1. -- FDW-156: Long string data in column greater than MAXDATALEN length should -- be fetched correctly. CREATE FOREIGN TABLE f_test7(c1 int, c2 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test7'); EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, length(c2) FROM f_test7 ORDER BY 1; QUERY PLAN ---------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test7 Output: c1, length(c2) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test7` ORDER BY `c1` IS NULL, `c1` ASC (3 rows) SELECT c1, length(c2) FROM f_test7 ORDER BY 1; c1 | length ----+-------- 1 | 67500 (1 row) -- FDW-130: ORDER BY with collation clause, should not get pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c2 COLLATE "en_US"; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c2)::character varying(10)) Sort Key: f_test_tbl1.c2 COLLATE "en_US" -> Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3, c4, c5, c6, c7, c8, c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` (6 rows) SELECT * FROM f_test_tbl1 ORDER BY c2 COLLATE "en_US"; 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 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 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 (14 rows) -- Order by desc EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c1 DESC; 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` ORDER BY `c1` IS NOT NULL, `c1` DESC (3 rows) SELECT * FROM f_test_tbl1 ORDER BY c1 DESC; 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) -- Order by is not null EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL, c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3, c4, c5, c6, c7, c8, (c7 IS NOT NULL) Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` ORDER BY (`c7` IS NOT NULL) IS NULL, (`c7` IS NOT NULL) ASC, `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL, 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 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 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 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 (14 rows) -- Order by is not null desc EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL DESC, c1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3, c4, c5, c6, c7, c8, (c7 IS NOT NULL) Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` ORDER BY (`c7` IS NOT NULL) IS NOT NULL, (`c7` IS NOT NULL) DESC, `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL DESC, 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 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 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 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) -- Order by desc nulls first EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS FIRST, c1; 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` ORDER BY `c7` IS NOT NULL, `c7` DESC, `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS FIRST, 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 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 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 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 (14 rows) -- Order by asc nulls last EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS LAST, c1; 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` ORDER BY `c7` IS NULL, `c7` DESC, `c1` IS NULL, `c1` ASC (3 rows) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS LAST, c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+-----------+------+------------+------------+------+---- 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 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 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) -- Test LIMIT where ORDER BY is not pushed down due to unsafe pathkeys. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 t1 ORDER BY t1 LIMIT 5; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------ Limit Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* -> Sort Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* Sort Key: t1.* -> Foreign Scan on public.f_test_tbl1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` (8 rows) SELECT * FROM f_test_tbl1 t1 ORDER BY t1 LIMIT 5; 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 (5 rows) -- FDW-322: Test mysql_fdw_display_pushdown_list() function. SELECT count(*) FROM mysql_fdw_display_pushdown_list(); count ------- 250 (1 row) -- FDW:554 - Array expression should not get pushdown to remote MySQL server as -- syntax is not supported. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 t1 WHERE c1 = ANY(ARRAY[100, 200, c1 + 10000]); QUERY PLAN ------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_test_tbl1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 Filter: (t1.c1 = ANY (ARRAY[100, 200, (t1.c1 + 10000)])) Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` (4 rows) SELECT * FROM f_test_tbl1 t1 WHERE c1 = ANY(ARRAY[100, 200, c1 + 10000]); 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 (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 FOREIGN TABLE f_test6; DROP FOREIGN TABLE f_test7; 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_9_1/expected/server_options.out000066400000000000000000000220101445421625600217310ustar00rootroot00000000000000\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 ------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.f_mysql_test Output: a Remote query: SELECT `a` FROM `mysql_fdw_regress`.`mysql_test` WHERE ((`a` < 2)) ORDER BY `a` IS NULL, `a` ASC (3 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; -- FDW-404: Support for character_set option at server level. CREATE SERVER charset101 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( character_set 'utf8' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'charset101' AND srvoptions @> array['character_set=utf8']; count ------- 1 (1 row) ALTER SERVER charset101 OPTIONS( SET character_set 'latin' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'charset101' AND srvoptions @> array['character_set=latin']; count ------- 1 (1 row) -- Cleanup character_set test objects. DROP SERVER charset101; -- Cleanup DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/mysql_fdw--1.0--1.1.sql000066400000000000000000000002341445421625600177730ustar00rootroot00000000000000/* 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_9_1/mysql_fdw--1.0.sql000066400000000000000000000013221445421625600174200ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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-2023, 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_9_1/mysql_fdw--1.1--1.2.sql000066400000000000000000000004321445421625600177750ustar00rootroot00000000000000/* mysql_fdw/mysql_fdw--1.1--1.2.sql */ CREATE OR REPLACE FUNCTION mysql_fdw_display_pushdown_list(IN reload boolean DEFAULT false, OUT object_type text, OUT object_name text) RETURNS SETOF record AS 'MODULE_PATHNAME', 'mysql_display_pushdown_list' LANGUAGE C PARALLEL SAFE; mysql_fdw-REL-2_9_1/mysql_fdw--1.1.sql000066400000000000000000000015061445421625600174250ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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-2023, 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_9_1/mysql_fdw--1.2.sql000066400000000000000000000017571445421625600174360ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw--1.2.sql * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2022-2023, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.2.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; CREATE OR REPLACE FUNCTION mysql_fdw_display_pushdown_list(IN reload boolean DEFAULT false, OUT object_type text, OUT object_name text) RETURNS SETOF record AS 'MODULE_PATHNAME', 'mysql_display_pushdown_list' LANGUAGE C PARALLEL SAFE; mysql_fdw-REL-2_9_1/mysql_fdw.c000066400000000000000000004421371445421625600165070ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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 "catalog/pg_type.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "mysql_pushability.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/paths.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" #if PG_VERSION_NUM >= 160000 #include "parser/parse_relation.h" #endif #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/regproc.h" #include "utils/selfuncs.h" #include "utils/syscache.h" #include "utils/typcache.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_STMT *stmt)); 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.9.1 so number will be 20901 */ #define CODE_VERSION 20901 /* * The number of rows in a foreign relation are estimated to be so less that * an in-memory sort on those many rows wouldn't cost noticeably higher than * the underlying scan. Hence for now, cost sorts same as underlying scans. */ #define DEFAULT_MYSQL_SORT_MULTIPLIER 1 /* * 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 }; /* * This enum describes what's kept in the fdw_private list for a ForeignPath. * We store: * * 1) Boolean flag showing if the remote query has the final sort * 2) Boolean flag showing if the remote query has the LIMIT clause */ enum FdwPathPrivateIndex { /* has-final-sort flag (as an integer Value node) */ FdwPathPrivateHasFinalSort, /* has-limit flag (as an integer Value node) */ FdwPathPrivateHasLimit }; 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); PG_FUNCTION_INFO_V1(mysql_display_pushdown_list); /* * 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); static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid); static void mysqlGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra); static bool mysqlRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot); static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); static List *mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); static void mysqlBeginForeignInsert(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo); static void mysqlEndForeignInsert(EState *estate, ResultRelInfo *resultRelInfo); #if PG_VERSION_NUM >= 140000 static void mysqlExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs); #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); 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); 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); 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); static List *mysql_get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel); static List *mysql_get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel); static void mysql_add_paths_with_pathkeys(PlannerInfo *root, RelOptInfo *rel, Path *epq_path, Cost base_startup_cost, Cost base_total_cost); #if PG_VERSION_NUM >= 120000 static void mysql_add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel); static void mysql_add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra); #endif #if PG_VERSION_NUM >= 160000 static TargetEntry *mysql_tlist_member_match_var(Var *var, List *targetlist); static List *mysql_varlist_append_unique_var(List *varlist, Var *var); #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(PlannerInfo *root, RangeTblEntry *rte); #if PG_VERSION_NUM >= 140000 static char *mysql_remove_quotes(char *s1); #endif /* * 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 */ fdwroutine->RecheckForeignScan = mysqlRecheckForeignScan; /* Support functions for EXPLAIN */ fdwroutine->ExplainForeignScan = mysqlExplainForeignScan; /* Support functions for ANALYZE */ fdwroutine->AnalyzeForeignTable = mysqlAnalyzeForeignTable; /* Support functions for IMPORT FOREIGN SCHEMA */ fdwroutine->ImportForeignSchema = mysqlImportForeignSchema; /* Partition routing and/or COPY from */ fdwroutine->BeginForeignInsert = mysqlBeginForeignInsert; fdwroutine->EndForeignInsert = mysqlEndForeignInsert; /* Support functions for join push-down */ fdwroutine->GetForeignJoinPaths = mysqlGetForeignJoinPaths; /* Support functions for upper relation push-down */ fdwroutine->GetForeignUpperPaths = mysqlGetForeignUpperPaths; #if PG_VERSION_NUM >= 140000 /* Support function for TRUNCATE */ fdwroutine->ExecForeignTruncate = mysqlExecForeignTruncate; #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; char sql_mode[255]; /* * 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 #if PG_VERSION_NUM >= 160000 mysql_build_whole_row_constr_info(festate, tupleDescriptor, fsplan->fs_base_relids, list_length(node->ss.ps.state->es_range_table), whole_row_lists, scan_tlist, fsplan->fdw_scan_tlist); #else 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); #endif /* 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 #if PG_VERSION_NUM >= 160000 rtindex = bms_next_member(fsplan->fs_base_relids, -1); #else rtindex = bms_next_member(fsplan->fs_relids, -1); #endif #if PG_VERSION_NUM >= 160000 rte = exec_rt_fetch(rtindex, estate); userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId(); #else rte = rt_fetch(rtindex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); #endif /* 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->has_var_size_col = false; festate->attinmeta = TupleDescGetAttInMetadata(tupleDescriptor); festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_DEFAULT_SIZES); 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); } snprintf(sql_mode, sizeof(sql_mode), "SET sql_mode = '%s'", options->sql_mode); if (mysql_query(festate->conn, sql_mode) != 0) mysql_error_print(festate->conn); /* 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; if (pgtype == TEXTOID) festate->has_var_size_col = true; 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++; } /* * Set STMT_ATTR_UPDATE_MAX_LENGTH so that mysql_stmt_store_result() can * update metadata MYSQL_FIELD->max_length value, this will be useful to * determine var length column size. */ mysql_stmt_attr_set(festate->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &festate->has_var_size_col); /* 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 #if PG_VERSION_NUM >= 160000 rtindex = bms_next_member(fsplan->fs_base_relids, -1); #else rtindex = bms_next_member(fsplan->fs_relids, -1); #endif 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) ExplainPropertyInteger("Local server startup cost", NULL, 10, es); else ExplainPropertyInteger("Remote server startup cost", NULL, 25, es); } /* 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; char sql_mode[255]; 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); snprintf(sql_mode, sizeof(sql_mode), "SET sql_mode = '%s'", options->sql_mode); if (mysql_query(conn, sql_mode) != 0) mysql_error_print(conn); pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &attrs_used); 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); } pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &fpinfo->attrs_used); 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, NULL, false, false, &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", mysql_quote_identifier(options->svr_database, '`'), mysql_quote_identifier(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, NULL, /* default pathtarget */ baserel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ baserel->lateral_relids, NULL, /* no extra plan */ NULL)); /* no fdw_private data */ /* Add paths with pathkeys */ mysql_add_paths_with_pathkeys(root, baserel, NULL, startup_cost, total_cost); } /* * mysqlGetForeignPlan * Get a foreign scan plan node */ static ForeignScan * mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) { 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; bool has_final_sort = false; bool has_limit = false; /* * Get FDW private data created by mysqlGetForeignUpperPaths(), if any. */ if (best_path->fdw_private) { has_final_sort = intVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); has_limit = intVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); } 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 (IS_UPPER_REL(foreignrel)) scan_var_list = pull_var_clause((Node *) fpinfo->grouped_tlist, PVC_RECURSE_AGGREGATES); else 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 (IS_JOIN_REL(foreignrel)) { 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) { /* * 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. */ Assert(!IS_UPPER_REL(foreignrel)); 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); } } } } 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); } /* * 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, best_path->path.pathkeys, has_final_sort, has_limit, &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 (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { 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. */ return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, fdw_scan_tlist, NIL, outer_plan); } /* * 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; bool doNothing = false; 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"))); /* * ON CONFLICT DO UPDATE and DO NOTHING case with inference specification * should have already been rejected in the optimizer, as presently there * is no way to recognize an arbiter index on a foreign table. Only DO * NOTHING is supported without an inference specification. */ if (plan->onConflictAction == ONCONFLICT_NOTHING) doNothing = true; else if (plan->onConflictAction != ONCONFLICT_NONE) elog(ERROR, "unexpected ON CONFLICT specification: %d", (int) plan->onConflictAction); /* * 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(root, 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(root, rte); /* We also want the rowid column to be available for the update */ targetAttrs = lcons_int(1, targetAttrs); } else targetAttrs = lcons_int(1, targetAttrs); attname = get_attname(foreignTableId, 1, false); /* * Construct the SQL command string. */ switch (operation) { case CMD_INSERT: mysql_deparse_insert(&sql, root, resultRelation, rel, targetAttrs, doNothing); 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; Oid userid; ForeignServer *server; UserMapping *user; ForeignTable *table; #if PG_VERSION_NUM >= 160000 ForeignScan *fsplan = (ForeignScan *) mtstate->ps.plan; #else RangeTblEntry *rte; #endif #if PG_VERSION_NUM >= 160000 userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId(); #else rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); #endif 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; fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_DEFAULT_SIZES); 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; char sql_mode[255]; Oid foreignTableId = RelationGetRelid(resultRelInfo->ri_RelationDesc); fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; n_params = list_length(fmstate->retrieved_attrs); fmstate->mysqlFdwOptions = mysql_get_options(foreignTableId, true); oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt); mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * n_params); isnull = (bool *) palloc0(sizeof(bool) * n_params); snprintf(sql_mode, sizeof(sql_mode), "SET sql_mode = '%s'", fmstate->mysqlFdwOptions->sql_mode); if (mysql_query(fmstate->conn, sql_mode) != 0) mysql_error_print(fmstate->conn); 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; #if PG_VERSION_NUM >= 140000 TupleDesc tupdesc = RelationGetDescr(rel); #endif 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; } #if PG_VERSION_NUM >= 140000 /* Ignore generated columns; they are set to DEFAULT. */ if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated) continue; #endif 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+) */ static List * mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) { List *commands = NIL; bool import_default = false; bool import_not_null = true; bool import_enum_as_text = false; ForeignServer *server; UserMapping *user; mysql_opt *options; MYSQL *conn; StringInfoData buf; MYSQL_RES *volatile res = NULL; MYSQL_ROW row; ListCell *lc; #if PG_VERSION_NUM >= 140000 bool import_generated = true; #endif /* 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 if (strcmp(def->defname, "import_enum_as_text") == 0) import_enum_as_text = defGetBoolean(def); #if PG_VERSION_NUM >= 140000 else if (strcmp(def->defname, "import_generated") == 0) import_generated = defGetBoolean(def); #endif 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')," #if PG_VERSION_NUM >= 140000 " c.COLUMN_DEFAULT," " c.EXTRA," " c.GENERATION_EXPRESSION" #else " c.COLUMN_DEFAULT" #endif " 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 PG_VERSION_NUM >= 140000 char *attgenerated; #endif /* * 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) { /* * If import_enum_as_text is set, then map MySQL enum type to * text while import, else emit a warning to create mapping * TYPE. */ if (import_enum_as_text) typename = "text"; else 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); #if PG_VERSION_NUM >= 140000 /* * Add GENERATED if needed. Map VIRTUAL GENERATED to STORED in * Postgres. */ attgenerated = (row[6] == NULL ? (char *) NULL : row[6]); if (import_generated && attgenerated != NULL && (strcmp(attgenerated, "STORED GENERATED") == 0 || strcmp(attgenerated, "VIRTUAL GENERATED") == 0)) { char *generated_expr; generated_expr = mysql_remove_quotes(row[7]); if (generated_expr == NULL) elog(ERROR, "unsupported expression found for GENERATED column"); appendStringInfo(&buf, " GENERATED ALWAYS AS %s STORED", generated_expr); pfree(generated_expr); } #endif /* 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; } /* * 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"))); } /* * 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.) */ *param_exprs = ExecInitExprList(fdw_exprs, node); /* 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 */ expr_value = ExecEvalExpr(expr_state, econtext, &isNull); 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. If fetching the var size column then bind * those again to allocate field->max_length memory. */ 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; ListCell *lc; TupleDesc tupleDescriptor = festate->attinmeta->tupdesc; int atindex = 0; MemoryContext oldcontext; /* * 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) { 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; if (!festate->has_var_size_col) return; /* Bind the result columns in long-lived memory context */ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); if (mysql_stmt_store_result(festate->stmt) != 0) mysql_stmt_error_print(festate, "failed to store the result"); /* Bind only var size columns as per field->max_length */ 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; if (pgtype != TEXTOID) { atindex++; 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"); MemoryContextSwitchTo(oldcontext); } 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(PlannerInfo *root, RangeTblEntry *rte) { List *targetAttrs = NIL; Bitmapset *tmpset; AttrNumber col; #if PG_VERSION_NUM >= 160000 RTEPermissionInfo *perminfo; int attidx = -1; perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); tmpset = bms_copy(perminfo->updatedCols); #else tmpset = bms_copy(rte->updatedCols); #endif #if PG_VERSION_NUM >= 160000 while ((attidx = bms_next_member(tmpset, attidx)) >= 0) #else while ((col = bms_first_member(tmpset)) >= 0) #endif { #if PG_VERSION_NUM >= 160000 col = attidx + FirstLowInvalidHeapAttributeNumber; #else col += FirstLowInvalidHeapAttributeNumber; #endif 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. */ 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); /* Add paths with pathkeys */ mysql_add_paths_with_pathkeys(root, joinrel, epq_path, startup_cost, total_cost); /* 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. */ relids = IS_OTHER_REL(joinrel) ? joinrel->top_parent_relids : joinrel->relids; 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; #if PG_VERSION_NUM >= 160000 ListCell *cell; #endif *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; #if PG_VERSION_NUM >= 160000 foreach(cell, wr_var_list) { Var *tlvar = (Var *) lfirst(cell); wr_scan_var_list = mysql_varlist_append_unique_var(wr_scan_var_list, tlvar); } #else wr_scan_var_list = list_concat_unique(wr_scan_var_list, wr_var_list); #endif bms_free(attrs_used); list_free(retrieved_attrs); } else #if PG_VERSION_NUM >= 160000 wr_scan_var_list = mysql_varlist_append_unique_var(wr_scan_var_list, var); #else wr_scan_var_list = list_append_unique(wr_scan_var_list, var); #endif } /* * 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; } /* * 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 >= 160000 tle_sl = mysql_tlist_member_match_var(var, scan_tlist); #else tle_sl = tlist_member((Expr *) 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 >= 160000 TargetEntry *tle_sl = mysql_tlist_member_match_var(var, scan_tlist); #else TargetEntry *tle_sl = tlist_member((Expr *) 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); } /* * 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. */ static bool mysql_foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual) { Query *query = root->parse; PathTarget *grouping_target = grouped_rel->reltarget; 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 *aggref = (Expr *) lfirst(l); if (IsA(aggref, Aggref)) tlist = add_to_flat_tlist(tlist, list_make1(aggref)); } } } 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 (havingQual) { foreach(lc, (List *) havingQual) { 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 >= 160000 rinfo = make_restrictinfo(root, expr, true, false, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); #elif 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; 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. */ static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { 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 PG_VERSION_NUM >= 120000 if ((stage != UPPERREL_GROUP_AGG && stage != UPPERREL_ORDERED && stage != UPPERREL_FINAL) || #else if (stage != UPPERREL_GROUP_AGG || #endif output_rel->fdw_private) return; fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); fpinfo->pushdown_safe = false; fpinfo->stage = stage; output_rel->fdw_private = fpinfo; #if PG_VERSION_NUM >= 120000 switch (stage) { case UPPERREL_GROUP_AGG: mysql_add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData *) extra); break; case UPPERREL_ORDERED: mysql_add_foreign_ordered_paths(root, input_rel, output_rel); break; case UPPERREL_FINAL: mysql_add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData *) extra); break; default: elog(ERROR, "unexpected upper relation: %d", (int) stage); break; } #else mysql_add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData *) extra); #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. */ static void mysql_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra) { 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 (!mysql_foreign_grouping_ok(root, grouped_rel, extra->havingQual)) 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 */ #else 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 */ #endif /* Add generated path into grouped_rel by add_path(). */ add_path(grouped_rel, (Path *) grouppath); } /* * mysql_get_useful_ecs_for_relation * Determine which EquivalenceClasses might be involved in useful * orderings of this relation. * * This function is in some respects a mirror image of the core function * pathkeys_useful_for_merging: for a regular table, we know what indexes * we have and want to test whether any of them are useful. For a foreign * table, we don't know what indexes are present on the remote side but * want to speculate about which ones we'd like to use if they existed. * * This function returns a list of potentially-useful equivalence classes, * but it does not guarantee that an EquivalenceMember exists which contains * Vars only from the given relation. For example, given ft1 JOIN t1 ON * ft1.x + t1.x = 0, this function will say that the equivalence class * containing ft1.x + t1.x is potentially useful. Supposing ft1 is remote and * t1 is local (or on a different server), it will turn out that no useful * ORDER BY clause can be generated. It's not our job to figure that out * here; we're only interested in identifying relevant ECs. */ static List * mysql_get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel) { List *useful_eclass_list = NIL; ListCell *lc; Relids relids; /* * First, consider whether any active EC is potentially useful for a merge * join against this relation. */ if (rel->has_eclass_joins) { foreach(lc, root->eq_classes) { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc); if (eclass_useful_for_merging(root, cur_ec, rel)) useful_eclass_list = lappend(useful_eclass_list, cur_ec); } } /* * Next, consider whether there are any non-EC derivable join clauses that * are merge-joinable. If the joininfo list is empty, we can exit * quickly. */ if (rel->joininfo == NIL) return useful_eclass_list; /* If this is a child rel, we must use the topmost parent rel to search. */ if (IS_OTHER_REL(rel)) { Assert(!bms_is_empty(rel->top_parent_relids)); relids = rel->top_parent_relids; } else relids = rel->relids; /* Check each join clause in turn. */ foreach(lc, rel->joininfo) { RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc); /* Consider only mergejoinable clauses */ if (restrictinfo->mergeopfamilies == NIL) continue; /* Make sure we've got canonical ECs. */ update_mergeclause_eclasses(root, restrictinfo); /* * restrictinfo->mergeopfamilies != NIL is sufficient to guarantee * that left_ec and right_ec will be initialized, per comments in * distribute_qual_to_rels. * * We want to identify which side of this merge-joinable clause * contains columns from the relation produced by this RelOptInfo. We * test for overlap, not containment, because there could be extra * relations on either side. For example, suppose we've got something * like ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y) LEFT JOIN D ON * A.y = D.y. The input rel might be the joinrel between A and B, and * we'll consider the join clause A.y = D.y. relids contains a * relation not involved in the join class (B) and the equivalence * class for the left-hand side of the clause contains a relation not * involved in the input rel (C). Despite the fact that we have only * overlap and not containment in either direction, A.y is potentially * useful as a sort column. * * Note that it's even possible that relids overlaps neither side of * the join clause. For example, consider A LEFT JOIN B ON A.x = B.x * AND A.x = 1. The clause A.x = 1 will appear in B's joininfo list, * but overlaps neither side of B. In that case, we just skip this * join clause, since it doesn't suggest a useful sort order for this * relation. */ if (bms_overlap(relids, restrictinfo->right_ec->ec_relids)) useful_eclass_list = list_append_unique_ptr(useful_eclass_list, restrictinfo->right_ec); else if (bms_overlap(relids, restrictinfo->left_ec->ec_relids)) useful_eclass_list = list_append_unique_ptr(useful_eclass_list, restrictinfo->left_ec); } return useful_eclass_list; } /* * mysql_get_useful_pathkeys_for_relation * Determine which orderings of a relation might be useful. * * Getting data in sorted order can be useful either because the requested * order matches the final output ordering for the overall query we're * planning, or because it enables an efficient merge join. Here, we try * to figure out which pathkeys to consider. */ static List * mysql_get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel) { List *useful_pathkeys_list = NIL; List *useful_eclass_list; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) rel->fdw_private; EquivalenceClass *query_ec = NULL; ListCell *lc; /* * Pushing the query_pathkeys to the remote server is always worth * considering, because it might let us avoid a local sort. */ fpinfo->qp_is_pushdown_safe = false; if (root->query_pathkeys) { bool query_pathkeys_ok = true; foreach(lc, root->query_pathkeys) { PathKey *pathkey = (PathKey *) lfirst(lc); /* * The planner and executor don't have any clever strategy for * taking data sorted by a prefix of the query's pathkeys and * getting it to be sorted by all of those pathkeys. We'll just * end up resorting the entire data set. So, unless we can push * down all of the query pathkeys, forget it. */ if (!mysql_is_foreign_pathkey(root, rel, pathkey)) { query_pathkeys_ok = false; break; } } if (query_pathkeys_ok) { useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys)); fpinfo->qp_is_pushdown_safe = true; } } /* Get the list of interesting EquivalenceClasses. */ useful_eclass_list = mysql_get_useful_ecs_for_relation(root, rel); /* Extract unique EC for query, if any, so we don't consider it again. */ if (list_length(root->query_pathkeys) == 1) { PathKey *query_pathkey = linitial(root->query_pathkeys); query_ec = query_pathkey->pk_eclass; } /* * As a heuristic, the only pathkeys we consider here are those of length * one. It's surely possible to consider more, but since each one we * choose to consider will generate a round-trip to the remote side, we * need to be a bit cautious here. It would sure be nice to have a local * cache of information about remote index definitions... */ foreach(lc, useful_eclass_list) { EquivalenceMember *em = NULL; EquivalenceClass *cur_ec = lfirst(lc); PathKey *pathkey; /* If redundant with what we did above, skip it. */ if (cur_ec == query_ec) continue; em = mysql_find_em_for_rel(root, cur_ec, rel); /* Can't push down the sort if the EC's opfamily is not shippable. */ if (!mysql_is_builtin(linitial_oid(cur_ec->ec_opfamilies))) continue; /* Looks like we can generate a pathkey, so let's do it. */ pathkey = make_canonical_pathkey(root, cur_ec, linitial_oid(cur_ec->ec_opfamilies), BTLessStrategyNumber, false); /* Check for sort operator pushability. */ if (mysql_get_sortby_direction_string(em, pathkey) == NULL) continue; useful_pathkeys_list = lappend(useful_pathkeys_list, list_make1(pathkey)); } return useful_pathkeys_list; } /* * mysql_add_paths_with_pathkeys * Add path with root->query_pathkeys if that's pushable. * * Pushing down query_pathkeys to the foreign server might let us avoid a * local sort. */ static void mysql_add_paths_with_pathkeys(PlannerInfo *root, RelOptInfo *rel, Path *epq_path, Cost base_startup_cost, Cost base_total_cost) { ListCell *lc; List *useful_pathkeys_list = NIL; /* List of all pathkeys */ useful_pathkeys_list = mysql_get_useful_pathkeys_for_relation(root, rel); /* Create one path for each set of pathkeys we found above. */ foreach(lc, useful_pathkeys_list) { Cost startup_cost; Cost total_cost; List *useful_pathkeys = lfirst(lc); Path *sorted_epq_path; /* TODO put accurate estimates. */ startup_cost = base_startup_cost * DEFAULT_MYSQL_SORT_MULTIPLIER; total_cost = base_total_cost * DEFAULT_MYSQL_SORT_MULTIPLIER; /* * The EPQ path must be at least as well sorted as the path itself, in * case it gets used as input to a mergejoin. */ sorted_epq_path = epq_path; if (sorted_epq_path != NULL && !pathkeys_contained_in(useful_pathkeys, sorted_epq_path->pathkeys)) sorted_epq_path = (Path *) create_sort_path(root, rel, sorted_epq_path, useful_pathkeys, -1.0); #if PG_VERSION_NUM >= 120000 if (IS_SIMPLE_REL(rel)) add_path(rel, (Path *) create_foreignscan_path(root, rel, NULL, rel->rows, startup_cost, total_cost, useful_pathkeys, rel->lateral_relids, sorted_epq_path, NIL)); else add_path(rel, (Path *) create_foreign_join_path(root, rel, NULL, rel->rows, startup_cost, total_cost, useful_pathkeys, rel->lateral_relids, sorted_epq_path, NIL)); #else add_path(rel, (Path *) create_foreignscan_path(root, rel, NULL, rel->rows, startup_cost, total_cost, useful_pathkeys, rel->lateral_relids, sorted_epq_path, NIL)); #endif } } /* * Given an EquivalenceClass and a foreign relation, find an EC member * that can be used to sort the relation remotely according to a pathkey * using this EC. * * If there is more than one suitable candidate, return an arbitrary * one of them. If there is none, return NULL. * * This checks that the EC member expression uses only Vars from the given * rel and is shippable. Caller must separately verify that the pathkey's * ordering operator is shippable. */ EquivalenceMember * mysql_find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) { ListCell *lc; foreach(lc, ec->ec_members) { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); /* * Note we require !bms_is_empty, else we'd accept constant * expressions which are not suitable for the purpose. */ if (bms_is_subset(em->em_relids, rel->relids) && !bms_is_empty(em->em_relids) && mysql_is_foreign_expr(root, rel, em->em_expr, true)) return em; } return NULL; } /* * mysql_add_foreign_ordered_paths * Add foreign paths for performing the final sort remotely. * * Given input_rel contains the source-data Paths. The paths are added to the * given ordered_rel. */ #if PG_VERSION_NUM >= 120000 static void mysql_add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel) { Query *parse = root->parse; MySQLFdwRelationInfo *ifpinfo = input_rel->fdw_private; MySQLFdwRelationInfo *fpinfo = ordered_rel->fdw_private; double rows; Cost startup_cost; Cost total_cost; List *fdw_private; ForeignPath *ordered_path; ListCell *lc; /* Shouldn't get here unless the query has ORDER BY */ Assert(parse->sortClause); /* We don't support cases where there are any SRFs in the targetlist */ if (parse->hasTargetSRFs) return; /* Save the input_rel as outerrel in fpinfo */ fpinfo->outerrel = input_rel; /* * If the input_rel is a base or join relation, we would already have * considered pushing down the final sort to the remote server when * creating pre-sorted foreign paths for that relation, because the * query_pathkeys is set to the root->sort_pathkeys in that case (see * standard_qp_callback()). */ if (input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL) { Assert(root->query_pathkeys == root->sort_pathkeys); /* Safe to push down if the query_pathkeys is safe to push down */ fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; return; } /* The input_rel should be a grouping relation */ Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); /* * We try to create a path below by extending a simple foreign path for * the underlying grouping relation to perform the final sort remotely, * which is stored into the fdw_private list of the resulting path. */ /* Assess if it is safe to push down the final sort */ foreach(lc, root->sort_pathkeys) { PathKey *pathkey = (PathKey *) lfirst(lc); EquivalenceClass *pathkey_ec = pathkey->pk_eclass; EquivalenceMember *em; /* * mysql_is_foreign_expr would detect volatile expressions as well, * but checking ec_has_volatile here saves some cycles. */ if (pathkey_ec->ec_has_volatile) return; /* * The EC must contain a shippable EM that is computed in input_rel's * reltarget, else we can't push down the sort. */ em = mysql_find_em_for_rel_target(root, pathkey_ec, input_rel); if (mysql_get_sortby_direction_string(em, pathkey) == NULL) return; } /* Safe to push down */ fpinfo->pushdown_safe = true; /* TODO: Put accurate estimates */ startup_cost = 15; total_cost = 10 + startup_cost; rows = 10; /* * Build the fdw_private list that will be used by mysqlGetForeignPlan. * Items in the list must match order in enum FdwPathPrivateIndex. */ fdw_private = list_make2(makeInteger(true), makeInteger(false)); /* Create foreign ordering path */ ordered_path = create_foreign_upper_path(root, input_rel, root->upper_targets[UPPERREL_ORDERED], rows, startup_cost, total_cost, root->sort_pathkeys, NULL, /* no extra plan */ fdw_private); /* and add it to the ordered_rel */ add_path(ordered_rel, (Path *) ordered_path); } #endif /* PG_VERSION_NUM >= 120000 */ /* * mysql_find_em_for_rel_target * * Find an EquivalenceClass member that is to be computed as a sort column * in the given rel's reltarget, and is shippable. * * If there is more than one suitable candidate, return an arbitrary * one of them. If there is none, return NULL. * * This checks that the EC member expression uses only Vars from the given * rel and is shippable. Caller must separately verify that the pathkey's * ordering operator is shippable. */ EquivalenceMember * mysql_find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) { PathTarget *target = rel->reltarget; ListCell *lc1; int i; i = 0; foreach(lc1, target->exprs) { Expr *expr = (Expr *) lfirst(lc1); Index sgref = get_pathtarget_sortgroupref(target, i); ListCell *lc2; /* Ignore non-sort expressions */ if (sgref == 0 || get_sortgroupref_clause_noerr(sgref, root->parse->sortClause) == NULL) { i++; continue; } /* We ignore binary-compatible relabeling on both ends */ while (expr && IsA(expr, RelabelType)) expr = ((RelabelType *) expr)->arg; /* Locate an EquivalenceClass member matching this expr, if any */ foreach(lc2, ec->ec_members) { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); Expr *em_expr; /* Don't match constants */ if (em->em_is_const) continue; /* Ignore child members */ if (em->em_is_child) continue; /* Match if same expression (after stripping relabel) */ em_expr = em->em_expr; while (em_expr && IsA(em_expr, RelabelType)) em_expr = ((RelabelType *) em_expr)->arg; if (!equal(em_expr, expr)) continue; /* Check that expression (including relabels!) is shippable */ if (mysql_is_foreign_expr(root, rel, em->em_expr, true)) return em; } i++; } return NULL; } #if PG_VERSION_NUM >= 120000 /* * mysql_add_foreign_final_paths * Add foreign paths for performing the final processing remotely. * * Given input_rel contains the source-data Paths. The paths are added to the * given final_rel. */ static void mysql_add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra) { Query *parse = root->parse; MySQLFdwRelationInfo *ifpinfo = (MySQLFdwRelationInfo *) input_rel->fdw_private; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) final_rel->fdw_private; bool has_final_sort = false; List *pathkeys = NIL; double rows; Cost startup_cost; Cost total_cost; List *fdw_private; ForeignPath *final_path; /* * Currently, we only support this for SELECT commands */ if (parse->commandType != CMD_SELECT) return; /* * No work if there is no FOR UPDATE/SHARE clause and if there is no need * to add a LIMIT node */ if (!parse->rowMarks && !extra->limit_needed) return; /* We don't support cases where there are any SRFs in the targetlist */ if (parse->hasTargetSRFs) return; /* MySQL does not support only OFFSET clause in a SELECT command. */ if (parse->limitOffset && !parse->limitCount) return; /* Save the input_rel as outerrel in fpinfo */ fpinfo->outerrel = input_rel; /* * If there is no need to add a LIMIT node, there might be a ForeignPath * in the input_rel's pathlist that implements all behavior of the query. * Note: we would already have accounted for the query's FOR UPDATE/SHARE * (if any) before we get here. */ if (!extra->limit_needed) { ListCell *lc; Assert(parse->rowMarks); /* * Grouping and aggregation are not supported with FOR UPDATE/SHARE, * so the input_rel should be a base, join, or ordered relation; and * if it's an ordered relation, its input relation should be a base or * join relation. */ Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED && (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL || ifpinfo->outerrel->reloptkind == RELOPT_JOINREL))); foreach(lc, input_rel->pathlist) { Path *path = (Path *) lfirst(lc); /* * apply_scanjoin_target_to_paths() uses create_projection_path() * to adjust each of its input paths if needed, whereas * create_ordered_paths() uses apply_projection_to_path() to do * that. So the former might have put a ProjectionPath on top of * the ForeignPath; look through ProjectionPath and see if the * path underneath it is ForeignPath. */ if (IsA(path, ForeignPath) || (IsA(path, ProjectionPath) && IsA(((ProjectionPath *) path)->subpath, ForeignPath))) { /* * Create foreign final path; this gets rid of a * no-longer-needed outer plan (if any), which makes the * EXPLAIN output look cleaner */ final_path = create_foreign_upper_path(root, path->parent, path->pathtarget, path->rows, path->startup_cost, path->total_cost, path->pathkeys, NULL, /* no extra plan */ NULL); /* no fdw_private */ /* and add it to the final_rel */ add_path(final_rel, (Path *) final_path); /* Safe to push down */ fpinfo->pushdown_safe = true; return; } } /* * If we get here it means no ForeignPaths; since we would already * have considered pushing down all operations for the query to the * remote server, give up on it. */ return; } Assert(extra->limit_needed); /* * If the input_rel is an ordered relation, replace the input_rel with its * input relation */ if (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED) { input_rel = ifpinfo->outerrel; ifpinfo = (MySQLFdwRelationInfo *) input_rel->fdw_private; has_final_sort = true; pathkeys = root->sort_pathkeys; } /* The input_rel should be a base, join, or grouping relation */ Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG)); /* * We try to create a path below by extending a simple foreign path for * the underlying base, join, or grouping relation to perform the final * sort (if has_final_sort) and the LIMIT restriction remotely, which is * stored into the fdw_private list of the resulting path. (We * re-estimate the costs of sorting the underlying relation, if * has_final_sort.) */ /* * Assess if it is safe to push down the LIMIT and OFFSET to the remote * server */ /* * If the underlying relation has any local conditions, the LIMIT/OFFSET * cannot be pushed down. */ if (ifpinfo->local_conds) return; /* * Support only Const and Param nodes as expressions are NOT suported. * MySQL doesn't support LIMIT/OFFSET NULL/ALL syntax, so check for the * same. If limitCount const node is null then do not pushdown * limit/offset clause and if limitOffset const node is null and * limitCount const node is not null then pushdown only limit clause. */ if (parse->limitCount) { if (nodeTag(parse->limitCount) != T_Const && nodeTag(parse->limitCount) != T_Param) return; if (nodeTag(parse->limitCount) == T_Const && ((Const *) parse->limitCount)->constisnull) return; } if (parse->limitOffset) { if (nodeTag(parse->limitOffset) != T_Const && nodeTag(parse->limitOffset) != T_Param) return; } /* Safe to push down */ fpinfo->pushdown_safe = true; /* TODO: Put accurate estimates */ startup_cost = 1; total_cost = 1 + startup_cost; rows = 1; /* * Build the fdw_private list that will be used by mysqlGetForeignPlan. * Items in the list must match order in enum FdwPathPrivateIndex. */ fdw_private = list_make2(makeInteger(has_final_sort), makeInteger(extra->limit_needed)); /* * Create foreign final path; this gets rid of a no-longer-needed outer * plan (if any), which makes the EXPLAIN output look cleaner */ final_path = create_foreign_upper_path(root, input_rel, root->upper_targets[UPPERREL_FINAL], rows, startup_cost, total_cost, pathkeys, NULL, /* no extra plan */ fdw_private); /* and add it to the final_rel */ add_path(final_rel, (Path *) final_path); } #endif /* PG_VERSION_NUM >= 120000 */ #if PG_VERSION_NUM >= 140000 /* * mysqlExecForeignTruncate * Truncate one or more foreign tables. */ static void mysqlExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs) { Oid serverid = InvalidOid; ForeignServer *server = NULL; UserMapping *user = NULL; MYSQL *conn = NULL; StringInfoData sql; ListCell *lc; bool server_truncatable = true; mysql_opt *options; /* CASCADE option is not supported as we don't have such option in MySQL */ if (behavior == DROP_CASCADE) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("CASCADE option in TRUNCATE is not supported by this FDW"))); /* * By default, all mysql_fdw foreign tables are assumed truncatable. This * can be overridden by a per-server setting, which in turn can be * overridden by a per-table setting. */ foreach(lc, rels) { Relation rel = lfirst(lc); ForeignTable *table = GetForeignTable(RelationGetRelid(rel)); ListCell *cell; bool truncatable; /* * First time through, determine whether the foreign server allows * truncates. Since all specified foreign tables are assumed to belong * to the same foreign server, this result can be used for other * foreign tables. */ if (!OidIsValid(serverid)) { serverid = table->serverid; server = GetForeignServer(serverid); foreach(cell, server->options) { DefElem *defel = (DefElem *) lfirst(cell); if (strcmp(defel->defname, "truncatable") == 0) { server_truncatable = defGetBoolean(defel); break; } } } /* * Confirm that all specified foreign tables belong to the same * foreign server. */ Assert(table->serverid == serverid); /* Determine whether this foreign table allows truncations */ truncatable = server_truncatable; foreach(cell, table->options) { DefElem *defel = (DefElem *) lfirst(cell); if (strcmp(defel->defname, "truncatable") == 0) { truncatable = defGetBoolean(defel); break; } } if (!truncatable) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("foreign table \"%s\" does not allow truncates", RelationGetRelationName(rel)))); } Assert(OidIsValid(serverid)); /* * Get connection to the foreign server. Connection manager will * establish new connection if necessary. */ user = GetUserMapping(GetUserId(), serverid); options = mysql_get_options(serverid, false); conn = mysql_get_connection(server, user, options); /* Construct the TRUNCATE command string */ foreach(lc, rels) { Relation rel = lfirst(lc); initStringInfo(&sql); mysql_deparse_truncate_sql(&sql, rel); /* Issue the TRUNCATE command to remote server */ if (mysql_query(conn, sql.data) != 0) mysql_error_print(conn); pfree(sql.data); } } #endif #if PG_VERSION_NUM >= 140000 /* * mysql_remove_quotes * * Return the string by replacing back-tick (`) characters with double quotes * ("). If there are two consecutive back-ticks, the first is the escape * character which is removed. Caller should free the allocated memory. */ static char * mysql_remove_quotes(char *s1) { int i, j; char *s2; if (s1 == NULL) return NULL; s2 = palloc0(strlen(s1) * 2); for (i = 0, j = 0; s1[i] != '\0'; i++, j++) { if (s1[i] == '`' && s1[i + 1] == '`') { s2[j] = '`'; i++; } else if (s1[i] == '`') s2[j] = '"'; else if (s1[i] == '"') { /* Double the inner double quotes for PG compatibility. */ s2[j] = '"'; s2[j + 1] = '"'; j++; } else s2[j] = s1[i]; } s2[j] = '\0'; return s2; } #endif /* * mysql_display_pushdown_list * Displays all records from the config file. Each record will return as a * single row. * * If it gets the argument as true, then it will rebuild the hash table and * then display it. Also, if this function is executing for the first time in * a session before any other mysql_fdw statement, then we by default build the * hash and display it. */ Datum mysql_display_pushdown_list(PG_FUNCTION_ARGS) { #define DISPLAY_PUSHDOWN_LIST_COLS 2 FuncCallContext *funcctx; List *objectList; if (SRF_IS_FIRSTCALL()) { bool reload = PG_GETARG_BOOL(0); TupleDesc tupdesc; MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); /* Switch context when allocating stuff to be used in later calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Fetch the object list */ objectList = mysql_get_configured_pushdown_objects(reload); /* Total number of tuples to be returned */ funcctx->max_calls = list_length(objectList); funcctx->user_fctx = (void *) objectList; /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); if (tupdesc->natts != DISPLAY_PUSHDOWN_LIST_COLS) elog(ERROR, "incorrect number of output arguments"); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* Return to original context when allocating transient memory */ MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); /* Get the saved state */ objectList = (List *) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; Datum values[DISPLAY_PUSHDOWN_LIST_COLS]; bool nulls[DISPLAY_PUSHDOWN_LIST_COLS] = {false}; FDWPushdownObject *object; object = lfirst(list_nth_cell(objectList, funcctx->call_cntr)); if (object->objectType == OBJECT_FUNCTION) { char *name = format_procedure_qualified(object->objectId); values[0] = PointerGetDatum(cstring_to_text("ROUTINE")); values[1] = PointerGetDatum(cstring_to_text(name)); } else if (object->objectType == OBJECT_OPERATOR) { char *name = format_operator_qualified(object->objectId); values[0] = PointerGetDatum(cstring_to_text("OPERATOR")); values[1] = PointerGetDatum(cstring_to_text(name)); } else elog(ERROR, "invalid object type in pushdown config file"); /* Build and return the next result tuple. */ tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); } /* Done */ SRF_RETURN_DONE(funcctx); } /* * mysql_get_sortby_direction_string * Fetch the operator oid from the operator family and datatype, and check * whether the operator is the default for sort expr's datatype. If it is, * then return ASC or DESC accordingly; NULL otherwise. */ char * mysql_get_sortby_direction_string(EquivalenceMember *em, PathKey *pathkey) { Oid oprid; TypeCacheEntry *typentry; if (em == NULL) return NULL; /* Can't push down the sort if pathkey's opfamily is not shippable. */ if (!mysql_is_builtin(pathkey->pk_opfamily)) return NULL; oprid = get_opfamily_member(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, pathkey->pk_strategy); if (!OidIsValid(oprid)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_strategy, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); /* Can't push down the sort if the operator is not shippable. */ if (!mysql_check_remote_pushability(oprid)) return NULL; /* * See whether the operator is default < or > for sort expr's datatype. * Here we need to use the expression's actual type to discover whether * the desired operator will be the default or not. */ typentry = lookup_type_cache(exprType((Node *) em->em_expr), TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); if (oprid == typentry->lt_opr) return "ASC"; else if (oprid == typentry->gt_opr) return "DESC"; return NULL; } #if PG_VERSION_NUM >= 160000 /* * mysql_tlist_member_match_var * Finds the (first) member of the given tlist whose Var is same as the * given Var. Result is NULL if no such member. */ static TargetEntry * mysql_tlist_member_match_var(Var *var, List *targetlist) { ListCell *temp; foreach(temp, targetlist) { TargetEntry *tlentry = (TargetEntry *) lfirst(temp); Var *tlvar = (Var *) tlentry->expr; if (!tlvar || !IsA(tlvar, Var)) continue; if (var->varno == tlvar->varno && var->varattno == tlvar->varattno && var->varlevelsup == tlvar->varlevelsup && var->vartype == tlvar->vartype) return tlentry; } return NULL; } /* * mysql_varlist_append_unique_var * Append var to var list, but only if it isn't already in the list. * * Whether a var is already a member of list is determined using varno and * varattno. */ static List * mysql_varlist_append_unique_var(List *varlist, Var *var) { ListCell *lc; foreach(lc, varlist) { Var *tlvar = (Var *) lfirst(lc); if (IsA(tlvar, Var) && tlvar->varno == var->varno && tlvar->varattno == var->varattno) return varlist; } return lappend(varlist, var); } #endif mysql_fdw-REL-2_9_1/mysql_fdw.control000066400000000000000000000010421445421625600177270ustar00rootroot00000000000000########################################################################## # # mysql_fdw.control # Foreign-data wrapper for remote MySQL servers # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # Portions Copyright (c) 2004-2023, EnterpriseDB Corporation. # # IDENTIFICATION # mysql_fdw.control # ########################################################################## comment = 'Foreign data wrapper for querying a MySQL server' default_version = '1.2' module_pathname = '$libdir/mysql_fdw' relocatable = true mysql_fdw-REL-2_9_1/mysql_fdw.h000066400000000000000000000307371445421625600165130ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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 */ char *character_set; /* Character set used for remote connection */ char *sql_mode; /* MySQL sql_mode variable for connection */ /* 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 */ bool has_var_size_col; /* true if fetching var size columns */ /* * 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; /* True means that the query_pathkeys is safe to push down */ bool qp_is_pushdown_safe; /* * 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; /* Upper relation information */ UpperRelationKind stage; } 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_STMT *stmt)); 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, bool doNothing); 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 *pathkeys, bool has_final_sort, bool has_limit, 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); extern bool mysql_is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); extern char *mysql_get_sortby_direction_string(EquivalenceMember *em, PathKey *pathkey); extern EquivalenceMember *mysql_find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); extern EquivalenceMember *mysql_find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); extern bool mysql_is_builtin(Oid objectId); #if PG_VERSION_NUM >= 140000 extern void mysql_deparse_truncate_sql(StringInfo buf, Relation rel); #endif extern char *mysql_quote_identifier(const char *str, char quotechar); /* connection.c headers */ MYSQL *mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt); MYSQL *mysql_fdw_connect(mysql_opt *opt); void mysql_cleanup_connection(void); void mysql_release_connection(MYSQL *conn); #endif /* MYSQL_FDW_H */ mysql_fdw-REL-2_9_1/mysql_fdw_pushdown.config000066400000000000000000000263411445421625600214540ustar00rootroot00000000000000#----------------------------------------------------------------------------- # This file lists object as aggregates, functions, and operators are allowed # to push down to the remote server. Each entry should be on its line and # consists of two columns: the first is object type as ROUTINE (for functions, # aggregates, and procedures) or OPERATOR, and the second column is optionally # schema-qualified object names with their arguments. The exact form of the # second column can be formatted using the following query: # # For routines: # # SELECT pronamespace::regnamespace || '.' || oid::regprocedure FROM pg_proc # WHERE proname = '' # # And for operator: # # SELECT oprnamespace::regnamespace || '.' || oid::regoperator FROM pg_operator # WHERE oprname = '' # # Note: To identify objects uniquely, the name and/or its argument must be # schema-qualified. #----------------------------------------------------------------------------- ROUTINE pg_catalog.sum(bigint) ROUTINE pg_catalog.sum(smallint) ROUTINE pg_catalog.sum(integer) ROUTINE pg_catalog.sum(real) ROUTINE pg_catalog.sum(double precision) ROUTINE pg_catalog.sum(numeric) ROUTINE pg_catalog.avg(bigint) ROUTINE pg_catalog.avg(smallint) ROUTINE pg_catalog.avg(integer) ROUTINE pg_catalog.avg(real) ROUTINE pg_catalog.avg(double precision) ROUTINE pg_catalog.avg(numeric) ROUTINE pg_catalog.min(bigint) ROUTINE pg_catalog.min(smallint) ROUTINE pg_catalog.min(integer) ROUTINE pg_catalog.min(real) ROUTINE pg_catalog.min(double precision) ROUTINE pg_catalog.min(numeric) ROUTINE pg_catalog.min(oid) ROUTINE pg_catalog.min(character) ROUTINE pg_catalog.min(time without time zone) ROUTINE pg_catalog.min(time with time zone) ROUTINE pg_catalog.min(timestamp without time zone) ROUTINE pg_catalog.min(timestamp with time zone) ROUTINE pg_catalog.min(text) ROUTINE pg_catalog.min(anyenum) ROUTINE pg_catalog.min(pg_catalog.date) ROUTINE pg_catalog.max(bigint) ROUTINE pg_catalog.max(smallint) ROUTINE pg_catalog.max(integer) ROUTINE pg_catalog.max(real) ROUTINE pg_catalog.max(double precision) ROUTINE pg_catalog.max(numeric) ROUTINE pg_catalog.max(oid) ROUTINE pg_catalog.max(character) ROUTINE pg_catalog.max(time without time zone) ROUTINE pg_catalog.max(time with time zone) ROUTINE pg_catalog.max(timestamp without time zone) ROUTINE pg_catalog.max(timestamp with time zone) ROUTINE pg_catalog.max(text) ROUTINE pg_catalog.max(anyenum) ROUTINE pg_catalog.max(pg_catalog.date) ROUTINE pg_catalog.count() ROUTINE pg_catalog.count("any") ROUTINE pg_catalog.rank() ROUTINE pg_catalog.rank("any") ROUTINE pg_catalog.percentile_cont(double precision,double precision) ROUTINE pg_catalog.percentile_cont(double precision[],double precision) ROUTINE pg_catalog.stddev(bigint) ROUTINE pg_catalog.stddev(integer) ROUTINE pg_catalog.stddev(smallint) ROUTINE pg_catalog.stddev(real) ROUTINE pg_catalog.stddev(double precision) ROUTINE pg_catalog.stddev(numeric) ROUTINE pg_catalog.length(text) ROUTINE pg_catalog.length(character) ROUTINE pg_catalog.length(bit) ROUTINE pg_catalog.length(bytea) ROUTINE pg_catalog.length(bytea,name) ROUTINE pg_catalog.lower(text) ROUTINE pg_catalog.upper(text) ROUTINE pg_catalog.floor(numeric) ROUTINE pg_catalog.floor(double precision) ROUTINE pg_catalog.sqrt(numeric) ROUTINE pg_catalog.sqrt(double precision) ROUTINE pg_catalog.exp(numeric) ROUTINE pg_catalog.exp(double precision) ROUTINE pg_catalog.mod(integer,integer) ROUTINE pg_catalog.mod(numeric,numeric) ROUTINE pg_catalog.mod(smallint,smallint) ROUTINE pg_catalog.mod(bigint,bigint) ROUTINE pg_catalog.float8(numeric) ROUTINE pg_catalog.float8(smallint) ROUTINE pg_catalog.float8(integer) ROUTINE pg_catalog.float8(bigint) ROUTINE pg_catalog.float8(real) ROUTINE pg_catalog.int4(numeric) ROUTINE pg_catalog.int4(smallint) ROUTINE pg_catalog.int4(double precision) ROUTINE pg_catalog.int4(bigint) ROUTINE pg_catalog.int4(bit) ROUTINE pg_catalog.int4(boolean) ROUTINE pg_catalog.int4(real) ROUTINE pg_catalog."numeric"(bigint) ROUTINE pg_catalog."numeric"(integer) ROUTINE pg_catalog."numeric"(double precision) ROUTINE pg_catalog."numeric"(smallint) ROUTINE pg_catalog."numeric"(numeric,integer) ROUTINE pg_catalog."numeric"(real) OPERATOR pg_catalog.=(bigint,bigint) OPERATOR pg_catalog.=(integer,integer) OPERATOR pg_catalog.=(text,text) OPERATOR pg_catalog.=(smallint,integer) OPERATOR pg_catalog.=(bigint,integer) OPERATOR pg_catalog.=(numeric,numeric) OPERATOR pg_catalog.=(smallint,smallint) OPERATOR pg_catalog.=(double precision,double precision) OPERATOR pg_catalog.=(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.=(oid,oid) OPERATOR pg_catalog.=(character,character) OPERATOR pg_catalog.=(time with time zone,time with time zone) OPERATOR pg_catalog.=(time without time zone,time without time zone) OPERATOR pg_catalog.=(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.=(boolean,boolean) OPERATOR pg_catalog.=(bytea,bytea) OPERATOR pg_catalog.=(name,name) OPERATOR pg_catalog.=(real,real) OPERATOR pg_catalog.=(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.=(bit,bit) OPERATOR pg_catalog.=(anyenum,anyenum) OPERATOR pg_catalog.>(bigint,bigint) OPERATOR pg_catalog.>(integer,integer) OPERATOR pg_catalog.>(text,text) OPERATOR pg_catalog.>(smallint,integer) OPERATOR pg_catalog.>(bigint,integer) OPERATOR pg_catalog.>(numeric,numeric) OPERATOR pg_catalog.>(boolean,boolean) OPERATOR pg_catalog.>(smallint,smallint) OPERATOR pg_catalog.>(double precision,double precision) OPERATOR pg_catalog.>(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.>(oid,oid) OPERATOR pg_catalog.>(character,character) OPERATOR pg_catalog.>(time with time zone,time with time zone) OPERATOR pg_catalog.>(time without time zone,time without time zone) OPERATOR pg_catalog.>(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.>(bytea,bytea) OPERATOR pg_catalog.>(name,name) OPERATOR pg_catalog.>(real,real) OPERATOR pg_catalog.>(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.>(bit,bit) OPERATOR pg_catalog.>(anyenum,anyenum) OPERATOR pg_catalog.<(bigint,bigint) OPERATOR pg_catalog.<(integer,integer) OPERATOR pg_catalog.<(text,text) OPERATOR pg_catalog.<(smallint,integer) OPERATOR pg_catalog.<(smallint,smallint) OPERATOR pg_catalog.<(bigint,integer) OPERATOR pg_catalog.<(double precision,double precision) OPERATOR pg_catalog.<(numeric,numeric) OPERATOR pg_catalog.<(character,character) OPERATOR pg_catalog.<(boolean,boolean) OPERATOR pg_catalog.<(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.<(oid,oid) OPERATOR pg_catalog.<(time with time zone,time with time zone) OPERATOR pg_catalog.<(time without time zone,time without time zone) OPERATOR pg_catalog.<(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.<(bytea,bytea) OPERATOR pg_catalog.<(name,name) OPERATOR pg_catalog.<(real,real) OPERATOR pg_catalog.<(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.<(bit,bit) OPERATOR pg_catalog.<(anyenum,anyenum) OPERATOR pg_catalog.>=(bigint,bigint) OPERATOR pg_catalog.>=(integer,integer) OPERATOR pg_catalog.>=(text,text) OPERATOR pg_catalog.>=(smallint,integer) OPERATOR pg_catalog.>=(bigint,integer) OPERATOR pg_catalog.>=(numeric,numeric) OPERATOR pg_catalog.>=(smallint,smallint) OPERATOR pg_catalog.>=(double precision,double precision) OPERATOR pg_catalog.>=(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.>=(oid,oid) OPERATOR pg_catalog.>=(character,character) OPERATOR pg_catalog.>=(time with time zone,time with time zone) OPERATOR pg_catalog.>=(time without time zone,time without time zone) OPERATOR pg_catalog.>=(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.>=(boolean,boolean) OPERATOR pg_catalog.>=(bytea,bytea) OPERATOR pg_catalog.>=(name,name) OPERATOR pg_catalog.>=(real,real) OPERATOR pg_catalog.>=(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.>=(bit,bit) OPERATOR pg_catalog.>=(anyenum,anyenum) OPERATOR pg_catalog.<=(bigint,bigint) OPERATOR pg_catalog.<=(integer,integer) OPERATOR pg_catalog.<=(text,text) OPERATOR pg_catalog.<=(smallint,integer) OPERATOR pg_catalog.<=(bigint,integer) OPERATOR pg_catalog.<=(numeric,numeric) OPERATOR pg_catalog.<=(smallint,smallint) OPERATOR pg_catalog.<=(double precision,double precision) OPERATOR pg_catalog.<=(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.<=(oid,oid) OPERATOR pg_catalog.<=(character,character) OPERATOR pg_catalog.<=(time with time zone,time with time zone) OPERATOR pg_catalog.<=(time without time zone,time without time zone) OPERATOR pg_catalog.<=(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.<=(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.<=(boolean,boolean) OPERATOR pg_catalog.<=(bytea,bytea) OPERATOR pg_catalog.<=(name,name) OPERATOR pg_catalog.<=(real,real) OPERATOR pg_catalog.<=(bit,bit) OPERATOR pg_catalog.<=(anyenum,anyenum) OPERATOR pg_catalog.<>(bigint,bigint) OPERATOR pg_catalog.<>(integer,integer) OPERATOR pg_catalog.<>(text,text) OPERATOR pg_catalog.<>(smallint,integer) OPERATOR pg_catalog.<>(bigint,integer) OPERATOR pg_catalog.<>(numeric,numeric) OPERATOR pg_catalog.<>(smallint,smallint) OPERATOR pg_catalog.<>(double precision,double precision) OPERATOR pg_catalog.<>(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.<>(oid,oid) OPERATOR pg_catalog.<>(character,character) OPERATOR pg_catalog.<>(time with time zone,time with time zone) OPERATOR pg_catalog.<>(time without time zone,time without time zone) OPERATOR pg_catalog.<>(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.<>(boolean,boolean) OPERATOR pg_catalog.<>(bytea,bytea) OPERATOR pg_catalog.<>(name,name) OPERATOR pg_catalog.<>(real,real) OPERATOR pg_catalog.<>(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.<>(bit,bit) OPERATOR pg_catalog.<>(anyenum,anyenum) OPERATOR pg_catalog.%(bigint,bigint) OPERATOR pg_catalog.%(smallint,smallint) OPERATOR pg_catalog.%(integer,integer) OPERATOR pg_catalog.%(numeric,numeric) OPERATOR pg_catalog.*(bigint,bigint) OPERATOR pg_catalog.*(smallint,smallint) OPERATOR pg_catalog.*(integer,integer) OPERATOR pg_catalog.*(numeric,numeric) OPERATOR pg_catalog.*(double precision,double precision) OPERATOR pg_catalog.*(bigint,integer) OPERATOR pg_catalog.*(real,real) OPERATOR pg_catalog.+(bigint,bigint) OPERATOR pg_catalog.+(smallint,smallint) OPERATOR pg_catalog.+(integer,integer) OPERATOR pg_catalog.+(numeric,numeric) OPERATOR pg_catalog.+(double precision,double precision) OPERATOR pg_catalog.+(real,real) OPERATOR pg_catalog.-(bigint,bigint) OPERATOR pg_catalog.-(smallint,smallint) OPERATOR pg_catalog.-(integer,integer) OPERATOR pg_catalog.-(numeric,numeric) OPERATOR pg_catalog.-(double precision,double precision) OPERATOR pg_catalog.-(timestamp with time zone,timestamp with time zone) OPERATOR pg_catalog.-(time without time zone,time without time zone) OPERATOR pg_catalog.-(timestamp without time zone,timestamp without time zone) OPERATOR pg_catalog.-(real,real) OPERATOR pg_catalog.-(pg_catalog.date,pg_catalog.date) OPERATOR pg_catalog.~~(text,text) OPERATOR pg_catalog.~~(character,text) OPERATOR pg_catalog./(bigint,bigint) OPERATOR pg_catalog./(smallint,smallint) OPERATOR pg_catalog./(integer,integer) OPERATOR pg_catalog./(numeric,numeric) OPERATOR pg_catalog./(double precision,double precision) OPERATOR pg_catalog./(real,real) mysql_fdw-REL-2_9_1/mysql_init.sh000077500000000000000000000176511445421625600170640ustar00rootroot00000000000000#!/bin/sh export MYSQL_PWD="${MYSQL_PWD:=edb}" export MYSQL_HOST="${MYSQL_HOST:=localhost}" export MYSQL_PORT="${MYSQL_PORT:=3306}" export MYSQL_USER_NAME="${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 dept;" 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 "DROP TABLE IF EXISTS test6;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test7;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS distinct_test;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS fdw520;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS fdw520_1;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS \`fdw-601\`;" 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 not null);" 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 -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test6 (c1 numeric(6,4))" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO test6 VALUES (25.252525)" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test7 (c1 int primary key auto_increment, c2 longtext NOT NULL);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO test7 (c2) SELECT repeat('abcdefgh ', 7500);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE distinct_test (id int primary key, c1 int, c2 int, c3 text, c4 text);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE dept (dept_id int PRIMARY KEY, stu_id varchar(10), FOREIGN KEY (stu_id) REFERENCES student1(stu_id));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e 'CREATE TABLE fdw520 (c1 int primary key, `c ``"" 2` int, c3 int generated always as (`c ``"" 2` * 2) stored, c4 int generated always as (`c ``"" 2` * 4) virtual not null);' mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE fdw520_1 (c1 int primary key, c2 int, c3 int generated always as (c2 * 2) stored, c4 int generated always as (c3 * 4) stored not null);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE \`fdw-601\` (a int primary key, b int);" mysql_fdw-REL-2_9_1/mysql_pushability.c000066400000000000000000000200661445421625600202550ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_pushability.c * routines for FDW pushability * * Portions Copyright (c) 2022-2023, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_pushability.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "common/string.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "miscadmin.h" #include "mysql_pushability.h" #include "storage/fd.h" #include "utils/fmgrprotos.h" #include "utils/hsearch.h" #include "utils/memutils.h" static char *get_config_filename(void); static void populate_pushability_hash(void); static void config_invalid_error_callback(void *arg); static bool get_line_buf(FILE *stream, StringInfo buf); /* Hash table for caching the configured pushdown objects */ static HTAB *pushabilityHash = NULL; /* * Memory context to hold the hash table, need to free incase of any error * while parsing the configuration file. */ static MemoryContext htab_ctx; /* * get_config_filename * Returns the path for the pushdown object configuration file for the * foreign-data wrapper. */ static char * get_config_filename(void) { char sharepath[MAXPGPATH]; char *result; get_share_path(my_exec_path, sharepath); result = (char *) palloc(MAXPGPATH); snprintf(result, MAXPGPATH, "%s/extension/%s_pushdown.config", sharepath, FDW_MODULE_NAME); return result; } /* * mysql_check_remote_pushability * Lookups into hash table by forming the hash key from provided object * oid. */ bool mysql_check_remote_pushability(Oid object_oid) { bool found = false; /* Populate pushability hash if not already. */ if (unlikely(!pushabilityHash)) populate_pushability_hash(); hash_search(pushabilityHash, &object_oid, HASH_FIND, &found); return found; } /* * populate_pushability_hash * Creates the hash table and populates the hash entries by reading the * pushdown object configuration file. */ static void populate_pushability_hash(void) { FILE *file = NULL; char *config_filename; HASHCTL ctl; ErrorContextCallback errcallback; unsigned int line_no = 0; StringInfoData linebuf; HTAB *hash; Assert(pushabilityHash == NULL); /* * Create a memory context to hold hash table. This makes it easy to * clean up in the case of error, we don't make the context long-lived * until we parse the complete config file without an error. */ htab_ctx = AllocSetContextCreate(CurrentMemoryContext, "mysql pushability_hash", ALLOCSET_DEFAULT_SIZES); ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(FDWPushdownObject); ctl.hcxt = htab_ctx; /* Create the hash table */ hash = hash_create("mysql_fdw push elements hash", 256, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); /* Get the config file name */ config_filename = get_config_filename(); file = AllocateFile(config_filename, PG_BINARY_R); if (file == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open \"%s\": %m", config_filename))); /* Set up callback to provide the error context */ errcallback.callback = config_invalid_error_callback; errcallback.arg = (void *) config_filename; errcallback.previous = error_context_stack; error_context_stack = &errcallback; initStringInfo(&linebuf); /* * Read the pushdown object configuration file and push object information * to the in-memory hash table for a faster lookup. */ while (get_line_buf(file, &linebuf)) { FDWPushdownObject *entry; Oid objectId; ObjectType objectType; bool found; char *str; line_no++; /* If record starts with #, then consider as comment. */ if (linebuf.data[0] == '#') continue; /* Ignore if all blank */ if (strspn(linebuf.data, " \t\r\n") == linebuf.len) continue; /* Strip trailing newline, including \r in case we're on Windows */ while (linebuf.len > 0 && (linebuf.data[linebuf.len - 1] == '\n' || linebuf.data[linebuf.len - 1] == '\r')) linebuf.data[--linebuf.len] = '\0'; /* Strip leading whitespaces. */ str = linebuf.data; while (isspace(*str)) str++; if (pg_strncasecmp(str, "ROUTINE", 7) == 0) { /* Move over ROUTINE */ str = str + 7; /* Move over any whitespace */ while (isspace(*str)) str++; objectType = OBJECT_FUNCTION; objectId = DatumGetObjectId(DirectFunctionCall1(regprocedurein, CStringGetDatum(str))); } else if (pg_strncasecmp(str, "OPERATOR", 8) == 0) { /* Move over OPERATOR */ str = str + 8; /* Move over any whitespace */ while (isspace(*str)) str++; objectType = OBJECT_OPERATOR; objectId = DatumGetObjectId(DirectFunctionCall1(regoperatorin, CStringGetDatum(str))); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid object type in configuration file at line number: %d", line_no), errhint("Valid values are: \"ROUTINE\", \"OPERATOR\"."))); /* Insert the new element to the hash table */ entry = hash_search(hash, &objectId, HASH_ENTER, &found); /* Two different objects cannot have the same system object id */ if (found && entry->objectType != objectType) elog(ERROR, "different pushdown objects have the same oid \"%d\"", objectId); entry->objectType = objectType; } if (ferror(file)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", config_filename))); error_context_stack = errcallback.previous; pfree(linebuf.data); FreeFile(file); /* * We have fully parsed the config file. Reparent hash table context so * that it has the right lifespan. */ MemoryContextSetParent(htab_ctx, CacheMemoryContext); pushabilityHash = hash; } /* * config_invalid_error_callback * Error callback to define the context. */ static void config_invalid_error_callback(void *arg) { char *filename = (char *) arg; /* Destroy the hash in case of error */ hash_destroy(pushabilityHash); pushabilityHash = NULL; errcontext("while processing \"%s\" file", filename); } /* * get_line_buf * Returns true if a line was successfully collected (including * the case of a non-newline-terminated line at EOF). * * Returns false if there was an I/O error or no data was available * before EOF. In the false-result case, buf is reset to empty. * (Borrowed the code from pg_get_line_buf().) */ bool get_line_buf(FILE *stream, StringInfo buf) { int orig_len; /* We just need to drop any data from the previous call */ resetStringInfo(buf); orig_len = buf->len; /* Read some data, appending it to whatever we already have */ while (fgets(buf->data + buf->len, buf->maxlen - buf->len, stream) != NULL) { buf->len += strlen(buf->data + buf->len); /* Done if we have collected a newline */ if (buf->len > orig_len && buf->data[buf->len - 1] == '\n') return true; /* Make some more room in the buffer, and loop to read more data */ enlargeStringInfo(buf, 128); } /* Check for I/O errors and EOF */ if (ferror(stream) || buf->len == orig_len) { /* Discard any data we collected before detecting error */ buf->len = orig_len; buf->data[orig_len] = '\0'; return false; } /* No newline at EOF, but we did collect some data */ return true; } /* * mysql_get_configured_pushdown_objects * Returns the hash table objects by sequentially scanning the hash table. */ List * mysql_get_configured_pushdown_objects(bool reload) { List *result = NIL; HASH_SEQ_STATUS scan; FDWPushdownObject *entry; FDWPushdownObject *object; Size size = sizeof(FDWPushdownObject); /* * To avoid the memory leak, destroy the existing hash in case of * reloading. */ if (reload) { hash_destroy(pushabilityHash); pushabilityHash = NULL; MemoryContextDelete(htab_ctx); } /* Reload configuration if that not loaded at all */ if (!pushabilityHash) populate_pushability_hash(); hash_seq_init(&scan, pushabilityHash); while ((entry = (FDWPushdownObject *) hash_seq_search(&scan)) != NULL) { object = (FDWPushdownObject *) palloc(size); memcpy(object, entry, size); result = lappend(result, object); } return result; } mysql_fdw-REL-2_9_1/mysql_pushability.h000066400000000000000000000017621445421625600202640ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_pushability.h * prototypes for mysql_pushability.c * * Portions Copyright (c) 2022-2023, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_pushability.h *------------------------------------------------------------------------- */ #ifndef MYSQL_PUSHABILITY_H #define MYSQL_PUSHABILITY_H #include "nodes/parsenodes.h" #include "nodes/pg_list.h" /* * NB: Module name must be the same as the MODULE_big configure in the Makefile * of FDW contrib module. Otherwise, the pushdown object configuration file will * not be located correctly. */ #define FDW_MODULE_NAME "mysql_fdw" /* Structure to help hold the pushdown object in the hash table */ typedef struct FDWPushdownObject { Oid objectId; ObjectType objectType; } FDWPushdownObject; extern bool mysql_check_remote_pushability(Oid objectOid); extern List *mysql_get_configured_pushdown_objects(bool reload); #endif /* MYSQL_PUSHABILITY_H */ mysql_fdw-REL-2_9_1/mysql_query.c000066400000000000000000000266531445421625600170750ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.c * Type handling for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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; char str[MAXDATELEN]; bytea *result; char *text_result = NULL; /* 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; 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; case TEXTOID: text_result = (char *) palloc(column->length + 1); memcpy(text_result, (char *) column->value, column->length); text_result[column->length] = '\0'; valueDatum = CStringGetDatum((char *) text_result); break; default: valueDatum = CStringGetDatum((char *) column->value); } value_datum = OidFunctionCall3(typeinput, valueDatum, ObjectIdGetDatum(pgtyp), Int32GetDatum(pgtypmod)); if (text_result) pfree(text_result); 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; memset(mbind, 0, sizeof(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; case TEXTOID: mbind->buffer_type = MYSQL_TYPE_VAR_STRING; if (field->max_length == 0) { column->value = (Datum) palloc0(MAXDATALEN); mbind->buffer_length = MAXDATALEN; } else { column->value = (Datum) palloc0(field->max_length); mbind->buffer_length = field->max_length; } mbind->buffer = (char *) column->value; 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_9_1/mysql_query.h000066400000000000000000000016551445421625600170750ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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_9_1/option.c000066400000000000000000000211061445421625600157770ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * option.c * FDW option handling for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2023, 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 "mb/pg_wchar.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}, {"character_set", ForeignServerRelationId}, #if PG_VERSION_NUM >= 140000 /* truncatable is available on both server and table */ {"truncatable", ForeignServerRelationId}, {"truncatable", ForeignTableRelationId}, #endif {"sql_mode", 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); } #if PG_VERSION_NUM >= 140000 else if (strcmp(def->defname, "truncatable") == 0) { /* accept only boolean values */ (void) defGetBoolean(def); } #endif } 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, "character_set") == 0) opt->character_set = defGetString(def); if (strcmp(def->defname, "sql_mode") == 0) opt->sql_mode = defGetString(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; /* Default value for character_set */ if (!opt->character_set) opt->character_set = MYSQL_AUTODETECT_CHARSET_NAME; /* Special value provided for existing behavior */ else if (strcmp(opt->character_set, "PGDatabaseEncoding") == 0) opt->character_set = (char *) GetDatabaseEncodingName(); /* Default value for sql_mode */ if (!opt->sql_mode) opt->sql_mode = "ANSI_QUOTES"; return opt; } mysql_fdw-REL-2_9_1/sql/000077500000000000000000000000001445421625600151225ustar00rootroot00000000000000mysql_fdw-REL-2_9_1/sql/aggregate_pushdown.sql000066400000000000000000000416361445421625600215320ustar00rootroot00000000000000\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 exp(max(c1)) = exp(2) ORDER BY 1, 2; SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING exp(max(c1)) = exp(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; -- FDW-129: Limit and offset pushdown with Aggregate pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 1 OFFSET 1; -- Limit 0, Offset 0 with aggregates. EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT 0 OFFSET 0; -- Limit NULL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT NULL OFFSET 2; -- Limit ALL EXPLAIN (VERBOSE, COSTS OFF) SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; SELECT min(c1), c1 FROM fdw132_t1 GROUP BY c1 ORDER BY c1 LIMIT ALL OFFSET 2; -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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_9_1/sql/connection_validation.sql000066400000000000000000000054511445421625600222210ustar00rootroot00000000000000\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_9_1/sql/dml.sql000066400000000000000000000223231445421625600164210ustar00rootroot00000000000000\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 (dbname 'mysql_fdw_regress1', 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 table does not exists. INSERT INTO fdw126_ft6 VALUES(1, 'One'); -- 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; -- FDW-518: Should honor ON CONFLICT DO NOTHING clause. SELECT * FROM f_mysql_test ORDER BY 1; -- Should not throw an error while inserting duplicate value as we are using -- ON CONFLICT DO NOTHING clause. INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT DO NOTHING; SELECT * FROM f_mysql_test ORDER BY 1; -- Should throw an error INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT (a, b) DO NOTHING; INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT DO UPDATE SET b = 10; INSERT INTO f_mysql_test VALUES(1,1) ON CONFLICT (a) DO UPDATE SET b = 10; -- FDW-601: database and table name should be quoted correctly in case of -- INSERT/UPDATE/DELETE. CREATE FOREIGN TABLE fdw601(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'fdw-601'); INSERT INTO fdw601 VALUES(1,1), (2,2); UPDATE fdw601 SET b = 3 WHERE b = 2; DELETE FROM fdw601 WHERE b = 1; SELECT * FROM fdw601 ORDER BY 1; DELETE FROM fdw601; -- 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 FOREIGN TABLE fdw601; 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_9_1/sql/join_pushdown.sql000066400000000000000000000501331445421625600205330ustar00rootroot00000000000000\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. 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. 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. 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. 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 LIMIT 2 OFFSET 2; 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 LIMIT 2 OFFSET 2; -- 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. 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. 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. 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. 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. 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. 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; SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1; -- 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 2; 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 2; -- 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 round(5.4) OFFSET 2; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT round(5.4) OFFSET 2; -- 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 8 OFFSET round(2.2); 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 8 OFFSET round(2.2); 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. 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; -- 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; 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; -- 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; 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; -- 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; 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; -- 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; 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; -- FDW-129: Limit and offset pushdown with join pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT round(2.2) OFFSET 2; -- Limit as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT NULL OFFSET 1; -- Limit as ALL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT ALL OFFSET 1; -- Offset as NULL, no LIMIT/OFFSET pushdown. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (TRUE) ORDER BY t1.c1, t2.c1 LIMIT 3 OFFSET NULL; -- 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. CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1); 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'); CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2); 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_9_1/sql/limit_offset_pushdown.sql000066400000000000000000000066161445421625600222670ustar00rootroot00000000000000\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 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 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'); INSERT INTO f_test_tbl2 VALUES(50, 'IT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(60, 'DB SERVER', 'PUNE'); SELECT * FROM f_test_tbl2 ORDER BY 1; -- LIMIT/OFFSET pushdown. -- Limit with Offset should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET 2; -- Only Limit should get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3; -- Expression in Limit clause. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT round(3.2) OFFSET 2; -- Only Offset without Limit should not get pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; SELECT * FROM f_test_tbl2 ORDER BY 1 OFFSET 2; -- Limit ALL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT ALL; -- Limit NULL EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT NULL OFFSET 2; -- Limit 0 and Offset 0 EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 0 OFFSET 0; -- Offset NULL. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT 3 OFFSET NULL; -- Limit with placeholder. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (SELECT COUNT(*) FROM f_test_tbl2); -- Limit with expression, should not pushdown. EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); SELECT * FROM f_test_tbl2 ORDER BY 1 LIMIT (10 - (SELECT COUNT(*) FROM f_test_tbl2)); DELETE FROM f_test_tbl2; 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_9_1/sql/misc.sql000066400000000000000000000152561445421625600166070ustar00rootroot00000000000000\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 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 foreign tables and insert data. CREATE FOREIGN TABLE fdw519_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 fdw519_ft2(c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE FOREIGN TABLE fdw519_ft3 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9), c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO fdw519_ft3 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO fdw519_ft3 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); -- Check truncatable option with invalid values. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'abc'); ALTER FOREIGN TABLE fdw519_ft1 OPTIONS (ADD truncatable 'abc'); -- Default behavior, should truncate. TRUNCATE fdw519_ft1; SELECT * FROM fdw519_ft1 ORDER BY 1; INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); -- Set truncatable to false -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (ADD truncatable 'false'); -- Truncate the table. TRUNCATE fdw519_ft1; SELECT * FROM fdw519_ft1 ORDER BY 1; -- Set truncatable to true -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1; SELECT * FROM fdw519_ft1 ORDER BY 1; -- truncatable to true on Server but false on table level. -- Since truncatable option is available since v14, this gives an error on v13 -- and previous versions. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (ADD truncatable 'true'); SELECT * FROM fdw519_ft2 ORDER BY 1; TRUNCATE fdw519_ft2; SELECT * FROM fdw519_ft2 ORDER BY 1; INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); INSERT INTO fdw519_ft2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO fdw519_ft2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); -- truncatable to true on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ALTER TABLE fdw519_ft1 OPTIONS (ADD truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2; SELECT * FROM fdw519_ft1 ORDER BY 1; SELECT * FROM fdw519_ft2 ORDER BY 1; -- truncatable to false on Server but false on one table and true for other -- table. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'false'); ALTER TABLE fdw519_ft2 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2; SELECT * FROM fdw519_ft1 ORDER BY 1; SELECT * FROM fdw519_ft2 ORDER BY 1; -- Truncate from different servers. ALTER SERVER mysql_svr OPTIONS (SET truncatable 'true'); ALTER SERVER mysql_svr1 OPTIONS (ADD truncatable 'true'); ALTER TABLE fdw519_ft1 OPTIONS (SET truncatable 'true'); TRUNCATE fdw519_ft1, fdw519_ft2, fdw519_ft3; SELECT * FROM fdw519_ft1 ORDER BY 1; SELECT * FROM fdw519_ft2 ORDER BY 1; SELECT * FROM fdw519_ft3 ORDER BY 1; INSERT INTO fdw519_ft1 VALUES(1, 'One', 101); SELECT * FROM fdw519_ft1 ORDER BY 1; -- Truncate with CASCADE is not supported. TRUNCATE fdw519_ft1 CASCADE; SELECT * FROM fdw519_ft1 ORDER BY 1; -- Default is RESTRICT, so it is allowed. TRUNCATE fdw519_ft1 RESTRICT; SELECT * FROM fdw519_ft1 ORDER BY 1; -- Should throw an error if primary key is referenced by foreign key. CREATE FOREIGN TABLE fdw519_ft4(stu_id varchar(10), stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student1'); CREATE FOREIGN TABLE fdw519_ft5(dept_id int, stu_id varchar(10)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'dept'); TRUNCATE fdw519_ft4; -- FDW-520: Support generated columns in IMPORT FOREIGN SCHEMA command. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); \d fdw520 -- Generated column refers to another generated column, should throw an error: IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520_1) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'true'); -- import_generated as false. DROP FOREIGN TABLE fdw520; IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public OPTIONS (import_generated 'false'); \d fdw520 -- Without import_generated option, default is true. DROP FOREIGN TABLE fdw520; IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (fdw520) FROM SERVER mysql_svr INTO public; \d fdw520 -- FDW-521: Insert and update operations on table having generated columns. INSERT INTO fdw520(c1, "c `"""" 2") VALUES(1, 2); INSERT INTO fdw520(c1, "c `"""" 2", c3, c4) VALUES(2, 4, DEFAULT, DEFAULT); -- Should fail. INSERT INTO fdw520 VALUES(1, 2, 3, 4); SELECT * FROM fdw520 ORDER BY 1; UPDATE fdw520 SET "c `"""" 2" = 20 WHERE c1 = 2; SELECT * FROM fdw520 ORDER BY 1; -- Should fail. UPDATE fdw520 SET c4 = 20 WHERE c1 = 2; UPDATE fdw520 SET c3 = 20 WHERE c1 = 2; -- Cleanup DELETE FROM fdw519_ft1; DELETE FROM fdw519_ft2; DELETE FROM fdw519_ft3; DELETE FROM fdw520; DROP FOREIGN TABLE fdw519_ft1; DROP FOREIGN TABLE fdw519_ft2; DROP FOREIGN TABLE fdw519_ft3; DROP FOREIGN TABLE fdw519_ft4; DROP FOREIGN TABLE fdw519_ft5; DROP FOREIGN TABLE fdw520; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/sql/pushdown.sql000066400000000000000000000231011445421625600175070ustar00rootroot00000000000000\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; -- FDW-516: IS [NOT] DISTINCT FROM clause should deparse correctly. CREATE FOREIGN TABLE f_distinct_test (id int, c1 int, c2 int, c3 text, c4 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'distinct_test'); INSERT INTO f_distinct_test VALUES (1, 1, 1, 'abc', 'abc'), (2, 2, NULL, 'abc', 'NULL'), (3, NULL, NULL, 'NULL', 'NULL'), (4, 3, 4, 'abc', 'pqr'), (5, 4, 5, 'abc', 'abc'), (6, 5, 5, 'abc', 'pqr'); SELECT * FROM f_distinct_test ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c3) IS DISTINCT FROM (c4) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c3) IS DISTINCT FROM (c4) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) and (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c1) IS DISTINCT FROM (c2) and (c3) IS NOT DISTINCT FROM (c4) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) or (c3) IS DISTINCT FROM (c4) ORDER BY id; SELECT * FROM f_distinct_test WHERE (c1) IS NOT DISTINCT FROM (c2) or (c3) IS DISTINCT FROM (c4) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS DISTINCT FROM (c2)) IS DISTINCT FROM ((c3) IS NOT DISTINCT FROM (c4)) ORDER BY id; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; SELECT * FROM f_distinct_test WHERE ((c1) IS NOT DISTINCT FROM (c2)) IS NOT DISTINCT FROM ((c3) IS DISTINCT FROM (c4)) ORDER BY id; -- FDW-562: Test ORDER BY with user defined operators. -- Create the operator family required for the test. CREATE OPERATOR PUBLIC.<^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4EQ ); CREATE OPERATOR PUBLIC.=^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4LT ); CREATE OPERATOR PUBLIC.>^ ( LEFTARG = INT4, RIGHTARG = INT4, PROCEDURE = INT4GT ); CREATE OPERATOR FAMILY my_op_family USING btree; CREATE FUNCTION MY_OP_CMP(A INT, B INT) RETURNS INT AS $$ BEGIN RETURN BTINT4CMP(A, B); END $$ LANGUAGE PLPGSQL; CREATE OPERATOR CLASS my_op_class FOR TYPE INT USING btree FAMILY my_op_family AS OPERATOR 1 PUBLIC.<^, OPERATOR 3 PUBLIC.=^, OPERATOR 5 PUBLIC.>^, FUNCTION 1 my_op_cmp(INT, INT); -- FDW-562: Test ORDER BY with user defined operators. -- User defined operators are not pushed down. EXPLAIN (COSTS FALSE, VERBOSE) SELECT * FROM f_test_tbl1 ORDER BY c1 USING OPERATOR(public.<^); EXPLAIN (COSTS FALSE, VERBOSE) SELECT MIN(c1) FROM f_test_tbl1 ORDER BY 1 USING OPERATOR(public.<^); -- Cleanup DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DELETE FROM f_distinct_test; DROP OPERATOR CLASS my_op_class USING btree; DROP FUNCTION my_op_cmp(a INT, b INT); DROP OPERATOR FAMILY my_op_family USING btree; DROP OPERATOR public.>^(INT, INT); DROP OPERATOR public.=^(INT, INT); DROP OPERATOR public.<^(INT, INT); DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP FOREIGN TABLE f_distinct_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_9_1/sql/select.sql000066400000000000000000000562111445421625600171270ustar00rootroot00000000000000\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-217: IMPORT FOREIGN SCHEMA command with 'import_enum_as_text' option as -- true should map MySQL ENUM type to TEXT type in PG. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (enum_t1, enum_t2) FROM SERVER mysql_svr INTO public OPTIONS (import_enum_as_text 'true'); 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; -- FDW-417: Updating a NOT NULL column with NULL should throw an error -- if we set sql_mode to STRICT_ALL_TABLES. ALTER SERVER mysql_svr OPTIONS (sql_mode 'ANSI_QUOTES,STRICT_ALL_TABLES'); UPDATE f_mysql_test SET b = NULL WHERE a = 1; SELECT * FROM f_mysql_test ORDER BY 1; ALTER SERVER mysql_svr OPTIONS (DROP sql_mode); UPDATE f_mysql_test SET b = NULL WHERE a = 1; SELECT * FROM f_mysql_test ORDER BY 1; UPDATE f_mysql_test SET b = 1 WHERE a = 1; -- We should get a run-time error when sql_mode is set to invalid value. ALTER SERVER mysql_svr OPTIONS (sql_mode 'ABCD'); SELECT * FROM f_mysql_test ORDER BY 1; ALTER SERVER mysql_svr OPTIONS (DROP sql_mode); -- FDW-426: The numeric value should display correctly per precision and scale -- defined. CREATE FOREIGN TABLE f_test6(c1 numeric(6,2)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; -- Number with the required precision. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(6,4)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; -- Number only with precision. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(6)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; -- Number with improper precision and scale, should throw an error. DROP FOREIGN TABLE f_test6; CREATE FOREIGN TABLE f_test6(c1 numeric(3,2)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test6'); SELECT * FROM f_test6 ORDER BY 1; -- FDW-156: Long string data in column greater than MAXDATALEN length should -- be fetched correctly. CREATE FOREIGN TABLE f_test7(c1 int, c2 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test7'); EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, length(c2) FROM f_test7 ORDER BY 1; SELECT c1, length(c2) FROM f_test7 ORDER BY 1; -- FDW-130: ORDER BY with collation clause, should not get pushdown. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c2 COLLATE "en_US"; SELECT * FROM f_test_tbl1 ORDER BY c2 COLLATE "en_US"; -- Order by desc EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c1 DESC; SELECT * FROM f_test_tbl1 ORDER BY c1 DESC; -- Order by is not null EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL, c1; SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL, c1; -- Order by is not null desc EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL DESC, c1; SELECT * FROM f_test_tbl1 ORDER BY c7 IS NOT NULL DESC, c1; -- Order by desc nulls first EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS FIRST, c1; SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS FIRST, c1; -- Order by asc nulls last EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS LAST, c1; SELECT * FROM f_test_tbl1 ORDER BY c7 DESC NULLS LAST, c1; -- Test LIMIT where ORDER BY is not pushed down due to unsafe pathkeys. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 t1 ORDER BY t1 LIMIT 5; SELECT * FROM f_test_tbl1 t1 ORDER BY t1 LIMIT 5; -- FDW-322: Test mysql_fdw_display_pushdown_list() function. SELECT count(*) FROM mysql_fdw_display_pushdown_list(); -- FDW:554 - Array expression should not get pushdown to remote MySQL server as -- syntax is not supported. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 t1 WHERE c1 = ANY(ARRAY[100, 200, c1 + 10000]); SELECT * FROM f_test_tbl1 t1 WHERE c1 = ANY(ARRAY[100, 200, c1 + 10000]); -- 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 FOREIGN TABLE f_test6; DROP FOREIGN TABLE f_test7; 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_9_1/sql/server_options.sql000066400000000000000000000170251445421625600207310ustar00rootroot00000000000000\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; -- FDW-404: Support for character_set option at server level. CREATE SERVER charset101 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( character_set 'utf8' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'charset101' AND srvoptions @> array['character_set=utf8']; ALTER SERVER charset101 OPTIONS( SET character_set 'latin' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'charset101' AND srvoptions @> array['character_set=latin']; -- Cleanup character_set test objects. DROP SERVER charset101; -- Cleanup DROP EXTENSION mysql_fdw;