From 65364c36ae033df3dd8406a992562d4f0dc65719 Mon Sep 17 00:00:00 2001 From: khieuvm <53564670+khieuvm@users.noreply.github.com> Date: Thu, 14 Jan 2021 12:58:44 +0700 Subject: [PATCH] Merge upstream REL-2_5_5 and Bug fixes Merge from EnterpriseDB REL-2_5_5 (PostgreSQL 13.0 support etc.) Support function pushdown in the target list (for PGSpider) Bug fixes - Fix deparsing query for binding an array param - Fix pushdown function numeric() - Fix divide results inconsistent between Postgres and Mysql - Fix division by zero not occur - Fix binding text array param - Fix Subquery with ANY --- .gitattributes | 9 + CONTRIBUTING.md | 88 ++ Jenkinsfile | 250 +++ LICENSE | 2 +- Makefile | 18 +- README.md | 283 ++-- connection.c | 230 ++- deparse.c | 952 ++++++++---- expected/connection_validation.out | 69 + expected/dml.out | 291 ++++ expected/mysql_fdw.out | 377 ----- expected/pushdown.out | 356 +++++ expected/select.out | 1483 ++++++++++++++++++ expected/selectfunc.out | 576 +++++++ expected/server_options.out | 176 +++ mysql_fdw--1.0.sql | 4 +- mysql_fdw--1.1.sql | 81 +- mysql_fdw.c | 2315 ++++++++++++++-------------- mysql_fdw.control | 3 +- mysql_fdw.h | 287 ++-- mysql_init.sh | 66 +- mysql_query.c | 554 ++++--- mysql_query.h | 15 +- option.c | 138 +- sql/connection_validation.sql | 63 + sql/dml.sql | 248 +++ sql/mysql_fdw.sql | 99 -- sql/parameters.conf | 4 + sql/pushdown.sql | 183 +++ sql/select.sql | 550 +++++++ sql/selectfunc.sql | 284 ++++ sql/server_options.sql | 134 ++ 32 files changed, 7462 insertions(+), 2726 deletions(-) create mode 100644 .gitattributes create mode 100644 Jenkinsfile create mode 100644 expected/connection_validation.out create mode 100644 expected/dml.out delete mode 100644 expected/mysql_fdw.out create mode 100644 expected/pushdown.out create mode 100644 expected/select.out create mode 100644 expected/selectfunc.out create mode 100644 expected/server_options.out create mode 100644 sql/connection_validation.sql create mode 100644 sql/dml.sql delete mode 100644 sql/mysql_fdw.sql create mode 100644 sql/parameters.conf create mode 100644 sql/pushdown.sql create mode 100644 sql/select.sql create mode 100644 sql/selectfunc.sql create mode 100644 sql/server_options.sql diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d17b72e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..7cf9406 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +Contributing 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 diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..822b29b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,250 @@ +def NODE_NAME = 'AWS_Instance_CentOS' +def MAIL_TO = '$DEFAULT_RECIPIENTS' +def BRANCH_NAME = 'Branch [' + env.BRANCH_NAME + ']' +def BUILD_INFO = 'Jenkins job: ' + env.BUILD_URL + '\n' + +def MYSQL_DOCKER_PATH = '/home/jenkins/Docker/Server/Mysql' +def ENHANCE_TEST_DOCKER_PATH = '/home/jenkins/Docker' +def TEST_TYPE = 'MYSQL' + +START_EXISTED_TEST = '' +INIT_EXISTED_TEST = '' +START_ENHANCE_TEST = '' +INIT_ENHANCE_TEST = '' + + +pipeline { + agent { + node { + label NODE_NAME + } + } + options { + gitLabConnection('GitLabConnection') + } + triggers { + gitlab( + triggerOnPush: true, + triggerOnMergeRequest: false, + triggerOnClosedMergeRequest: false, + triggerOnAcceptedMergeRequest: true, + triggerOnNoteRequest: false, + setBuildDescription: true, + branchFilterType: 'All', + secretToken: "14edd1f2fc244d9f6dfc41f093db270a" + ) + } + stages { + stage('Start_containers_Existed_Test') { + steps { + script { + if (env.GIT_URL != null) { + BUILD_INFO = BUILD_INFO + "Git commit: " + env.GIT_URL.replace(".git", "/commit/") + env.GIT_COMMIT + "\n" + } + } + catchError() { + sh """ + cd ${MYSQL_DOCKER_PATH} + docker-compose up --build -d + """ + } + } + post { + failure { + script { + START_EXISTED_TEST = 'FAILED' + } + updateGitlabCommitStatus name: 'Build', state: 'failed' + } + success { + script { + START_EXISTED_TEST = 'SUCCESS' + } + updateGitlabCommitStatus name: 'Build', state: 'success' + } + } + } + stage('Initialize_for_Existed_Test') { + steps { + catchError() { + sh """ + docker exec mysqlserver_for_existed_test /bin/bash -c '/tmp/start_existed_test.sh ${env.GIT_BRANCH}' + """ + } + } + post { + failure { + script { + INIT_EXISTED_TEST = 'FAILED' + } + updateGitlabCommitStatus name: 'Init_Data', state: 'failed' + } + success { + script { + INIT_EXISTED_TEST = 'SUCCESS' + } + updateGitlabCommitStatus name: 'Init_Data', state: 'success' + } + } + } + stage('make_check_Existed_Test') { + steps { + catchError() { + sh """ + rm -rf make_check_existed_test.out || true + docker exec -u postgres postgresserver_for_mysql_existed_test /bin/bash -c '/tmp/mysql_existed_test.sh ${env.GIT_BRANCH}' + docker exec -w /home/postgres/postgresql-13beta2/contrib/mysql_fdw postgresserver_for_mysql_existed_test /bin/bash -c 'su -c "make clean && make" postgres' + docker exec -w /home/postgres/postgresql-13beta2/contrib/mysql_fdw postgresserver_for_mysql_existed_test /bin/bash -c 'su -c "export LD_LIBRARY_PATH=":/usr/lib64/mysql/" && export LANGUAGE="en_US.UTF-8" && export LANG="en_US.UTF-8" && export LC_ALL="en_US.UTF-8" && make check | tee make_check.out" postgres' + docker cp postgresserver_for_mysql_existed_test:/home/postgres/postgresql-13beta2/contrib/mysql_fdw/make_check.out make_check_existed_test.out + """ + } + script { + status = sh(returnStatus: true, script: "grep -q 'All [0-9]* tests passed' 'make_check_existed_test.out'") + if (status != 0) { + unstable(message: "Set UNSTABLE result") + sh 'docker cp postgresserver_for_mysql_existed_test:/home/postgres/postgresql-13beta2/contrib/mysql_fdw/regression.diffs regression.diffs' + sh 'cat regression.diffs || true' + updateGitlabCommitStatus name: 'make_check', state: 'failed' + } else { + updateGitlabCommitStatus name: 'make_check', state: 'success' + } + } + } + } + stage('Start_containers_Enhance_Test') { + steps { + catchError() { + sh """ + cd ${ENHANCE_TEST_DOCKER_PATH} + docker-compose up --build -d + """ + } + } + post { + failure { + script { + START_ENHANCE_TEST = 'FAILED' + } + updateGitlabCommitStatus name: 'Build', state: 'failed' + } + success { + script { + START_ENHANCE_TEST = 'SUCCESS' + } + updateGitlabCommitStatus name: 'Build', state: 'success' + } + } + } + stage('Initialize_for_Enhance_Test') { + steps { + catchError() { + sh """ + docker exec mysqlserver1_enhance_test /bin/bash -c '/tmp/start_enhance_test.sh' + docker exec mysqlserver2_enhance_test /bin/bash -c '/tmp/start_enhance_test.sh' + docker exec postgresserver1_enhance_test /bin/bash -c '/tmp/start_enhance_test_1.sh' + docker exec postgresserver2_enhance_test /bin/bash -c '/tmp/start_enhance_test_2.sh' + docker exec tinybraceserver1_enhance_test /bin/bash -c '/tmp/start_enhance_test_1.sh' + docker exec -d -w /usr/local/tinybrace tinybraceserver1_enhance_test /bin/bash -c 'bin/tbserver &' + docker exec tinybraceserver2_enhance_test /bin/bash -c '/tmp/start_enhance_test_2.sh' + docker exec -d -w /usr/local/tinybrace tinybraceserver2_enhance_test /bin/bash -c 'bin/tbserver &' + docker exec influxserver1_enhance_test /bin/bash -c '/tmp/start_enhance_test.sh' + docker exec influxserver2_enhance_test /bin/bash -c '/tmp/start_enhance_test.sh' + docker exec -d gridserver1_enhance_test /bin/bash -c '/tmp/start_enhance_test_1.sh' + sleep 10 + docker exec -d gridserver2_enhance_test /bin/bash -c '/tmp/start_enhance_test_2.sh' + sleep 10 + docker exec pgspiderserver1_enhance_test /bin/bash -c 'su -c "/tmp/start_enhance_test.sh ${env.GIT_BRANCH} ${TEST_TYPE}" pgspider' + """ + } + } + post { + failure { + script { + INIT_ENHANCE_TEST = 'FAILED' + } + updateGitlabCommitStatus name: 'Init_Data', state: 'failed' + } + success { + script { + INIT_ENHANCE_TEST = 'SUCCESS' + } + updateGitlabCommitStatus name: 'Init_Data', state: 'success' + } + } + } + stage('make_check_Enhance_Test') { + steps { + catchError() { + sh """ + rm -rf make_check_enhance_test.out regression.diffs || true + docker exec -w /home/pgspider/GIT/PGSpider/contrib/pgspider_core_fdw pgspiderserver1_enhance_test /bin/bash -c 'su -c "chmod a+x *.sh" pgspider' + docker exec -w /home/pgspider/GIT/PGSpider/contrib/pgspider_core_fdw pgspiderserver1_enhance_test /bin/bash -c "sed -i 's/enhance\\\\\\\\\\/BasicFeature1_File_4ARG enhance\\\\\\\\\\/BasicFeature1_File_AllARG enhance\\\\\\\\\\/BasicFeature1_GridDB_4ARG enhance\\\\\\\\\\/BasicFeature1_GridDB_AllARG enhance\\\\\\\\\\/BasicFeature1_InfluxDB_4ARG enhance\\\\\\\\\\/BasicFeature1_InfluxDB_AllARG enhance\\\\\\\\\\/BasicFeature1_MySQL_4ARG enhance\\\\\\\\\\/BasicFeature1_MySQL_AllARG enhance\\\\\\\\\\/BasicFeature1_PostgreSQL_4ARG enhance\\\\\\\\\\/BasicFeature1_PostgreSQL_AllARG enhance\\\\\\\\\\/BasicFeature1_SQLite_4ARG enhance\\\\\\\\\\/BasicFeature1_SQLite_AllARG enhance\\\\\\\\\\/BasicFeature1_TinyBrace_4ARG enhance\\\\\\\\\\/BasicFeature1_TinyBrace_AllARG enhance\\\\\\\\\\/BasicFeature1_t_max_range enhance\\\\\\\\\\/BasicFeature1_tmp_t15_4ARG enhance\\\\\\\\\\/BasicFeature1_tmp_t15_AllARG enhance\\\\\\\\\\/BasicFeature2_JOIN_Multi_Tbl enhance\\\\\\\\\\/BasicFeature2_SELECT_Muli_Tbl enhance\\\\\\\\\\/BasicFeature2_UNION_Multi_Tbl enhance\\\\\\\\\\/BasicFeature_Additional_Test enhance\\\\\\\\\\/BasicFeature_ComplexCommand enhance\\\\\\\\\\/BasicFeature_For_Bug_54 enhance\\\\\\\\\\/BasicFeature_For_Bug_60/enhance\\\\\\\\\\/BasicFeature1_MySQL_4ARG enhance\\\\\\\\\\/BasicFeature1_MySQL_AllARG/g' test_enhance.sh" + docker exec -w /home/pgspider/GIT/PGSpider/contrib/pgspider_core_fdw pgspiderserver1_enhance_test /bin/bash -c 'su -c "./test_enhance.sh" pgspider' + docker cp pgspiderserver1_enhance_test:/home/pgspider/GIT/PGSpider/contrib/pgspider_core_fdw/make_check.out make_check_enhance_test.out + """ + } + script { + status = sh(returnStatus: true, script: "grep -q 'All [0-9]* tests passed' 'make_check_enhance_test.out'") + if (status != 0) { + unstable(message: "Set UNSTABLE result") + sh 'docker cp pgspiderserver1_enhance_test:/home/pgspider/GIT/PGSpider/contrib/pgspider_core_fdw/regression.diffs regression.diffs' + sh 'cat regression.diffs || true' + updateGitlabCommitStatus name: 'make_check', state: 'failed' + } else { + updateGitlabCommitStatus name: 'make_check', state: 'success' + } + } + } + } + } + post { + success { + script { + prevResult = 'SUCCESS' + if (currentBuild.previousBuild != null) { + prevResult = currentBuild.previousBuild.result.toString() + } + if (prevResult != 'SUCCESS') { + emailext subject: '[CI MYSQL_FDW] InfluxDB_Test BACK TO NORMAL on ' + BRANCH_NAME, body: BUILD_INFO + '\n---------EXISTED_TEST---------\n' + '${FILE,path="make_check_existed_test.out"}' + '\n---------ENHANCE_TEST---------\n' + '${FILE,path="make_check_enhance_test.out"}', to: "${MAIL_TO}", attachLog: false + } + } + } + unsuccessful { + script { + if (START_EXISTED_TEST == 'FAILED') { + if (START_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Start Containers FAILED | ENHANCE_TEST: Start Containers FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${BUILD_LOG, maxLines=200, escapeHtml=false}', to: "${MAIL_TO}", attachLog: false + } else if (INIT_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Start Containers FAILED | ENHANCE_TEST: Initialize FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${BUILD_LOG, maxLines=200, escapeHtml=false}', to: "${MAIL_TO}", attachLog: false + } else { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Start Containers FAILED | ENHANCE_TEST: Result make check ' + BRANCH_NAME, body: BUILD_INFO + '${FILE,path="make_check_enhance_test.out"}', to: "${MAIL_TO}", attachLog: false + } + } else if (INIT_EXISTED_TEST == 'FAILED') { + if (START_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Initialize FAILED | ENHANCE_TEST: Start Containers FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${BUILD_LOG, maxLines=200, escapeHtml=false}', to: "${MAIL_TO}", attachLog: false + } else if (INIT_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Initialize FAILED | ENHANCE_TEST: Initialize FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${BUILD_LOG, maxLines=200, escapeHtml=false}', to: "${MAIL_TO}", attachLog: false + } else { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Initialize FAILED | ENHANCE_TEST: Result make check ' + BRANCH_NAME, body: BUILD_INFO + '${FILE,path="make_check_enhance_test.out"}', to: "${MAIL_TO}", attachLog: false + } + } else { + if (START_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Result make check | ENHANCE_TEST: Start Containers FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${FILE,path="make_check_existed_test.out"}', to: "${MAIL_TO}", attachLog: false + } else if (INIT_ENHANCE_TEST == 'FAILED') { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Result make check | ENHANCE_TEST: Initialize FAILED ' + BRANCH_NAME, body: BUILD_INFO + '${FILE,path="make_check_existed_test.out"}', to: "${MAIL_TO}", attachLog: false + } else { + emailext subject: '[CI MYSQL_FDW] EXISTED_TEST: Result make check | ENHANCE_TEST: Result make check ' + BRANCH_NAME, body: BUILD_INFO + '\n---------EXISTED_TEST---------\n' + '${FILE,path="make_check_existed_test.out"}' + '\n---------ENHANCE_TEST---------\n' + '${FILE,path="make_check_enhance_test.out"}', to: "${MAIL_TO}", attachLog: false + } + } + } + } + always { + sh """ + cd ${MYSQL_DOCKER_PATH} + docker-compose down + cd ${ENHANCE_TEST_DOCKER_PATH} + docker-compose down + """ + } + } +} diff --git a/LICENSE b/LICENSE index bbe8977..b33fa44 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MySQL Foreign Data Wrapper for PostgreSQL -Copyright (c) 2011 - 2019, EnterpriseDB Corporation +Copyright (c) 2011-2020, 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 diff --git a/Makefile b/Makefile index d5e7b36..388b0ad 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,8 @@ -######################################################################------------------------------------------------------------------------- -# -# mysql_fdw.c -# Foreign-data wrapper for remote MySQL servers +# mysql_fdw/Makefile # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group +# Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. # -# Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. -# -# IDENTIFICATION -# mysql_fdw.c -# -########################################################################## MODULE_big = mysql_fdw OBJS = connection.o option.o deparse.o mysql_query.o mysql_fdw.o @@ -18,7 +10,7 @@ OBJS = connection.o option.o deparse.o mysql_query.o mysql_fdw.o EXTENSION = mysql_fdw DATA = mysql_fdw--1.0.sql mysql_fdw--1.1.sql mysql_fdw--1.0--1.1.sql -REGRESS = mysql_fdw +REGRESS = server_options connection_validation dml select pushdown selectfunc MYSQL_CONFIG = mysql_config PG_CPPFLAGS := $(shell $(MYSQL_CONFIG) --include) @@ -48,8 +40,8 @@ include $(PGXS) ifndef MAJORVERSION MAJORVERSION := $(basename $(VERSION)) endif -ifeq (,$(findstring $(MAJORVERSION), 9.3 9.4 9.5 9.6 10 11 12)) -$(error PostgreSQL 9.3, 9.4, 9.5, 9.6 10 11 12 is required to compile this extension) +ifeq (,$(findstring $(MAJORVERSION), 9.5 9.6 10 11 12 13)) +$(error PostgreSQL 9.5, 9.6, 10, 11, 12, or 13 is required to compile this extension) endif else diff --git a/README.md b/README.md index 8a55bf7..7bdff38 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,27 @@ MySQL Foreign Data Wrapper for PostgreSQL This PostgreSQL extension implements a Foreign Data Wrapper (FDW) for [MySQL][1]. -Please note that this version of mysql_fdw works with PostgreSQL and EDB Postgres Advanced Server 9.3, 9.4, 9.5, 9.6, 10, 11 and 12. +Please note that this version of mysql_fdw works with PostgreSQL and EDB +Postgres Advanced Server 9.5, 9.6, 10, 11, 12, and 13. -1. Installation ---------------- +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]. +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: +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. +2. The `mysql_config` must also be in the path, it resides in the MySQL + `bin` directory. ``` $ export PATH=/usr/local/mysql/bin/:$PATH @@ -35,233 +42,171 @@ To compile the [MySQL][1] foreign data wrapper, MySQL's C client library is need $ make USE_PGXS=1 install ``` -Not that we have tested the `mysql_fdw` extension only on MacOS and Ubuntu systems, but other \*NIX's should also work. +If you run into any issues, please [let us know][2]. + Enhancements ------------ -The following enhancements are added to the latest version of `mysql_fdw`: +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 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. +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. +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 to bring across to PostgreSQL. This is a performance feature. +### 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. +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 Statment +### Prepared Statement (Refactoring for `select` queries to use prepared statement) -The `select` queries are now using prepared statements instead of simple query protocol. +The `select` queries are now using prepared statements instead of simple +query protocol. 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` + * `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` + * `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` + * `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. 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. + * `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. The following parameters need to supplied while creating user mapping. * `username`: Username to use when connecting to MySQL. * `password`: Password to authenticate to the MySQL server with. +Examples +-------- +```sql -- load extension first time after install - - CREATE EXTENSION mysql_fdw; +CREATE EXTENSION mysql_fdw; -- create server object - - CREATE SERVER mysql_server - FOREIGN DATA WRAPPER mysql_fdw - OPTIONS (host '127.0.0.1', port '3306'); +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 +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 datetime) - SERVER mysql_server - OPTIONS (dbname 'db', table_name 'warehouse'); - +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', sysdate()); - INSERT INTO warehouse values (2, 'TV', sysdate()); - INSERT INTO warehouse values (3, 'Table', sysdate()); - +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; - SELECT * FROM warehouse; - - warehouse_id | warehouse_name | warehouse_created - - --------------+----------------+-------------------- - - 1 | UPS | 29-SEP-14 23:33:46 - - 2 | TV | 29-SEP-14 23:34:25 - - 3 | Table | 29-SEP-14 23:33:49 - +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; - +DELETE FROM warehouse where warehouse_id = 3; -- update a row of table +UPDATE warehouse set warehouse_name = 'UPS_NEW' where warehouse_id = 1; - UPDATE warehouse set warehouse_name = 'UPS_NEW' where warehouse_id = 1; - - --- explain a table - - EXPLAIN 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) - -> Foreign Scan on warehouse (cost=10.00..13.00 rows=3 width=36) - Local server startup cost: 10 - Remote query: SELECT warehouse_id, warehouse_name FROM db.warehouse WHERE ((warehouse_name like 'TV')) - Planning time: 0.564 ms -(5 rows) +-- 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 experince 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 guidlines][4]. +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. - -Changelog ---------- - -Version 2.5.0 -------------- -The following features are added as part of this mysql_fdw release : - -1) Support for PostgreSQL 11 - -2) Support for MySQL 8.0 - - -Version 2.4.0 -------------- -The following features are added as part of this mysql_fdw release : - -1) Support for finding .so version using - - ``` - select mysql_fdw_version(); - mysql_fdw_version - ----------------- - 20400 - (1 row) - ``` - - -Version 2.1.0 -------------- -The following features are added as part of this mysql_fdw release : - -1) Support for PostgreSQL 9.5. - -2) `IMPORT FOREIGN SCHEMA` support. Now mysql_fdw can use the import foreign schema functionality to import the remote server schema -metadata in PostgreSQL. (pull request #62) - -3) DML support for binary data. Binary data (longblob) in MySQL can mapped to PostgreSQL's bytea datatype. (#66) - -4) Support and map the MySQL datatype 'mediumblob' to BYTEA in PostgreSQL. (pull request #82) - -5) Support for JSON data type. (#58) - -6) Added MariaDB client library compatibility. (pull request #59) - -7) Added init_command server option which is used as MYSQL_INIT_COMMAND and may be used as OPTION in CREATE SERVER statement. - -8) Introduced new server option "use_remote_estimate". (#75) - -A new foreign server option "use_remote_estimate" is -added. If this option is true for a server then server gets the -number of rows from remote MySQL server. Server issues -an "EXPLAIN" call for the query to remote MySQL server and -gets the rows column and filtered column. Using the rows -and filtered column, it calculates the actual number of rows for -the query. - -9) Adding mysql regression's init script. - -10) Script create database and all the required table in MySQL's database. Script need to be run before running the regression. - -11) Enable / Disable secure authentication by using MYSQL option flag MYSQL_SECURE_AUTH. By default secure authentication is true. This option is -added for legacy systems which does not support secure authentication. (#39) - - -The following bug fixes are done as part of this mysql_fdw release: - - -1) Fix IMPORT FOREIGN SCHEMA issues with LIMIT/EXCLUDE (pull request #84) - -2) Wrapped table names for LIMIT/EXCLUDE in single quotes so these keywords can be used as table names. - -3) Modified foreign schema query to use null-safe equals operator - -4) Don't send "E" for regular expression. This is needed so regular expressions can work properly. (#77) - -5) Builtin functions have different names in PostgreSQL and MySQL. Current implementation is for translating PostgreSQL's -btrim to MySQL' trim. To do the translation mysql_deparse_func_expr function performs the replacement. (#70) - -6) Don't quote functions in WHERE clause while push down to remote server. The mysql server doesn't execute them as a function if they functions -names are quoted. (#42) - -7) Don't push WHERE clause in case of PARAM value. Since the param values are not available at the remote server, this ends up causing an error. (#44) - -8) Added a check, that forces that the first column of MySQL table must have a unique constraint. (#45) - -9) Improvements to pattern matching algorithm +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 - 2019, EnterpriseDB Corporation +Copyright (c) 2011-2020, 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. +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`][5] file for full details. +See the [`LICENSE`][4] file for full details. [1]: http://www.mysql.com -[3]: https://github.com/EnterpriseDB/mysql_fdw/issues/new -[4]: CONTRIBUTING.md -[5]: LICENSE +[2]: https://github.com/enterprisedb/mysql_fdw/issues/new +[3]: CONTRIBUTING.md +[4]: LICENSE diff --git a/connection.c b/connection.c index 6b18027..bb5f6cd 100644 --- a/connection.c +++ b/connection.c @@ -1,11 +1,10 @@ /*------------------------------------------------------------------------- * * connection.c - * Foreign-data wrapper for remote MySQL servers + * Connection management functions for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * connection.c @@ -14,14 +13,16 @@ */ #include "postgres.h" -#include "mysql_fdw.h" -#include "access/xact.h" +#if PG_VERSION_NUM >= 130000 +#include "common/hashfn.h" +#endif #include "mb/pg_wchar.h" -#include "miscadmin.h" +#include "mysql_fdw.h" #include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/memutils.h" -#include "utils/resowner.h" +#include "utils/syscache.h" /* Length of host */ #define HOST_LEN 256 @@ -41,8 +42,11 @@ typedef struct ConnCacheKey typedef struct ConnCacheEntry { - ConnCacheKey key; /* hash key (must be first) */ - MYSQL *conn; /* connection to foreign server, or NULL */ + 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; /* @@ -50,33 +54,45 @@ typedef struct ConnCacheEntry */ 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. + * 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 * mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt) { - bool found; + bool found; ConnCacheEntry *entry; ConnCacheKey key; /* First time through, initialize connection cache hashtable */ if (ConnectionHash == NULL) { - HASHCTL ctl; + 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 */ + /* Allocate ConnectionHash in the cache context */ ctl.hcxt = CacheMemoryContext; ConnectionHash = hash_create("mysql_fdw connections", 8, - &ctl, - HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + &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 */ @@ -89,39 +105,69 @@ mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt) entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); if (!found) { - /* initialize new hashtable entry (key is already filled in) */ + /* 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_connect( - opt->svr_address, - opt->svr_username, - opt->svr_password, - opt->svr_database, - opt->svr_port, - opt->svr_sa, - opt->svr_init_command, - opt->ssl_key, - opt->ssl_cert, - opt->ssl_ca, - opt->ssl_capath, - opt->ssl_cipher - ); +#if PG_VERSION_NUM < 90600 + Oid umoid; +#endif + + entry->conn = mysql_connect(opt); elog(DEBUG3, "new mysql_fdw connection %p for server \"%s\"", entry->conn, server->servername); + + /* + * Once the connection is established, then set the connection + * invalidation flag to false, also set the server and user mapping + * hash values. + */ + entry->invalidated = false; + entry->server_hashvalue = + GetSysCacheHashValue1(FOREIGNSERVEROID, + ObjectIdGetDatum(server->serverid)); +#if PG_VERSION_NUM >= 90600 + entry->mapping_hashvalue = + GetSysCacheHashValue1(USERMAPPINGOID, + ObjectIdGetDatum(user->umid)); +#else + /* Pre-9.6, UserMapping doesn't store its OID, so look it up again */ + umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(user->userid), + ObjectIdGetDatum(user->serverid)); + if (!OidIsValid(umoid)) + { + /* Not found for the specific user -- try PUBLIC */ + umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(InvalidOid), + ObjectIdGetDatum(user->serverid)); + } + entry->mapping_hashvalue = + GetSysCacheHashValue1(USERMAPPINGOID, ObjectIdGetDatum(umoid)); +#endif } return entry->conn; } /* - * cleanup_connection: - * Delete all the cache entries on backend exists. + * mysql_cleanup_connection: + * Delete all the cache entries on backend exists. */ void mysql_cleanup_connection(void) { - HASH_SEQ_STATUS scan; + HASH_SEQ_STATUS scan; ConnCacheEntry *entry; if (ConnectionHash == NULL) @@ -134,16 +180,16 @@ mysql_cleanup_connection(void) continue; elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); - _mysql_close(entry->conn); + mysql_close(entry->conn); entry->conn = NULL; } } /* - * Release connection created by calling GetConnection. + * Release connection created by calling mysql_get_connection. */ void -mysql_rel_connection(MYSQL *conn) +mysql_release_connection(MYSQL *conn) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; @@ -160,7 +206,7 @@ mysql_rel_connection(MYSQL *conn) if (entry->conn == conn) { elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); - _mysql_close(entry->conn); + mysql_close(entry->conn); entry->conn = NULL; hash_seq_term(&scan); break; @@ -168,65 +214,87 @@ mysql_rel_connection(MYSQL *conn) } } - -MYSQL* -mysql_connect( - char *svr_address, - char *svr_username, - char *svr_password, - char *svr_database, - int svr_port, - bool svr_sa, - char *svr_init_command, - char *ssl_key, - char *ssl_cert, - char *ssl_ca, - char *ssl_capath, - char *ssl_cipher) +MYSQL * +mysql_connect(mysql_opt *opt) { - MYSQL *conn = NULL; + 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; + my_bool secure_auth = svr_sa; #endif /* Connect to the server */ - conn = _mysql_init(NULL); + conn = mysql_init(NULL); if (!conn) ereport(ERROR, - (errcode(ERRCODE_FDW_OUT_OF_MEMORY), - errmsg("failed to initialise the MySQL connection object") - )); + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("failed to initialise the MySQL connection object"))); - _mysql_options(conn, MYSQL_SET_CHARSET_NAME, GetDatabaseEncodingName()); + mysql_options(conn, MYSQL_SET_CHARSET_NAME, GetDatabaseEncodingName()); #if MYSQL_VERSION_ID < 80000 - _mysql_options(conn, MYSQL_SECURE_AUTH, &secure_auth); + 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); + mysql_options(conn, MYSQL_INIT_COMMAND, svr_init_command); + + mysql_ssl_set(conn, opt->ssl_key, opt->ssl_cert, opt->ssl_ca, + opt->ssl_capath, ssl_cipher); - _mysql_ssl_set(conn, ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher); - - if (!_mysql_real_connect(conn, svr_address, svr_username, svr_password, svr_database, svr_port, NULL, 0)) + 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)) - )); + (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 + /* 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) - ); + "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; + } +} diff --git a/deparse.c b/deparse.c index 1c5459b..7168435 100644 --- a/deparse.c +++ b/deparse.c @@ -1,24 +1,18 @@ /*------------------------------------------------------------------------- * * deparse.c - * Foreign-data wrapper for remote MySQL servers + * Query deparser for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * deparse.c * *------------------------------------------------------------------------- */ - #include "postgres.h" -#include "mysql_fdw.h" - -#include "pgtime.h" - #include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" @@ -30,21 +24,26 @@ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "datatype/timestamp.h" +#include "mysql_fdw.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #if PG_VERSION_NUM < 120000 - #include "optimizer/var.h" +#include "optimizer/var.h" #else - #include "optimizer/optimizer.h" +#include "optimizer/optimizer.h" #endif #include "parser/parsetree.h" +#include "pgtime.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/timestamp.h" +/* Return true if integer type */ +#define IS_INTEGER_TYPE(typid) ((typid == INT2OID) || (typid == INT4OID) || (typid == INT8OID)) -static char *mysql_quote_identifier(const char *s, char q); +static char *mysql_quote_identifier(const char *str, char quotechar); +static bool mysql_contain_immutable_functions_walker(Node *node, void *context); /* * Global context for foreign_expr_walker's search of an expression tree. @@ -70,6 +69,7 @@ typedef struct foreign_loc_cxt { Oid collation; /* OID of current collation, if any */ FDWCollateState state; /* state of current collation choice */ + bool can_skip_cast; /* outer function can skip float cast */ } foreign_loc_cxt; /* @@ -80,7 +80,8 @@ typedef struct deparse_expr_cxt PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ StringInfo buf; /* output buffer to append to */ - List **params_list; /* exprs that will become remote Params */ + List **params_list; /* exprs that will become remote Params */ + int can_skip_cast; /* outer function can skip float8/numeric cast */ } deparse_expr_cxt; @@ -94,26 +95,37 @@ 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); +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_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); + deparse_expr_cxt *context); +static void mysql_deparse_relabel_type(RelabelType *node, + deparse_expr_cxt *context); static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context); static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context); -static void mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context); -static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, - deparse_expr_cxt *context); +static void mysql_deparse_array_expr(ArrayExpr *node, + deparse_expr_cxt *context); +static void mysql_print_remote_param(int paramindex, Oid paramtype, + int32 paramtypmod, + deparse_expr_cxt *context); static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, - deparse_expr_cxt *context); + 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, List *tlist, RelOptInfo *baserel); -static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root); +static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + Bitmapset *attrs_used, + List **retrieved_attrs, + List *tlist, RelOptInfo *baserel); +static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, + PlannerInfo *root); +static bool mysql_deparse_op_divide(Expr *node, deparse_expr_cxt *context); /* * Functions to construct string representation of a specific types. @@ -126,19 +138,19 @@ static void deparse_interval(StringInfo buf, Datum datum); 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. + * 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 = NULL; + ForeignTable *table; + const char *nspname = NULL; + const char *relname = NULL; + ListCell *lc; - /* obtain additional catalog information. */ + /* Obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); /* @@ -146,7 +158,7 @@ mysql_deparse_relation(StringInfo buf, Relation rel) */ foreach(lc, table->options) { - DefElem *def = (DefElem *) lfirst(lc); + DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "dbname") == 0) nspname = defGetString(def); @@ -163,40 +175,37 @@ mysql_deparse_relation(StringInfo buf, Relation rel) if (relname == NULL) relname = RelationGetRelationName(rel); - appendStringInfo(buf, "%s.%s", mysql_quote_identifier(nspname, '`'), mysql_quote_identifier(relname, '`')); + appendStringInfo(buf, "%s.%s", mysql_quote_identifier(nspname, '`'), + mysql_quote_identifier(relname, '`')); } static char * -mysql_quote_identifier(const char *s , char q) +mysql_quote_identifier(const char *str, char quotechar) { - char *result = palloc(strlen(s) * 2 + 3); - char *r = result; + char *result = palloc(strlen(str) * 2 + 3); + char *res = result; - *r++ = q; - while (*s) + *res++ = quotechar; + while (*str) { - if (*s == q) - *r++ = *s; - *r++ = *s; - s++; + if (*str == quotechar) + *res++ = *str; + *res++ = *str; + str++; } - *r++ = q; - *r++ = '\0'; + *res++ = quotechar; + *res++ = '\0'; + return result; } - /* - * Deparese SELECT statment + * Deparse SELECT statement */ void -mysql_deparse_select(StringInfo buf, - PlannerInfo *root, - RelOptInfo *baserel, - Bitmapset *attrs_used, - char *svr_table, - List **retrieved_attrs, - List *tlist) +mysql_deparse_select(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, + Bitmapset *attrs_used, char *svr_table, + List **retrieved_attrs, List *tlist) { RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); Relation rel; @@ -205,10 +214,14 @@ mysql_deparse_select(StringInfo buf, * 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 appendStringInfoString(buf, "SELECT "); - mysql_deparse_target_list(buf, root, baserel->relid, rel, attrs_used, + mysql_deparse_target_list(buf, root, baserel->relid, rel, attrs_used, retrieved_attrs, tlist, baserel); /* @@ -216,30 +229,35 @@ mysql_deparse_select(StringInfo buf, */ appendStringInfoString(buf, " FROM "); mysql_deparse_relation(buf, rel); + +#if PG_VERSION_NUM < 130000 heap_close(rel, NoLock); +#else + table_close(rel, NoLock); +#endif } /* - * deparse remote INSERT statement + * Deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void -mysql_deparse_insert(StringInfo buf, PlannerInfo *root, - Index rtindex, Relation rel, - List *targetAttrs) +mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, + Relation rel, List *targetAttrs) { - AttrNumber pindex; - bool first; - ListCell *lc; + ListCell *lc; appendStringInfoString(buf, "INSERT INTO "); mysql_deparse_relation(buf, rel); if (targetAttrs) { + AttrNumber pindex; + bool first; + appendStringInfoChar(buf, '('); first = true; @@ -280,7 +298,8 @@ 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); + appendStringInfo(sql, " WHERE table_schema = '%s' AND table_name = '%s'", + dbname, relname); } /* @@ -288,30 +307,59 @@ mysql_deparse_analyze(StringInfo sql, char *dbname, char *relname) * 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, - List *tlist, - RelOptInfo *baserel) +mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, + Relation rel, Bitmapset *attrs_used, + List **retrieved_attrs, + List *tlist, + RelOptInfo *baserel) { - TupleDesc tupdesc = RelationGetDescr(rel); - bool have_wholerow; bool first; - int i; - ListCell *cell; - /* If there's a whole-row reference, we'll need all the columns. */ - have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, - attrs_used); first = true; - - if (retrieved_attrs) + + *retrieved_attrs = NIL; + + if (baserel->reloptkind == RELOPT_JOINREL || + tlist != NULL || + baserel->reloptkind == RELOPT_UPPER_REL) + { + /* Pushdown target list */ + + ListCell *cell; + int i = 0; + + /* Set up context struct for recursion */ + deparse_expr_cxt context; + context.root = root; + context.foreignrel = baserel; + context.buf = buf; + context.params_list = NULL; + context.can_skip_cast = false; + foreach (cell, tlist) + { + Expr *expr = ((TargetEntry *)lfirst(cell))->expr; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + /* Deparse target list for push down */ + deparseExpr(expr, &context); + *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); + i++; + } + } + else { + TupleDesc tupdesc = RelationGetDescr(rel); + int i; + bool have_wholerow; + + /* If there's a whole-row reference, we'll need all the columns. */ + have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, + attrs_used); + /* Not pushdown target list */ - *retrieved_attrs = NIL; for (i = 1; i <= tupdesc->natts; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); @@ -333,34 +381,11 @@ mysql_deparse_target_list(StringInfo buf, } } } - else - { - /* Pushdown target list */ - - /* Set up context struct for recursion */ - deparse_expr_cxt context; - context.root = root; - context.foreignrel = baserel; - context.buf = buf; - context.params_list = NULL; - foreach (cell, tlist) - { - Expr *expr = ((TargetEntry *)lfirst(cell))->expr; - - if (!first) - appendStringInfoString(buf, ", "); - first = false; - - /* Deparse target list for push down */ - deparseExpr(expr, &context); - } - } /* Don't generate bad syntax if no undropped columns */ if (first) appendStringInfoString(buf, "NULL"); } - /* * Deparse WHERE clauses in given list of RestrictInfos and append them to buf. * @@ -376,12 +401,9 @@ mysql_deparse_target_list(StringInfo buf, * so Params and other-relation Vars should be replaced by dummy values. */ void -mysql_append_where_clause(StringInfo buf, - PlannerInfo *root, - RelOptInfo *baserel, - List *exprs, - bool is_first, - List **params) +mysql_append_where_clause(StringInfo buf, PlannerInfo *root, + RelOptInfo *baserel, List *exprs, bool is_first, + List **params) { deparse_expr_cxt context; ListCell *lc; @@ -394,6 +416,7 @@ mysql_append_where_clause(StringInfo buf, context.foreignrel = baserel; context.buf = buf; context.params_list = params; + context.can_skip_cast = false; foreach(lc, exprs) { @@ -413,18 +436,18 @@ mysql_append_where_clause(StringInfo buf, } } - /* - * 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. + * 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) +mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, + PlannerInfo *root) { RangeTblEntry *rte; - char *colname = NULL; - List *options; - ListCell *lc; + 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)); @@ -439,7 +462,7 @@ mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *r options = GetForeignColumnOptions(rte->relid, varattno); foreach(lc, options) { - DefElem *def = (DefElem *) lfirst(lc); + DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "column_name") == 0) { @@ -462,38 +485,35 @@ mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *r appendStringInfoString(buf, mysql_quote_identifier(colname, '`')); } - static void mysql_deparse_string(StringInfo buf, const char *val, bool isstr) { const char *valptr; - int i = -1; + int i = 0; - for (valptr = val; *valptr; valptr++) - { - char ch = *valptr; - i++; + if (isstr) + appendStringInfoChar(buf, '\''); - if (i == 0 && 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. + * 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 == '\"') + if ((ch == '{' && i == 0) || (ch == '}' && (i == (strlen(val) - 1))) || + ch == '\"') continue; - if (ch == ',' && isstr) + if (isstr && ch == ',') { - appendStringInfoChar(buf, '\''); - appendStringInfoChar(buf, ch); - appendStringInfoChar(buf, ' '); - appendStringInfoChar(buf, '\''); + appendStringInfoString(buf, "', '"); continue; } appendStringInfoChar(buf, ch); } + if (isstr) appendStringInfoChar(buf, '\''); } @@ -505,16 +525,21 @@ static void mysql_deparse_string_literal(StringInfo buf, const char *val) { const char *valptr; + appendStringInfoChar(buf, '\''); + for (valptr = val; *valptr; valptr++) { - char ch = *valptr; + char ch = *valptr; + if (SQL_STR_DOUBLE(ch, true)) - appendStringInfoChar(buf, ch); + appendStringInfoChar(buf, ch); appendStringInfoChar(buf, ch); } + appendStringInfoChar(buf, '\''); } + /* * Deparse given expression into context->buf. * @@ -528,9 +553,13 @@ mysql_deparse_string_literal(StringInfo buf, const char *val) static void deparseExpr(Expr *node, deparse_expr_cxt *context) { + bool outer_can_skip_cast = context->can_skip_cast; + if (node == NULL) return; + context->can_skip_cast = false; + switch (nodeTag(node)) { case T_Var: @@ -551,6 +580,7 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) #endif break; case T_FuncExpr: + context->can_skip_cast = outer_can_skip_cast; mysql_deparse_func_expr((FuncExpr *) node, context); break; case T_OpExpr: @@ -560,7 +590,8 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) mysql_deparse_distinct_expr((DistinctExpr *) node, context); break; case T_ScalarArrayOpExpr: - mysql_deparse_scalar_array_op_expr((ScalarArrayOpExpr *) node, context); + mysql_deparse_scalar_array_op_expr((ScalarArrayOpExpr *) node, + context); break; case T_RelabelType: mysql_deparse_relabel_type((RelabelType *) node, context); @@ -635,22 +666,20 @@ do { \ } } - /* - * deparse remote UPDATE statement + * 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) +mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, + Relation rel, List *targetAttrs, char *attname) { - AttrNumber pindex; - bool first; - ListCell *lc; + AttrNumber pindex; + bool first; + ListCell *lc; appendStringInfoString(buf, "UPDATE "); mysql_deparse_relation(buf, rel); @@ -660,7 +689,8 @@ mysql_deparse_update(StringInfo buf, PlannerInfo *root, first = true; foreach(lc, targetAttrs) { - int attnum = lfirst_int(lc); + int attnum = lfirst_int(lc); + if (attnum == 1) continue; @@ -672,21 +702,20 @@ mysql_deparse_update(StringInfo buf, PlannerInfo *root, appendStringInfo(buf, " = ?"); pindex++; } + appendStringInfo(buf, " WHERE %s = ?", attname); } - /* - * deparse remote DELETE statement + * 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) +mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, + Relation rel, char *name) { appendStringInfoString(buf, "DELETE FROM "); mysql_deparse_relation(buf, rel); @@ -710,17 +739,18 @@ mysql_deparse_var(Var *node, deparse_expr_cxt *context) node->varlevelsup == 0) { /* Var belongs to foreign table */ - mysql_deparse_column_ref(buf, node->varno, node->varattno, context->root); + mysql_deparse_column_ref(buf, node->varno, node->varattno, + context->root); } else { /* Treat like a Param */ if (context->params_list) { - int pindex = 0; - ListCell *lc; + int pindex = 0; + ListCell *lc; - /* find its index in params_list */ + /* Find its index in params_list */ foreach(lc, *context->params_list) { pindex++; @@ -729,16 +759,16 @@ mysql_deparse_var(Var *node, deparse_expr_cxt *context) } if (lc == NULL) { - /* not in list, so add it */ + /* 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); + mysql_print_remote_param(pindex, node->vartype, node->vartypmod, + context); } else - { - mysql_print_remote_placeholder(node->vartype, node->vartypmod, context); - } + mysql_print_remote_placeholder(node->vartype, node->vartypmod, + context); } } @@ -750,10 +780,10 @@ mysql_deparse_var(Var *node, deparse_expr_cxt *context) static void mysql_deparse_const(Const *node, deparse_expr_cxt *context) { - StringInfo buf = context->buf; - Oid typoutput; - bool typIsVarlena; - char *extval; + StringInfo buf = context->buf; + Oid typoutput; + bool typIsVarlena; + char *extval; if (node->constisnull) { @@ -761,8 +791,7 @@ mysql_deparse_const(Const *node, deparse_expr_cxt *context) return; } - getTypeOutputInfo(node->consttype, - &typoutput, &typIsVarlena); + getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); switch (node->consttype) { @@ -775,6 +804,7 @@ mysql_deparse_const(Const *node, deparse_expr_cxt *context) 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(). @@ -805,23 +835,24 @@ mysql_deparse_const(Const *node, deparse_expr_cxt *context) 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 + 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; + 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. * @@ -835,10 +866,10 @@ mysql_deparse_param(Param *node, deparse_expr_cxt *context) { if (context->params_list) { - int pindex = 0; - ListCell *lc; + int pindex = 0; + ListCell *lc; - /* find its index in params_list */ + /* Find its index in params_list */ foreach(lc, *context->params_list) { pindex++; @@ -847,17 +878,17 @@ mysql_deparse_param(Param *node, deparse_expr_cxt *context) } if (lc == NULL) { - /* not in list, so add it */ + /* 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); + mysql_print_remote_param(pindex, node->paramtype, node->paramtypmod, + context); } else - { - mysql_print_remote_placeholder(node->paramtype, node->paramtypmod, context); - } + mysql_print_remote_placeholder(node->paramtype, node->paramtypmod, + context); } /* @@ -901,7 +932,11 @@ mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context) { 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, ']'); @@ -911,30 +946,41 @@ mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context) } /* - * This possible that name of function in PostgreSQL and - * mysql differ, so return the mysql equelent function name + * This is possible that the name of function in PostgreSQL and mysql differ, + * so return the mysql eloquent function name. */ -static char* +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; - HeapTuple proctup; - Form_pg_proc procform; - const char *proname; - bool first; - ListCell *arg; + StringInfo buf = context->buf; + HeapTuple proctup; + Form_pg_proc procform; + const char *proname; + bool first; + ListCell *arg; + bool can_skip_cast = false; + + /* + * 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; + } /* * Normal function: display as proname(args). @@ -942,6 +988,7 @@ mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(node->funcid)); if (!HeapTupleIsValid(proctup)) elog(ERROR, "cache lookup failed for function %u", node->funcid); + procform = (Form_pg_proc) GETSTRUCT(proctup); /* Translate PostgreSQL function into mysql function */ @@ -958,6 +1005,10 @@ mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) ArrayExpr *anode; bool swt_arg; node = lfirst(arg); + if (IsA(node, ArrayCoerceExpr)) + { + node = (Expr *)((ArrayCoerceExpr *)node)->arg; + } Assert(nodeTag(node)==T_ArrayExpr); anode = (ArrayExpr *)node; appendStringInfoString(buf, "MATCH ("); @@ -1002,28 +1053,145 @@ mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) } appendStringInfoChar(buf, ')'); } + ReleaseSysCache(proctup); + + return; } - else + + /* remove cast function if parent function is can handle without cast */ + if (context->can_skip_cast == true && + (strcmp(NameStr(procform->proname), "float8") == 0 || strcmp(NameStr(procform->proname), "numeric") == 0)) { - /* Deparse the function name ... */ - appendStringInfo(buf, "%s(", proname); - /* ... and all the arguments */ - first = true; - foreach(arg, node->args) - { - if (!first) - appendStringInfoString(buf, ", "); - deparseExpr((Expr *) lfirst(arg), context); - first = false; - } - appendStringInfoChar(buf, ')'); + ReleaseSysCache(proctup); + arg = list_head(node->args); + context->can_skip_cast = false; + deparseExpr((Expr *) lfirst(arg), context); + return; } + /* inner function can skip cast if any */ + if (strcmp(NameStr(procform->proname), "sqrt") == 0 || strcmp(NameStr(procform->proname), "log") == 0) + can_skip_cast = true; + + /* Deparse the function name ... */ + appendStringInfo(buf, "%s(", proname); + ReleaseSysCache(proctup); + + /* ... and all the arguments */ + first = true; + foreach(arg, node->args) + { + if (!first) + appendStringInfoString(buf, ", "); + + if (can_skip_cast) + context->can_skip_cast = true; + deparseExpr((Expr *) lfirst(arg), context); + first = false; + } + appendStringInfoChar(buf, ')'); } /* - * Deparse given operator expression. To avoid problems around + * In Postgres, with divide operand '/', the results is integer with + * truncates which different with mysql. + * In mysql, with divide operand '/', the results is the scale + * of the first operand plus the value of the div_precision_increment + * system variable (which is 4 by default) + * + * To make Postgres consistence with mysql in this case, we will do follow: + * + Check operands recursively. + * + If all operands are non floating point type, change '/' to 'DIV'. + */ +static bool +mysql_deparse_op_divide(Expr *node, deparse_expr_cxt *context) +{ + bool is_convert = true; + + if (node == NULL) + return false; + + switch (nodeTag(node)) + { + case T_Var: + { + Var *var = (Var *) node; + RangeTblEntry *rte; + PlannerInfo *root = context->root; + int col_type = 0; + int varno = var->varno; + int varattno = var->varattno; + + /* 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); + + col_type = get_atttype(rte->relid, varattno); + is_convert = IS_INTEGER_TYPE(col_type); + } + break; + case T_Const: + { + Const *c = (Const *) node; + is_convert = IS_INTEGER_TYPE(c->consttype); + } + break; + case T_FuncExpr: + { + FuncExpr *f = (FuncExpr *) node; + is_convert = IS_INTEGER_TYPE(f->funcresulttype); + } + break; + case T_OpExpr: + { + HeapTuple tuple; + Form_pg_operator form; + char oprkind; + ListCell *arg; + + OpExpr *op = (OpExpr *) node; + + /* Retrieve information about the operator from system catalog. */ + tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(op->opno)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator %u", op->opno); + form = (Form_pg_operator) GETSTRUCT(tuple); + oprkind = form->oprkind; + + /* Sanity check. */ + Assert((oprkind == 'r' && list_length(op->args) == 1) || + (oprkind == 'l' && list_length(op->args) == 1) || + (oprkind == 'b' && list_length(op->args) == 2)); + /* Check left operand. */ + if (oprkind == 'r' || oprkind == 'b') + { + arg = list_head(op->args); + is_convert = mysql_deparse_op_divide(lfirst(arg), context); + } + + /* If left operand is ok, going to check right operand. */ + if (is_convert && (oprkind == 'l' || oprkind == 'b')) + { + arg = list_tail(op->args); + is_convert = is_convert ? mysql_deparse_op_divide(lfirst(arg), context) : false; + } + ReleaseSysCache(tuple); + } + break; + default: + is_convert = false; + elog(ERROR, "unsupported expression type for check type operand for convert divide : %d", + (int) nodeTag(node)); + break; + } + return is_convert; +} + +/* + * Deparse given operator expression. To avoid problems around * priority of operations, we always parenthesize the arguments. */ static void @@ -1034,11 +1202,13 @@ mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context) Form_pg_operator form; char oprkind; ListCell *arg; + bool is_convert = false; /* Flag to determine that convert '/' to 'DIV' or not */ /* 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; @@ -1047,6 +1217,11 @@ mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context) (oprkind == 'l' && list_length(node->args) == 1) || (oprkind == 'b' && list_length(node->args) == 2)); + cur_opname = NameStr(form->oprname); + /* If opname is '/' check all type of operands recursively */ + if (form->oprnamespace == PG_CATALOG_NAMESPACE && strcmp(cur_opname, "/") == 0) + is_convert = mysql_deparse_op_divide((Expr *)node, context); + /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); @@ -1058,8 +1233,14 @@ mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context) appendStringInfoChar(buf, ' '); } - /* Deparse operator name. */ - mysql_deparse_operator_name(buf, form); + /* + * Deparse operator name. + * If all operands are non floating point type, change '/' to 'DIV'. + */ + if (is_convert) + appendStringInfoString(buf, "DIV"); + else + mysql_deparse_operator_name(buf, form); /* Deparse right operand. */ if (oprkind == 'l' || oprkind == 'b') @@ -1096,41 +1277,23 @@ mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform) 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); - } } } @@ -1156,17 +1319,18 @@ mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context) * around priority of operations, we always parenthesize the arguments. */ static void -mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *context) +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; + 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)); @@ -1177,29 +1341,56 @@ mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *co /* 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. */ + /* + * Deparse right operand to check type of argument first. + * For an fixed-len array, we use IN clause, e.g. ANY(ARRAY[1, 2, 3]). + * For an variable-len array, we use FIND_IN_SET clause, e.g. ANY(ARRAY(SELECT * FROM table), + * because we can bind a string representation of array. + */ arg2 = lsecond(node->args); - switch (nodeTag((Node*)arg2)) + if (nodeTag((Node*)arg2) == T_Const) + { + /* Deparse left operand. */ + arg1 = linitial(node->args); + deparseExpr(arg1, context); + appendStringInfoChar(buf, ' '); + + if (strcmp(opname, "<>") == 0) + appendStringInfo(buf, " NOT "); + + /* Deparse operator name plus decoration. */ + appendStringInfo(buf, " IN ("); + } + else + { + if (strcmp(opname, "<>") == 0) + appendStringInfo(buf, " NOT "); + + /* Use FIND_IN_SET for binding the array parameter */ + appendStringInfo(buf, " FIND_IN_SET ("); + + /* Deparse left operand. */ + arg1 = linitial(node->args); + deparseExpr(arg1, context); + appendStringInfoChar(buf, ','); + } + + switch (nodeTag((Node *) arg2)) { case T_Const: - { - Const *c = (Const*)arg2; - if (!c->constisnull) { - getTypeOutputInfo(c->consttype, - &typoutput, &typIsVarlena); + 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) @@ -1213,18 +1404,13 @@ mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *co break; } } - else - { - appendStringInfoString(buf, " NULL"); - return; - } - } - break; + break; default: deparseExpr(arg2, context); break; } appendStringInfoChar(buf, ')'); + ReleaseSysCache(tuple); } @@ -1249,7 +1435,7 @@ 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; + ListCell *lc; switch (node->boolop) { @@ -1260,7 +1446,8 @@ mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context) op = "OR"; break; case NOT_EXPR: - appendStringInfoString(buf, "(NOT "); + appendStringInfoChar(buf, '('); + appendStringInfoString(buf, "NOT "); deparseExpr(linitial(node->args), context); appendStringInfoChar(buf, ')'); return; @@ -1289,9 +1476,10 @@ mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context) appendStringInfoChar(buf, '('); deparseExpr(node->arg, context); if (node->nulltesttype == IS_NULL) - appendStringInfoString(buf, " IS NULL)"); + appendStringInfoString(buf, " IS NULL"); else - appendStringInfoString(buf, " IS NOT NULL)"); + appendStringInfoString(buf, " IS NOT NULL"); + appendStringInfoChar(buf, ')'); } /* @@ -1325,7 +1513,7 @@ mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context) */ static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, - deparse_expr_cxt *context) + deparse_expr_cxt *context) { StringInfo buf = context->buf; @@ -1334,9 +1522,10 @@ mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, - deparse_expr_cxt *context) + deparse_expr_cxt *context) { StringInfo buf = context->buf; + appendStringInfo(buf, "(SELECT null)"); } @@ -1364,7 +1553,6 @@ is_builtin(Oid oid) return (oid < FirstBootstrapObjectId); } - /* * Check if expression is safe to execute remotely, and return true if so. * @@ -1373,13 +1561,12 @@ is_builtin(Oid oid) * 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. + * 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_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt) { bool check_type = true; @@ -1395,6 +1582,7 @@ foreign_expr_walker(Node *node, /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; inner_cxt.state = FDW_COLLATE_NONE; + inner_cxt.can_skip_cast = false; switch (nodeTag(node)) { @@ -1406,7 +1594,7 @@ foreign_expr_walker(Node *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, ie it's not safe for it to have a + * Param's collation, i.e. it's not safe for it to have a * non-default collation. */ if (var->varno == glob_cxt->foreignrel->relid && @@ -1434,8 +1622,8 @@ foreign_expr_walker(Node *node, Const *c = (Const *) node; /* - * If the constant has nondefault collation, either it's of a - * non-builtin type, or it reflects folding of a CollateExpr; + * 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 && @@ -1469,7 +1657,7 @@ foreign_expr_walker(Node *node, #else case T_SubscriptingRef: { - SubscriptingRef *ar = (SubscriptingRef *) node; + SubscriptingRef *ar = (SubscriptingRef *) node; #endif /* Assignment should not be in restrictions. */ @@ -1509,6 +1697,7 @@ foreign_expr_walker(Node *node, { FuncExpr *fe = (FuncExpr *) node; char *opername = NULL; + Node *node_arg = (Node *)fe->args; /* * If function used by the expression is not built-in, it @@ -1524,13 +1713,40 @@ foreign_expr_walker(Node *node, ReleaseSysCache(tuple); /* pushed down to mysql */ - if (!is_builtin(fe->funcid) && strcmp(opername, "match_against") != 0) + if (!is_builtin(fe->funcid) && + strcmp(opername, "float8") != 0 && + strcmp(opername, "numeric") != 0 && + strcmp(opername, "log") != 0 && + strcmp(opername, "match_against") != 0) return false; + /* inner function can skip float cast if any */ + if (strcmp(opername, "sqrt") == 0 || strcmp(opername, "log") == 0) + inner_cxt.can_skip_cast = true; + + /* Accept type cast functions if outer is specific functions */ + if (strcmp(opername, "float8") == 0 || strcmp(opername, "numeric") == 0) + { + if (outer_cxt->can_skip_cast == false) + return false; + } + + if (strcmp(opername, "match_against") == 0 && IsA(node_arg, List)) + { + List *l = (List *) node_arg; + ListCell *lc = list_head(l); + + node_arg = (Node *)lfirst(lc); + if (IsA(node_arg, ArrayCoerceExpr)) + { + node_arg = (Node *)((ArrayCoerceExpr *)node_arg)->arg; + } + } + /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *) fe->args, + if (!foreign_expr_walker((Node *) node_arg, glob_cxt, &inner_cxt)) return false; @@ -1720,6 +1936,9 @@ foreign_expr_walker(Node *node, List *l = (List *) node; ListCell *lc; + /* inherit can_skip_cast flag */ + inner_cxt.can_skip_cast = outer_cxt->can_skip_cast; + /* * Recurse to component subexpressions. */ @@ -1810,29 +2029,154 @@ foreign_expr_walker(Node *node, * Returns true if given expr is safe to evaluate on the foreign server. */ bool -mysql_is_foreign_expr(PlannerInfo *root, - RelOptInfo *baserel, - Expr *expr) +mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { - foreign_glob_cxt glob_cxt; - foreign_loc_cxt loc_cxt; - - /* - * Check that the expression consists of nodes that are safe to execute - * remotely. - */ - glob_cxt.root = root; - glob_cxt.foreignrel = baserel; - loc_cxt.collation = InvalidOid; - loc_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt)) - return false; - - /* Expressions examined here should be boolean, ie noncollatable */ - Assert(loc_cxt.collation == InvalidOid); - Assert(loc_cxt.state == FDW_COLLATE_NONE); - - /* OK to evaluate on the remote server */ - return true; + foreign_glob_cxt glob_cxt; + foreign_loc_cxt loc_cxt; + + /* + * Check that the expression consists of nodes that are safe to execute + * remotely. + */ + glob_cxt.root = root; + glob_cxt.foreignrel = baserel; + loc_cxt.collation = InvalidOid; + loc_cxt.state = FDW_COLLATE_NONE; + loc_cxt.can_skip_cast = false; + if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt)) + return false; + + /* Expressions examined here should be boolean, ie noncollatable */ + Assert(loc_cxt.collation == InvalidOid); + Assert(loc_cxt.state == FDW_COLLATE_NONE); + + /* OK to evaluate on the remote server */ + return true; +} + +/***************************************************************************** + * Check clauses for immutable functions + *****************************************************************************/ + +/* + * contain_immutable_functions + * Recursively search for immutable functions within a clause. + * + * Returns true if any immutable function (or operator implemented by a + * immutable function) is found. + * + * We will recursively look into TargetEntry exprs. + */ +static bool +mysql_contain_immutable_functions(Node *clause) +{ + return mysql_contain_immutable_functions_walker(clause, NULL); +} + +static bool +mysql_contain_immutable_functions_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + /* Check for mutable functions in node itself */ + if (nodeTag(node) == T_FuncExpr) + { + FuncExpr *expr = (FuncExpr *) node; + if (func_volatile(expr->funcid) == PROVOLATILE_IMMUTABLE) + return true; + } + + /* + * It should be safe to treat MinMaxExpr as immutable, because it will + * depend on a non-cross-type btree comparison function, and those should + * always be immutable. Treating XmlExpr as immutable is more dubious, + * and treating CoerceToDomain as immutable is outright dangerous. But we + * have done so historically, and changing this would probably cause more + * problems than it would fix. In practice, if you have a non-immutable + * domain constraint you are in for pain anyhow. + */ + + /* Recurse to check arguments */ + if (IsA(node, Query)) + { + /* Recurse into subselects */ + return query_tree_walker((Query *) node, + mysql_contain_immutable_functions_walker, + context, 0); + } + return expression_tree_walker(node, mysql_contain_immutable_functions_walker, + context); } +/* + * Returns true if given tlist is safe to evaluate on the foreign server. + */ +bool mysql_is_foreign_function_tlist(PlannerInfo *root, + RelOptInfo *baserel, + List *tlist) +{ + foreign_glob_cxt glob_cxt; + foreign_loc_cxt loc_cxt; + ListCell *lc; + bool is_contain_function; + + if (!(baserel->reloptkind == RELOPT_BASEREL || + baserel->reloptkind == RELOPT_OTHER_MEMBER_REL)) + return false; + + /* + * Check that the expression consists of any immutable function. + */ + is_contain_function = false; + foreach(lc, tlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (mysql_contain_immutable_functions((Node *) tle->expr)) + { + is_contain_function = true; + break; + } + } + + if (!is_contain_function) + return false; + + /* + * Check that the expression consists of nodes that are safe to execute + * remotely. + */ + foreach(lc, tlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + glob_cxt.root = root; + glob_cxt.foreignrel = baserel; + loc_cxt.collation = InvalidOid; + loc_cxt.state = FDW_COLLATE_NONE; + loc_cxt.can_skip_cast = false; + + if (!foreign_expr_walker((Node *) tle->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 *) tle->expr)) + return false; + } + + /* OK for the target list with functions to evaluate on the remote server */ + return true; +} diff --git a/expected/connection_validation.out b/expected/connection_validation.out new file mode 100644 index 0000000..9fdaa6e --- /dev/null +++ b/expected/connection_validation.out @@ -0,0 +1,69 @@ +\set ECHO none +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +-- Create foreign table and Validate +--Testcase 4: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 5: +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. +ALTER SERVER mysql_svr OPTIONS (SET host 'localhos'); +--Testcase 6: +SELECT * FROM f_mysql_test ORDER BY 1, 2; +ERROR: failed to connect to MySQL: Unknown MySQL server host 'localhos' (2) +-- Set the correct host-name, next operation should succeed. +ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST); +--Testcase 7: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 +(1 row) + +-- Alter USER MAPPING option. +-- Set wrong user-name and password, next operation should fail. +ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr + OPTIONS (SET username 'foo1', SET password 'bar1'); +--Testcase 8: +SELECT * FROM f_mysql_test ORDER BY 1, 2; +ERROR: failed to connect to MySQL: Access denied for user 'foo1'@'localhost' (using password: YES) +-- 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); +--Testcase 9: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 +(1 row) + +-- Cleanup +--Testcase 10: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 11: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 12: +DROP SERVER mysql_svr; +--Testcase 13: +DROP EXTENSION mysql_fdw; diff --git a/expected/dml.out b/expected/dml.out new file mode 100644 index 0000000..9f70c9b --- /dev/null +++ b/expected/dml.out @@ -0,0 +1,291 @@ +\set ECHO none +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +-- Create foreign tables +--Testcase 4: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 5: +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'); +--Testcase 6: +CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) + SERVER mysql_svr OPTIONS (table_name 'student'); +--Testcase 7: +CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'numbers'); +--Testcase 8: +CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'nosuchtable'); +--Testcase 9: +CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress2', table_name 'numbers'); +--Testcase 10: +CREATE FOREIGN TABLE fdw126_ft6(stu_id int, stu_name varchar(255)) + SERVER mysql_svr OPTIONS (table_name 'mysql_fdw_regress1.student'); +--Testcase 11: +CREATE FOREIGN TABLE f_empdata(emp_id int, emp_dat bytea) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'empdata'); +--Testcase 40: +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. +--Testcase 12: +INSERT INTO f_empdata VALUES (1, decode ('01234567', 'hex')); +--Testcase 13: +SELECT count(*) FROM f_empdata ORDER BY 1; + count +------- + 1 +(1 row) + +--Testcase 14: +SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; + emp_id | emp_dat +--------+------------ + 1 | \x01234567 +(1 row) + +--Testcase 15: +UPDATE f_empdata SET emp_dat = decode ('0123', 'hex'); +--Testcase 16: +SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; + emp_id | emp_dat +--------+--------- + 1 | \x0123 +(1 row) + +-- 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. +--Testcase 17: +INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); +--Testcase 18: +UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1; +--Testcase 19: +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. +--Testcase 20: +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. +--Testcase 21: +INSERT INTO fdw126_ft2 VALUES(2, 'Two'); +ERROR: 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. +--Testcase 22: +INSERT INTO fdw126_ft3 VALUES(1, 'One'); +ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation +--Testcase 23: +SELECT a, b FROM fdw126_ft3 ORDER BY 1, 2 LIMIT 1; + a | b +---+--- +(0 rows) + +--Testcase 24: +UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1; +ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation +--Testcase 25: +DELETE FROM fdw126_ft3 WHERE a = 1; +ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation +-- Check when table_name is given in database.table form in foreign table +-- should error out as syntax error +--Testcase 26: +INSERT INTO fdw126_ft6 VALUES(1, 'One'); +ERROR: failed to execute the MySQL query: +You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.student' at line 1 +-- Perform the ANALYZE on the foreign table which is not present on the remote +-- side. Should not crash. +-- The database is present but not the target table. +ANALYZE fdw126_ft4; +ERROR: relation mysql_fdw_regress1.nosuchtable does not exist +-- The database itself is not present. +ANALYZE fdw126_ft5; +ERROR: relation mysql_fdw_regress2.numbers does not exist +-- Some other variant of analyze and vacuum. +-- when table exists, should give skip-warning +VACUUM f_empdata; +WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables +VACUUM FULL f_empdata; +WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables +VACUUM FREEZE f_empdata; +WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables +ANALYZE f_empdata; +WARNING: skipping "f_empdata" --- cannot analyze this foreign table +ANALYZE f_empdata(emp_id); +WARNING: skipping "f_empdata" --- cannot analyze this foreign table +VACUUM ANALYZE f_empdata; +WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables +-- Verify the before update trigger which modifies the column value which is not +-- part of update statement. +--Testcase 41: +CREATE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ +BEGIN + NEW.stu_name := NEW.stu_name || ' trigger updated!'; + RETURN NEW; + END +$$ language plpgsql; +--Testcase 42: +CREATE TRIGGER before_row_update_trig +BEFORE UPDATE ON fdw126_ft1 +FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); +--Testcase 43: +INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); +--Testcase 44: +EXPLAIN (verbose, costs off) +UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw126_ft1 + -> Foreign Scan on public.fdw126_ft1 + Output: stu_id, stu_name, 201, stu_id, fdw126_ft1.* + Local server startup cost: 10 + Remote query: SELECT `stu_id`, `stu_name`, `stu_dept` FROM `mysql_fdw_regress1`.`student` WHERE ((`stu_id` = 1)) FOR UPDATE +(5 rows) + +--Testcase 45: +UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; +--Testcase 46: +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. +--Testcase 47: +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. +--Testcase 48: +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; +--Testcase 49: +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. +--Testcase 50: +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; +--Testcase 51: +CREATE TRIGGER before_row_update_trig1 +BEFORE UPDATE ON fdw193_ft1 +FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); +--Testcase 52: +INSERT INTO fdw193_ft1 VALUES('aa', 'One', 101); +--Testcase 53: +EXPLAIN (verbose, costs off) +UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw193_ft1 + -> Foreign Scan on public.fdw193_ft1 + Output: stu_id, stu_name, 201, stu_id, fdw193_ft1.* + Local server startup cost: 10 + Remote query: SELECT `stu_id`, `stu_name`, `stu_dept` FROM `mysql_fdw_regress1`.`student1` WHERE ((`stu_id` = 'aa')) FOR UPDATE +(5 rows) + +--Testcase 54: +UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; +--Testcase 55: +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. +--Testcase 56: +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; +--Testcase 57: +UPDATE fdw193_ft1 SET stu_dept = 301 WHERE stu_id = 'aa'; +ERROR: row identifier column update is not supported +-- Verify the NULL assignment scenario. +--Testcase 58: +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; +--Testcase 59: +UPDATE fdw193_ft1 SET stu_dept = 401 WHERE stu_id = 'aa'; +ERROR: row identifier column update is not supported +-- Cleanup +--Testcase 27: +DELETE FROM fdw126_ft1; +--Testcase 28: +DELETE FROM f_empdata; +--Testcase 60: +DELETE FROM fdw193_ft1; +--Testcase 29: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 30: +DROP FOREIGN TABLE fdw126_ft1; +--Testcase 31: +DROP FOREIGN TABLE fdw126_ft2; +--Testcase 32: +DROP FOREIGN TABLE fdw126_ft3; +--Testcase 33: +DROP FOREIGN TABLE fdw126_ft4; +--Testcase 34: +DROP FOREIGN TABLE fdw126_ft5; +--Testcase 35: +DROP FOREIGN TABLE fdw126_ft6; +--Testcase 36: +DROP FOREIGN TABLE f_empdata; +--Testcase 61: +DROP FOREIGN TABLE fdw193_ft1; +--Testcase 62: +DROP FUNCTION before_row_update_func(); +--Testcase 37: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 38: +DROP SERVER mysql_svr; +--Testcase 39: +DROP EXTENSION mysql_fdw; diff --git a/expected/mysql_fdw.out b/expected/mysql_fdw.out deleted file mode 100644 index aca67e4..0000000 --- a/expected/mysql_fdw.out +++ /dev/null @@ -1,377 +0,0 @@ -\c postgres postgres -CREATE EXTENSION mysql_fdw; -CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw; -CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username 'foo', password 'bar'); -CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'department'); -CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee'); -CREATE FOREIGN TABLE empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'empdata'); -CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb', table_name 'numbers'); -SELECT * FROM department LIMIT 10; - department_id | department_name ----------------+----------------- -(0 rows) - -SELECT * FROM employee LIMIT 10; - emp_id | emp_name | emp_dept_id ---------+----------+------------- -(0 rows) - -SELECT * FROM empdata LIMIT 10; - emp_id | emp_dat ---------+--------- -(0 rows) - -INSERT INTO department VALUES(generate_series(1,100), 'dept - ' || generate_series(1,100)); -INSERT INTO employee VALUES(generate_series(1,100), 'emp - ' || generate_series(1,100), generate_series(1,100)); -INSERT INTO empdata VALUES(1, decode ('01234567', 'hex')); -insert into numbers values(1, 'One'); -insert into numbers values(2, 'Two'); -insert into numbers values(3, 'Three'); -insert into numbers values(4, 'Four'); -insert into numbers values(5, 'Five'); -insert into numbers values(6, 'Six'); -insert into numbers values(7, 'Seven'); -insert into numbers values(8, 'Eight'); -insert into numbers values(9, 'Nine'); -SELECT count(*) FROM department; - count -------- - 100 -(1 row) - -SELECT count(*) FROM employee; - count -------- - 100 -(1 row) - -SELECT count(*) FROM empdata; - count -------- - 1 -(1 row) - -EXPLAIN (COSTS FALSE) SELECT * FROM department d, employee e WHERE d.department_id = e.emp_dept_id LIMIT 10; - QUERY PLAN --------------------------------------------------------- - Limit - -> Nested Loop - Join Filter: (d.department_id = e.emp_dept_id) - -> Foreign Scan on department d - -> Materialize - -> Foreign Scan on employee e -(6 rows) - -EXPLAIN (COSTS FALSE) SELECT * FROM department d, employee e WHERE d.department_id IN (SELECT department_id FROM department) LIMIT 10; - QUERY PLAN -------------------------------------------------------------------------- - Limit - -> Nested Loop - -> Nested Loop Semi Join - Join Filter: (d.department_id = department.department_id) - -> Foreign Scan on department d - -> Materialize - -> Foreign Scan on department - -> Materialize - -> Foreign Scan on employee e -(9 rows) - -SELECT * FROM department d, employee e WHERE d.department_id = e.emp_dept_id LIMIT 10; - department_id | department_name | emp_id | emp_name | emp_dept_id ----------------+-----------------+--------+----------+------------- - 1 | dept - 1 | 1 | emp - 1 | 1 - 2 | dept - 2 | 2 | emp - 2 | 2 - 3 | dept - 3 | 3 | emp - 3 | 3 - 4 | dept - 4 | 4 | emp - 4 | 4 - 5 | dept - 5 | 5 | emp - 5 | 5 - 6 | dept - 6 | 6 | emp - 6 | 6 - 7 | dept - 7 | 7 | emp - 7 | 7 - 8 | dept - 8 | 8 | emp - 8 | 8 - 9 | dept - 9 | 9 | emp - 9 | 9 - 10 | dept - 10 | 10 | emp - 10 | 10 -(10 rows) - -SELECT * FROM department d, employee e WHERE d.department_id IN (SELECT department_id FROM department) LIMIT 10; - department_id | department_name | emp_id | emp_name | emp_dept_id ----------------+-----------------+--------+----------+------------- - 1 | dept - 1 | 1 | emp - 1 | 1 - 1 | dept - 1 | 2 | emp - 2 | 2 - 1 | dept - 1 | 3 | emp - 3 | 3 - 1 | dept - 1 | 4 | emp - 4 | 4 - 1 | dept - 1 | 5 | emp - 5 | 5 - 1 | dept - 1 | 6 | emp - 6 | 6 - 1 | dept - 1 | 7 | emp - 7 | 7 - 1 | dept - 1 | 8 | emp - 8 | 8 - 1 | dept - 1 | 9 | emp - 9 | 9 - 1 | dept - 1 | 10 | emp - 10 | 10 -(10 rows) - -SELECT * FROM empdata; - emp_id | emp_dat ---------+------------ - 1 | \x01234567 -(1 row) - -DELETE FROM employee WHERE emp_id = 10; -SELECT COUNT(*) FROM department LIMIT 10; - count -------- - 100 -(1 row) - -SELECT COUNT(*) FROM employee WHERE emp_id = 10; - count -------- - 0 -(1 row) - -UPDATE employee SET emp_name = 'Updated emp' WHERE emp_id = 20; -SELECT emp_id, emp_name FROM employee WHERE emp_name like 'Updated emp'; - emp_id | emp_name ---------+------------- - 20 | Updated emp -(1 row) - -UPDATE empdata SET emp_dat = decode ('0123', 'hex'); -SELECT * FROM empdata; - emp_id | emp_dat ---------+--------- - 1 | \x0123 -(1 row) - -SELECT * FROM employee LIMIT 10; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 1 | emp - 1 | 1 - 2 | emp - 2 | 2 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 - 6 | emp - 6 | 6 - 7 | emp - 7 | 7 - 8 | emp - 8 | 8 - 9 | emp - 9 | 9 - 11 | emp - 11 | 11 -(10 rows) - -SELECT * FROM employee WHERE emp_id IN (1); - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 1 | emp - 1 | 1 -(1 row) - -SELECT * FROM employee WHERE emp_id IN (1,3,4,5); - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 1 | emp - 1 | 1 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 -(4 rows) - -SELECT * FROM employee WHERE emp_id IN (10000,1000); - emp_id | emp_name | emp_dept_id ---------+----------+------------- -(0 rows) - -SELECT * FROM employee WHERE emp_id NOT IN (1) LIMIT 5; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 2 | emp - 2 | 2 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 - 6 | emp - 6 | 6 -(5 rows) - -SELECT * FROM employee WHERE emp_id NOT IN (1,3,4,5) LIMIT 5; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 2 | emp - 2 | 2 - 6 | emp - 6 | 6 - 7 | emp - 7 | 7 - 8 | emp - 8 | 8 - 9 | emp - 9 | 9 -(5 rows) - -SELECT * FROM employee WHERE emp_id NOT IN (10000,1000) LIMIT 5; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 1 | emp - 1 | 1 - 2 | emp - 2 | 2 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 -(5 rows) - -SELECT * FROM employee WHERE emp_id NOT IN (SELECT emp_id FROM employee WHERE emp_id IN (1,10)); - emp_id | emp_name | emp_dept_id ---------+-------------+------------- - 2 | emp - 2 | 2 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 - 6 | emp - 6 | 6 - 7 | emp - 7 | 7 - 8 | emp - 8 | 8 - 9 | emp - 9 | 9 - 11 | emp - 11 | 11 - 12 | emp - 12 | 12 - 13 | emp - 13 | 13 - 14 | emp - 14 | 14 - 15 | emp - 15 | 15 - 16 | emp - 16 | 16 - 17 | emp - 17 | 17 - 18 | emp - 18 | 18 - 19 | emp - 19 | 19 - 20 | Updated emp | 20 - 21 | emp - 21 | 21 - 22 | emp - 22 | 22 - 23 | emp - 23 | 23 - 24 | emp - 24 | 24 - 25 | emp - 25 | 25 - 26 | emp - 26 | 26 - 27 | emp - 27 | 27 - 28 | emp - 28 | 28 - 29 | emp - 29 | 29 - 30 | emp - 30 | 30 - 31 | emp - 31 | 31 - 32 | emp - 32 | 32 - 33 | emp - 33 | 33 - 34 | emp - 34 | 34 - 35 | emp - 35 | 35 - 36 | emp - 36 | 36 - 37 | emp - 37 | 37 - 38 | emp - 38 | 38 - 39 | emp - 39 | 39 - 40 | emp - 40 | 40 - 41 | emp - 41 | 41 - 42 | emp - 42 | 42 - 43 | emp - 43 | 43 - 44 | emp - 44 | 44 - 45 | emp - 45 | 45 - 46 | emp - 46 | 46 - 47 | emp - 47 | 47 - 48 | emp - 48 | 48 - 49 | emp - 49 | 49 - 50 | emp - 50 | 50 - 51 | emp - 51 | 51 - 52 | emp - 52 | 52 - 53 | emp - 53 | 53 - 54 | emp - 54 | 54 - 55 | emp - 55 | 55 - 56 | emp - 56 | 56 - 57 | emp - 57 | 57 - 58 | emp - 58 | 58 - 59 | emp - 59 | 59 - 60 | emp - 60 | 60 - 61 | emp - 61 | 61 - 62 | emp - 62 | 62 - 63 | emp - 63 | 63 - 64 | emp - 64 | 64 - 65 | emp - 65 | 65 - 66 | emp - 66 | 66 - 67 | emp - 67 | 67 - 68 | emp - 68 | 68 - 69 | emp - 69 | 69 - 70 | emp - 70 | 70 - 71 | emp - 71 | 71 - 72 | emp - 72 | 72 - 73 | emp - 73 | 73 - 74 | emp - 74 | 74 - 75 | emp - 75 | 75 - 76 | emp - 76 | 76 - 77 | emp - 77 | 77 - 78 | emp - 78 | 78 - 79 | emp - 79 | 79 - 80 | emp - 80 | 80 - 81 | emp - 81 | 81 - 82 | emp - 82 | 82 - 83 | emp - 83 | 83 - 84 | emp - 84 | 84 - 85 | emp - 85 | 85 - 86 | emp - 86 | 86 - 87 | emp - 87 | 87 - 88 | emp - 88 | 88 - 89 | emp - 89 | 89 - 90 | emp - 90 | 90 - 91 | emp - 91 | 91 - 92 | emp - 92 | 92 - 93 | emp - 93 | 93 - 94 | emp - 94 | 94 - 95 | emp - 95 | 95 - 96 | emp - 96 | 96 - 97 | emp - 97 | 97 - 98 | emp - 98 | 98 - 99 | emp - 99 | 99 - 100 | emp - 100 | 100 -(98 rows) - -SELECT * FROM employee WHERE emp_name NOT IN ('emp - 1', 'emp - 2') LIMIT 5; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 - 6 | emp - 6 | 6 - 7 | emp - 7 | 7 -(5 rows) - -SELECT * FROM employee WHERE emp_name NOT IN ('emp - 10') LIMIT 5; - emp_id | emp_name | emp_dept_id ---------+----------+------------- - 1 | emp - 1 | 1 - 2 | emp - 2 | 2 - 3 | emp - 3 | 3 - 4 | emp - 4 | 4 - 5 | emp - 5 | 5 -(5 rows) - -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 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(integer, text) returns integer as ' - select a from numbers where a=$1 and b=$2; -' LANGUAGE sql; -SELECT test_param_where2(1, 'One'); - test_param_where2 -------------------- - 1 -(1 row) - -DELETE FROM employee; -DELETE FROM department; -DELETE FROM empdata; -DELETE FROM numbers; -DROP FUNCTION test_param_where(); -DROP FUNCTION test_param_where2(integer, text); -DROP FOREIGN TABLE numbers; -DROP FOREIGN TABLE department; -DROP FOREIGN TABLE employee; -DROP FOREIGN TABLE empdata; -DROP USER MAPPING FOR postgres SERVER mysql_svr; -DROP SERVER mysql_svr; -DROP EXTENSION mysql_fdw CASCADE; diff --git a/expected/pushdown.out b/expected/pushdown.out new file mode 100644 index 0000000..c13d910 --- /dev/null +++ b/expected/pushdown.out @@ -0,0 +1,356 @@ +\set ECHO none +-- Before running this file User must create database mysql_fdw_regress on +-- mysql with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +-- Create foreign tables +--Testcase 4: +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'); +--Testcase 5: +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 +--Testcase 6: +INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); +--Testcase 7: +INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); +--Testcase 8: +INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); +--Testcase 9: +INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); +--Testcase 10: +INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); +--Testcase 11: +INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); +--Testcase 12: +INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); +--Testcase 13: +INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); +--Testcase 14: +INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); +--Testcase 15: +INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); +--Testcase 16: +INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); +--Testcase 17: +INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); +--Testcase 18: +INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); +--Testcase 19: +INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); +--Testcase 20: +INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); +--Testcase 21: +INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); +--Testcase 22: +INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); +--Testcase 23: +INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); +SET datestyle TO ISO; +-- WHERE clause pushdown +--Testcase 24: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e + WHERE c6 IN (800,2450) + ORDER BY c1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c6` IN ('800', '2450')) +(7 rows) + +--Testcase 25: +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) + +--Testcase 26: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT * FROM f_test_tbl1 e + WHERE c6 > 3000 + ORDER BY c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` > 3000)) +(7 rows) + +--Testcase 27: +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) + +--Testcase 28: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 = 1500 + ORDER BY c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` = 1500)) +(7 rows) + +--Testcase 29: +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) + +--Testcase 30: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 BETWEEN 1000 AND 4000 + ORDER BY c1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` >= 1000)) AND ((`c6` <= 4000)) +(7 rows) + +--Testcase 31: +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) + +--Testcase 32: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IS NOT NULL + ORDER BY c1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c2` IS NOT NULL)) +(7 rows) + +--Testcase 33: +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) + +--Testcase 34: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT * FROM f_test_tbl1 e + WHERE c5 <= '1980-12-17' + ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c5` <= '1980-12-17')) +(7 rows) + +--Testcase 35: +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) + +--Testcase 36: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c2` IN ('EMP6', 'EMP12', 'EMP5')) +(7 rows) + +--Testcase 37: +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) + +--Testcase 38: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c2` IN ('EMP6', 'EMP12', 'EMP5')) +(7 rows) + +--Testcase 39: +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) + +--Testcase 40: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'SALESMAN' + ORDER BY c1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c3` LIKE BINARY 'SALESMAN')) +(7 rows) + +--Testcase 41: +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) + +--Testcase 42: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'MANA%' + ORDER BY c1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c6, c8 + Sort Key: e.c1 + -> Foreign Scan on public.f_test_tbl1 e + Output: c1, c2, c6, c8 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c3` LIKE BINARY 'MANA%')) +(7 rows) + +--Testcase 43: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'MANA%' + ORDER BY c1; + c1 | c2 | c6 | c8 +-----+------+------------+---- + 400 | EMP4 | 2975.12000 | 20 + 600 | EMP6 | 2850.00000 | 30 + 700 | EMP7 | 2450.45000 | 10 +(3 rows) + +-- Cleanup +--Testcase 44: +DELETE FROM f_test_tbl1; +--Testcase 45: +DELETE FROM f_test_tbl2; +--Testcase 46: +DROP FOREIGN TABLE f_test_tbl1; +--Testcase 47: +DROP FOREIGN TABLE f_test_tbl2; +--Testcase 48: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 49: +DROP SERVER mysql_svr; +--Testcase 50: +DROP EXTENSION mysql_fdw; diff --git a/expected/select.out b/expected/select.out new file mode 100644 index 0000000..ba2f9ff --- /dev/null +++ b/expected/select.out @@ -0,0 +1,1483 @@ +\set ECHO none +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR PUBLIC SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +-- Check version +--Testcase 4: +SELECT mysql_fdw_version(); + mysql_fdw_version +------------------- + 20505 +(1 row) + +-- Create foreign tables +--Testcase 5: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 6: +CREATE FOREIGN TABLE f_numbers(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'numbers'); +--Testcase 7: +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'); +--Testcase 8: +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'); +--Testcase 9: +CREATE TYPE size_t AS enum('small','medium','large'); +--Testcase 10: +CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); +-- Insert data in MySQL db using foreign tables +--Testcase 11: +INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); +--Testcase 12: +INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); +--Testcase 13: +INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); +--Testcase 14: +INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); +--Testcase 15: +INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); +--Testcase 16: +INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); +--Testcase 17: +INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); +--Testcase 18: +INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); +--Testcase 19: +INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); +--Testcase 20: +INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); +--Testcase 21: +INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); +--Testcase 22: +INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); +--Testcase 23: +INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); +--Testcase 24: +INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); +--Testcase 25: +INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); +--Testcase 26: +INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); +--Testcase 27: +INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); +--Testcase 28: +INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); +SET datestyle TO ISO; +-- Retrieve Data from Foreign Table using SELECT Statement. +--Testcase 29: +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) + +--Testcase 30: +SELECT DISTINCT c8 FROM f_test_tbl1 ORDER BY 1; + c8 +---- + 10 + 20 + 30 +(3 rows) + +--Testcase 31: +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) + +--Testcase 32: +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) + +--Testcase 33: +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) + +--Testcase 34: +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) + +--Testcase 35: +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) + +--Testcase 36: +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) + +--Testcase 37: +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) + +--Testcase 38: +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) + +--Testcase 39: +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. +--Testcase 40: +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) + +--Testcase 41: +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) + +--Testcase 42: +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 +--Testcase 43: +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. +--Testcase 44: +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) + +--Testcase 45: +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) + +--Testcase 46: +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. +--Testcase 47: +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) + +--Testcase 48: +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. +--Testcase 49: +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) + +--Testcase 50: +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. +--Testcase 51: +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) + +--Testcase 52: +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). +--Testcase 53: +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) + +--Testcase 54: +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. +--Testcase 55: +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) + +--Testcase 56: +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) + +--Testcase 57: +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 +--Testcase 58: +CREATE VIEW smpl_vw AS + SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 + ORDER BY c1; +--Testcase 59: +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) + +--Testcase 60: +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; +--Testcase 61: +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) + +--Testcase 62: +CREATE TEMPORARY VIEW ttest_tbl1_vw AS + SELECT c1, c2, c3 FROM f_test_tbl2; +--Testcase 63: +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) + +--Testcase 64: +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; +--Testcase 65: +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. +--Testcase 66: +INSERT INTO f_numbers VALUES (1, 'One'); +--Testcase 67: +INSERT INTO f_numbers VALUES (2, 'Two'); +--Testcase 68: +INSERT INTO f_numbers VALUES (3, 'Three'); +--Testcase 69: +INSERT INTO f_numbers VALUES (4, 'Four'); +--Testcase 70: +INSERT INTO f_numbers VALUES (5, 'Five'); +--Testcase 71: +INSERT INTO f_numbers VALUES (6, 'Six'); +--Testcase 72: +INSERT INTO f_numbers VALUES (7, 'Seven'); +--Testcase 73: +INSERT INTO f_numbers VALUES (8, 'Eight'); +--Testcase 74: +INSERT INTO f_numbers VALUES (9, 'Nine'); +-- Retrieve Data From foreign tables in functions. +--Testcase 75: +CREATE OR REPLACE FUNCTION test_param_where() RETURNS void AS $$ +DECLARE + n varchar; +BEGIN + FOR x IN 1..9 LOOP +--Testcase 76: + SELECT b INTO n FROM f_numbers WHERE a = x; + RAISE NOTICE 'Found number %', n; + END LOOP; + return; +END +$$ LANGUAGE plpgsql; +--Testcase 77: +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) + +--Testcase 78: +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; +--Testcase 79: +SELECT test_param_where2(1, 'One'); + test_param_where2 +------------------- + 1 +(1 row) + +-- Foreign-Foreign table joins +-- CROSS JOIN. +--Testcase 80: +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. +--Testcase 81: +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) + +--Testcase 82: +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. +--Testcase 83: +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) + +--Testcase 84: +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) + +--Testcase 85: +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. +--Testcase 86: +CREATE TABLE l_test_tbl1 AS + SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1; +--Testcase 87: +CREATE TABLE l_test_tbl2 AS + SELECT c1, c2, c3 FROM f_test_tbl2; +-- CROSS JOIN. +--Testcase 88: +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. +--Testcase 89: +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) + +--Testcase 90: +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. +--Testcase 91: +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) + +--Testcase 92: +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) + +--Testcase 93: +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 +--Testcase 121: +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM f_mysql_test t1 LEFT JOIN LATERAL ( + SELECT t2.a, t1.a AS t1_a FROM f_mysql_test t2) t3 ON t1.a = t3.a ORDER BY 1; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Sort + Output: t1.a, t1.b, t2.a, (t1.a) + Sort Key: t1.a + -> Nested Loop Left Join + Output: t1.a, t1.b, t2.a, (t1.a) + -> Foreign Scan on public.f_mysql_test t1 + Output: t1.a, t1.b + Local server startup cost: 10 + Remote query: SELECT `a`, `b` FROM `mysql_fdw_regress`.`mysql_test` + -> Foreign Scan on public.f_mysql_test t2 + Output: t2.a, t1.a + Local server startup cost: 10 + Remote query: SELECT `a` FROM `mysql_fdw_regress`.`mysql_test` WHERE ((? = `a`)) +(13 rows) + +--Testcase 122: +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) + +--Testcase 141: +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) + +--Testcase 142: +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) + +--Testcase 143: +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 +--Testcase 144: +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 +--Testcase 145: +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. +--Testcase 146: +INSERT INTO f_test_tbl2 VALUES (50, 'TEMP1', NULL); +--Testcase 147: +INSERT INTO f_test_tbl2 VALUES (60, 'TEMP2', NULL); +ALTER SERVER mysql_svr OPTIONS (use_remote_estimate 'true'); +--Testcase 148: +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) + +--Testcase 149: +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) + +--Testcase 150: +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. +--Testcase 151: +INSERT INTO f_enum_t1 + VALUES (1, 'small'), (2, 'medium'), (3, 'medium'), (4, 'small'); +--Testcase 152: +SELECT * FROM f_enum_t1 WHERE id = 4; + id | size +----+------- + 4 | small +(1 row) + +--Testcase 153: +UPDATE f_enum_t1 SET size = 'large' WHERE id = 4; +--Testcase 154: +SELECT * FROM f_enum_t1 WHERE id = 4; + id | size +----+------- + 4 | large +(1 row) + +--Testcase 155: +DELETE FROM f_enum_t1 WHERE size = 'large'; +--Testcase 156: +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. +--Testcase 157: +DROP FOREIGN TABLE f_enum_t1; +--Testcase 158: +DROP TYPE size_t; +-- Create the type with extra enum values. +--Testcase 159: +CREATE TYPE size_t AS enum('small', 'medium', 'large', 'largest', ''); +--Testcase 160: +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. +--Testcase 161: +INSERT INTO f_enum_t1 VALUES (4, 'largest'); +--Testcase 162: +SELECT * from f_enum_t1; + id | size +----+-------- + 1 | small + 2 | medium + 3 | medium + 4 | +(4 rows) + +--Testcase 163: +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. +--Testcase 164: +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. +--Testcase 94: +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) +--Testcase 95: +EXPLAIN (VERBOSE, COSTS OFF) +SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; + QUERY PLAN +---------------------------------------------------------------------- + Aggregate + Output: $0, sum(f_enum_t1.id) + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.f_enum_t1 + Output: f_enum_t1.id, f_enum_t1.size + Local server startup cost: 10 + Remote query: SELECT `id` FROM `mysql_fdw_regress`.`enum_t1` +(8 rows) + +--Testcase 96: +SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; + 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. +--Testcase 123: +CREATE TYPE enum_t1_size_t AS enum('small', 'medium', 'large'); +--Testcase 124: +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$$; + +--Testcase 125: +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) + +--Testcase 126: +SELECT * FROM enum_t1 ORDER BY id; + id | size +----+-------- + 1 | small + 2 | medium + 3 | medium +(3 rows) + +--Testcase 127: +SELECT * FROM enum_t2 ORDER BY id; + id | size +----+------ + 10 | S + 20 | M + 30 | M +(3 rows) + +--Testcase 128: +DROP FOREIGN TABLE enum_t1; +--Testcase 129: +DROP FOREIGN TABLE enum_t2; +-- Parameterized queries should work correctly. +--Testcase 130: +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, c2 FROM f_test_tbl1 + WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) + ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: f_test_tbl1.c1, f_test_tbl1.c2 + Sort Key: f_test_tbl1.c1 + InitPlan 2 (returns $1) + -> Foreign Scan on public.f_test_tbl2 + Output: f_test_tbl2.c1 + Local server startup cost: 10 + Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test_tbl2` WHERE ((`c1` = ?)) + InitPlan 1 (returns $0) + -> Result + Output: 20 + -> Foreign Scan on public.f_test_tbl1 + Output: f_test_tbl1.c1, f_test_tbl1.c2 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c8` = ?)) +(15 rows) + +--Testcase 131: +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) + +--Testcase 132: +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. +--Testcase 133: +CREATE FOREIGN TABLE f_test_tbl3 (c1 INTEGER, c2 text, c3 text) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); +--Testcase 134: +CREATE TABLE local_t1 (c1 INTEGER, c2 text); +--Testcase 135: +INSERT INTO local_t1 VALUES (1, 'SALES'); +--Testcase 136: +SELECT c1, c2 FROM f_test_tbl3 WHERE c3 = (SELECT 'PUNE'::text) ORDER BY c1; + c1 | c2 +----+------------- + 10 | DEVELOPMENT +(1 row) + +--Testcase 137: +SELECT c1, c2 FROM f_test_tbl2 WHERE c3 = (SELECT 'PUNE'::varchar) ORDER BY c1; + c1 | c2 +----+------------- + 10 | DEVELOPMENT +(1 row) + +--Testcase 138: +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) + +--Testcase 165: +EXPLAIN (VERBOSE, COSTS OFF) +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; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: f_test_tbl1.c1, f_test_tbl1.c2 + Sort Key: f_test_tbl1.c1 + InitPlan 2 (returns $1) + -> Foreign Scan on public.f_test_tbl2 f_test_tbl2_1 + Output: f_test_tbl2_1.c1 + Local server startup cost: 10 + Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test_tbl2` WHERE ((`c1` = ?)) + InitPlan 1 (returns $0) + -> Aggregate + Output: (min(f_test_tbl2.c1) + 1) + -> Foreign Scan on public.f_test_tbl2 + Output: f_test_tbl2.c1, f_test_tbl2.c2, f_test_tbl2.c3 + Local server startup cost: 10 + Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test_tbl2` + -> Foreign Scan on public.f_test_tbl1 + Output: f_test_tbl1.c1, f_test_tbl1.c2 + Local server startup cost: 10 + Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c8` = ?)) +(19 rows) + +--Testcase 166: +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) + +--Testcase 167: +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) + +--Testcase 168: +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) + +-- Cleanup +--Testcase 99: +DROP TABLE l_test_tbl1; +--Testcase 100: +DROP TABLE l_test_tbl2; +--Testcase 101: +DROP TABLE local_t1; +--Testcase 119: +DROP VIEW smpl_vw; +--Testcase 102: +DROP VIEW comp_vw; +--Testcase 103: +DROP VIEW ttest_tbl1_vw; +--Testcase 104: +DROP VIEW mul_tbl_view; +--Testcase 105: +DELETE FROM f_test_tbl1; +--Testcase 106: +DELETE FROM f_test_tbl2; +--Testcase 107: +DELETE FROM f_numbers; +--Testcase 108: +DELETE FROM f_enum_t1; +--Testcase 169: +DROP FOREIGN TABLE f_test_tbl1; +--Testcase 109: +DROP FOREIGN TABLE f_test_tbl2; +--Testcase 110: +DROP FOREIGN TABLE f_numbers; +--Testcase 111: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 112: +DROP FOREIGN TABLE f_enum_t1; +--Testcase 113: +DROP FOREIGN TABLE f_test_tbl3; +--Testcase 120: +DROP TYPE size_t; +--Testcase 139: +DROP TYPE enum_t1_size_t; +--Testcase 140: +DROP TYPE enum_t2_size_t; +--Testcase 114: +DROP FUNCTION test_param_where(); +--Testcase 115: +DROP FUNCTION test_param_where2(int, text); +--Testcase 116: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 117: +DROP SERVER mysql_svr; +--Testcase 118: +DROP EXTENSION mysql_fdw; diff --git a/expected/selectfunc.out b/expected/selectfunc.out new file mode 100644 index 0000000..a34251d --- /dev/null +++ b/expected/selectfunc.out @@ -0,0 +1,576 @@ +SET datestyle=ISO; +SET timezone='Japan'; +\set ECHO none +--Testcase 1: +CREATE EXTENSION mysql_fdw; +--Testcase 2: +CREATE SERVER server1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR CURRENT_USER SERVER server1 + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +--IMPORT FOREIGN SCHEMA public FROM SERVER server1 INTO public OPTIONS(import_time_text 'false'); +--Testcase 4: +CREATE FOREIGN TABLE s3(id int, tag1 text, value1 float, value2 int, value3 float, value4 int, str1 text, str2 text) SERVER server1 OPTIONS(dbname 'mysql_fdw_regress', table_name 's3'); +-- s3 (value1 as float8, value2 as bigint) +--Testcase 5: +\d s3; + Foreign table "public.s3" + Column | Type | Collation | Nullable | Default | FDW options +--------+------------------+-----------+----------+---------+------------- + id | integer | | | | + tag1 | text | | | | + value1 | double precision | | | | + value2 | integer | | | | + value3 | double precision | | | | + value4 | integer | | | | + str1 | text | | | | + str2 | text | | | | +Server: server1 +FDW options: (dbname 'mysql_fdw_regress', table_name 's3') + +--Testcase 6: +SELECT * FROM s3; + id | tag1 | value1 | value2 | value3 | value4 | str1 | str2 +----+------+--------+--------+--------+--------+-----------+----------- + 0 | a | 0.1 | 100 | -0.1 | -100 | ---XYZ--- | XYZ + 1 | a | 0.2 | 100 | -0.2 | -100 | ---XYZ--- | XYZ + 2 | a | 0.3 | 100 | -0.3 | -100 | ---XYZ--- | XYZ + 3 | b | 1.1 | 200 | -1.1 | -200 | ---XYZ--- | XYZ + 4 | b | 2.2 | 200 | -2.2 | -200 | ---XYZ--- | XYZ + 5 | b | 3.3 | 200 | -3.3 | -200 | ---XYZ--- | XYZ +(6 rows) + +-- select float8() (not pushdown, remove float8, explain) +--Testcase 7: +EXPLAIN VERBOSE +SELECT float8(value1), float8(value2), float8(value3), float8(value4) FROM s3; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1015.00 rows=1000 width=32) + Output: value1, float8(value2), value3, float8(value4) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2`, `value3`, `value4` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- select float8() (not pushdown, remove float8, result) +--Testcase 8: +SELECT float8(value1), float8(value2), float8(value3), float8(value4) FROM s3; + float8 | float8 | float8 | float8 +--------+--------+--------+-------- + 0.1 | 100 | -0.1 | -100 + 0.2 | 100 | -0.2 | -100 + 0.3 | 100 | -0.3 | -100 + 1.1 | 200 | -1.1 | -200 + 2.2 | 200 | -2.2 | -200 + 3.3 | 200 | -3.3 | -200 +(6 rows) + +-- select sqrt (builtin function, explain) +--Testcase 9: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3; + QUERY PLAN +------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1017.50 rows=1000 width=16) + Output: sqrt(value1), sqrt((value2)::double precision) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- select sqrt (buitin function, result) +--Testcase 10: +SELECT sqrt(value1), sqrt(value2) FROM s3; + sqrt | sqrt +---------------------+-------------------- + 0.31622776601683794 | 10 + 0.4472135954999579 | 10 + 0.5477225575051661 | 10 + 1.0488088481701516 | 14.142135623730951 + 1.4832396974191326 | 14.142135623730951 + 1.816590212458495 | 14.142135623730951 +(6 rows) + +-- select sqrt (builtin function,, not pushdown constraints, explain) +--Testcase 11: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE to_hex(value2) != '64'; + QUERY PLAN +------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1017.50 rows=1000 width=16) + Output: sqrt(value1), sqrt((value2)::double precision) + Filter: (to_hex(s3.value2) <> '64'::text) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2` FROM `mysql_fdw_regress`.`s3` +(5 rows) + +-- select sqrt (builtin function, not pushdown constraints, result) +--Testcase 12: +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE to_hex(value2) != '64'; + sqrt | sqrt +--------------------+-------------------- + 1.0488088481701516 | 14.142135623730951 + 1.4832396974191326 | 14.142135623730951 + 1.816590212458495 | 14.142135623730951 +(3 rows) + +-- select sqrt (builtin function, pushdown constraints, explain) +--Testcase 13: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE value2 != 200; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1017.50 rows=1000 width=16) + Output: sqrt(value1), sqrt((value2)::double precision) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2` FROM `mysql_fdw_regress`.`s3` WHERE ((`value2` <> 200)) +(4 rows) + +-- select sqrt (builtin function, pushdown constraints, result) +--Testcase 14: +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE value2 != 200; + sqrt | sqrt +---------------------+------ + 0.31622776601683794 | 10 + 0.4472135954999579 | 10 + 0.5477225575051661 | 10 +(3 rows) + +-- select abs (builtin function, explain) +--Testcase 15: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1020.00 rows=1000 width=24) + Output: abs(value1), abs(value2), abs(value3), abs(value4) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2`, `value3`, `value4` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- ABS() returns negative values if integer (https://github.com/influxdata/influxdb/issues/10261) +-- select abs (buitin function, result) +--Testcase 16: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3; + abs | abs | abs | abs +-----+-----+-----+----- + 0.1 | 100 | 0.1 | 100 + 0.2 | 100 | 0.2 | 100 + 0.3 | 100 | 0.3 | 100 + 1.1 | 200 | 1.1 | 200 + 2.2 | 200 | 2.2 | 200 + 3.3 | 200 | 3.3 | 200 +(6 rows) + +-- select abs (builtin function, not pushdown constraints, explain) +--Testcase 17: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE to_hex(value2) != '64'; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1020.00 rows=1000 width=24) + Output: abs(value1), abs(value2), abs(value3), abs(value4) + Filter: (to_hex(s3.value2) <> '64'::text) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2`, `value3`, `value4` FROM `mysql_fdw_regress`.`s3` +(5 rows) + +-- select abs (builtin function, not pushdown constraints, result) +--Testcase 18: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE to_hex(value2) != '64'; + abs | abs | abs | abs +-----+-----+-----+----- + 1.1 | 200 | 1.1 | 200 + 2.2 | 200 | 2.2 | 200 + 3.3 | 200 | 3.3 | 200 +(3 rows) + +-- select abs (builtin function, pushdown constraints, explain) +--Testcase 19: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE value2 != 200; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1020.00 rows=1000 width=24) + Output: abs(value1), abs(value2), abs(value3), abs(value4) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2`, `value3`, `value4` FROM `mysql_fdw_regress`.`s3` WHERE ((`value2` <> 200)) +(4 rows) + +-- select abs (builtin function, pushdown constraints, result) +--Testcase 20: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE value2 != 200; + abs | abs | abs | abs +-----+-----+-----+----- + 0.1 | 100 | 0.1 | 100 + 0.2 | 100 | 0.2 | 100 + 0.3 | 100 | 0.3 | 100 +(3 rows) + +-- select log (builtin function, need to swap arguments, numeric cast, explain) +-- log_(v) : postgresql (base, v), influxdb (v, base), mysql (base, v) +--Testcase 21: +EXPLAIN VERBOSE +SELECT log(value1::numeric, value2::numeric) FROM s3 WHERE value1 != 1; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1017.50 rows=1000 width=32) + Output: log((value1)::numeric, (value2)::numeric) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2` FROM `mysql_fdw_regress`.`s3` WHERE ((`value1` <> 1)) +(4 rows) + +-- select log (builtin function, need to swap arguments, numeric cast, result) +--Testcase 22: +SELECT log(value1::numeric, value2::numeric) FROM s3 WHERE value1 != 1; + log +--------------------- + -2.0000000000000000 + -2.8613531161467861 + -3.8249785787863969 + 55.590256753535330 + 6.7198527566540755 + 4.4377398922117404 +(6 rows) + +-- select log (stub function, need to swap arguments, float8, explain) +--Testcase 23: +EXPLAIN VERBOSE +SELECT log(value1, 0.1) FROM s3 WHERE value1 != 1; + QUERY PLAN +--------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1260.00 rows=1000 width=8) + Output: log(value1, '0.1'::double precision) + Local server startup cost: 10 + Remote query: SELECT `value1` FROM `mysql_fdw_regress`.`s3` WHERE ((`value1` <> 1)) +(4 rows) + +-- select log (stub function, need to swap arguments, float8, result) +--Testcase 24: +SELECT log(value1, 0.1) FROM s3 WHERE value1 != 1; +ERROR: stub log(float8, float8) is called +CONTEXT: PL/pgSQL function log(double precision,double precision) line 3 at RAISE +-- select log (stub function, need to swap arguments, bigint, explain) +--Testcase 25: +EXPLAIN VERBOSE +SELECT log(value2, 3) FROM s3 WHERE value1 != 1; + QUERY PLAN +--------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1262.50 rows=1000 width=8) + Output: log((value2)::double precision, '3'::double precision) + Local server startup cost: 10 + Remote query: SELECT `value2` FROM `mysql_fdw_regress`.`s3` WHERE ((`value1` <> 1)) +(4 rows) + +-- select log (stub function, need to swap arguments, bigint, result) +--Testcase 26: +SELECT log(value2, 3) FROM s3 WHERE value1 != 1; +ERROR: stub log(float8, float8) is called +CONTEXT: PL/pgSQL function log(double precision,double precision) line 3 at RAISE +-- select log (stub function, need to swap arguments, mix type, explain) +--Testcase 27: +EXPLAIN VERBOSE +SELECT log(value1, value2) FROM s3 WHERE value1 != 1; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1262.50 rows=1000 width=8) + Output: log(value1, (value2)::double precision) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2` FROM `mysql_fdw_regress`.`s3` WHERE ((`value1` <> 1)) +(4 rows) + +-- select log (stub function, need to swap arguments, mix type, result) +--Testcase 28: +SELECT log(value1, value2) FROM s3 WHERE value1 != 1; +ERROR: stub log(float8, float8) is called +CONTEXT: PL/pgSQL function log(double precision,double precision) line 3 at RAISE +-- select log2 (stub function, explain) +-- EXPLAIN VERBOSE +-- SELECT log2(value1),log2(value2) FROM s3; +-- select log2 (stub function, result) +-- SELECT log2(value1),log2(value2) FROM s3; +-- select spread (stub agg function, explain) +-- EXPLAIN VERBOSE +-- SELECT spread(value1),spread(value2),spread(value3),spread(value4) FROM s3; +-- select spread (stub agg function, result) +-- SELECT spread(value1),spread(value2),spread(value3),spread(value4) FROM s3; +-- select spread (stub agg function, raise exception if not expected type) +-- SELECT spread(value1::numeric),spread(value2::numeric),spread(value3::numeric),spread(value4::numeric) FROM s3; +-- select abs as nest function with agg (pushdown, explain) +--Testcase 29: +EXPLAIN VERBOSE +SELECT sum(value3),abs(sum(value3)) FROM s3; + QUERY PLAN +-------------------------------------------------------------------------- + Aggregate (cost=1015.00..1015.01 rows=1 width=16) + Output: sum(value3), abs(sum(value3)) + -> Foreign Scan on public.s3 (cost=10.00..1010.00 rows=1000 width=8) + Output: id, tag1, value1, value2, value3, value4, str1, str2 + Local server startup cost: 10 + Remote query: SELECT `value3` FROM `mysql_fdw_regress`.`s3` +(6 rows) + +-- select abs as nest function with agg (pushdown, result) +--Testcase 30: +SELECT sum(value3),abs(sum(value3)) FROM s3; + sum | abs +------+----- + -7.2 | 7.2 +(1 row) + +-- select abs as nest with log2 (pushdown, explain) +-- EXPLAIN VERBOSE +-- SELECT abs(log2(value1)),abs(log2(1/value1)) FROM s3; +-- select abs as nest with log2 (pushdown, result) +-- SELECT abs(log2(value1)),abs(log2(1/value1)) FROM s3; +-- select abs with non pushdown func and explicit constant (explain) +--Testcase 31: +EXPLAIN VERBOSE +SELECT abs(value3), pi(), 4.1 FROM s3; + QUERY PLAN +--------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1012.50 rows=1000 width=48) + Output: abs(value3), '3.141592653589793'::double precision, 4.1 + Local server startup cost: 10 + Remote query: SELECT `value3` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- select abs with non pushdown func and explicit constant (result) +--Testcase 32: +SELECT abs(value3), pi(), 4.1 FROM s3; + abs | pi | ?column? +-----+-------------------+---------- + 0.1 | 3.141592653589793 | 4.1 + 0.2 | 3.141592653589793 | 4.1 + 0.3 | 3.141592653589793 | 4.1 + 1.1 | 3.141592653589793 | 4.1 + 2.2 | 3.141592653589793 | 4.1 + 3.3 | 3.141592653589793 | 4.1 +(6 rows) + +-- select sqrt as nest function with agg and explicit constant (pushdown, explain) +--Testcase 33: +EXPLAIN VERBOSE +SELECT sqrt(count(value1)), pi(), 4.1 FROM s3; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Aggregate (cost=1012.50..1012.51 rows=1 width=48) + Output: sqrt((count(value1))::double precision), '3.141592653589793'::double precision, 4.1 + -> Foreign Scan on public.s3 (cost=10.00..1010.00 rows=1000 width=8) + Output: id, tag1, value1, value2, value3, value4, str1, str2 + Local server startup cost: 10 + Remote query: SELECT `value1` FROM `mysql_fdw_regress`.`s3` +(6 rows) + +-- select sqrt as nest function with agg and explicit constant (pushdown, result) +--Testcase 34: +SELECT sqrt(count(value1)), pi(), 4.1 FROM s3; + sqrt | pi | ?column? +-------------------+-------------------+---------- + 2.449489742783178 | 3.141592653589793 | 4.1 +(1 row) + +-- select sqrt as nest function with agg and explicit constant and tag (error, explain) +--Testcase 35: +EXPLAIN VERBOSE +SELECT sqrt(count(value1)), pi(), 4.1, tag1 FROM s3; +ERROR: column "s3.tag1" must appear in the GROUP BY clause or be used in an aggregate function +LINE 2: SELECT sqrt(count(value1)), pi(), 4.1, tag1 FROM s3; + ^ +-- select spread (stub agg function and group by influx_time() and tag) (explain) +-- EXPLAIN VERBOSE +-- SELECT spread("value1"),influx_time(time, interval '1s'),tag1 FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY influx_time(time, interval '1s'), tag1; +-- select spread (stub agg function and group by influx_time() and tag) (result) +-- SELECT spread("value1"),influx_time(time, interval '1s'),tag1 FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY influx_time(time, interval '1s'), tag1; +-- select spread (stub agg function and group by tag only) (result) +-- SELECT tag1,spread("value1") FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY tag1; +-- select spread (stub agg function and other aggs) (result) +-- SELECT sum("value1"),spread("value1"),count("value1") FROM s3; +-- select abs with order by (explain) +--Testcase 36: +EXPLAIN VERBOSE +SELECT value1, abs(1-value1) FROM s3 order by abs(1-value1); + QUERY PLAN +--------------------------------------------------------------------------- + Sort (cost=1064.83..1067.33 rows=1000 width=16) + Output: value1, (abs(('1'::double precision - value1))) + Sort Key: (abs(('1'::double precision - s3.value1))) + -> Foreign Scan on public.s3 (cost=10.00..1015.00 rows=1000 width=16) + Output: value1, abs(('1'::double precision - value1)) + Local server startup cost: 10 + Remote query: SELECT `value1` FROM `mysql_fdw_regress`.`s3` +(7 rows) + +-- select abs with order by (result) +--Testcase 37: +SELECT value1, abs(1-value1) FROM s3 order by abs(1-value1); + value1 | abs +--------+--------------------- + 1.1 | 0.10000000000000009 + 0.3 | 0.7 + 0.2 | 0.8 + 0.1 | 0.9 + 2.2 | 1.2000000000000002 + 3.3 | 2.3 +(6 rows) + +-- select abs with order by index (result) +--Testcase 38: +SELECT value1, abs(1-value1) FROM s3 order by 2,1; + value1 | abs +--------+--------------------- + 1.1 | 0.10000000000000009 + 0.3 | 0.7 + 0.2 | 0.8 + 0.1 | 0.9 + 2.2 | 1.2000000000000002 + 3.3 | 2.3 +(6 rows) + +-- select abs with order by index (result) +--Testcase 39: +SELECT value1, abs(1-value1) FROM s3 order by 1,2; + value1 | abs +--------+--------------------- + 0.1 | 0.9 + 0.2 | 0.8 + 0.3 | 0.7 + 1.1 | 0.10000000000000009 + 2.2 | 1.2000000000000002 + 3.3 | 2.3 +(6 rows) + +-- select abs and as +--Testcase 40: +SELECT abs(value3) as abs1 FROM s3; + abs1 +------ + 0.1 + 0.2 + 0.3 + 1.1 + 2.2 + 3.3 +(6 rows) + +-- select spread over join query (explain) +-- EXPLAIN VERBOSE +-- SELECT spread(t1.value1), spread(t2.value1) FROM s3 t1 INNER JOIN s3 t2 ON (t1.value1 = t2.value1) where t1.value1 = 0.1; +-- select spread over join query (result, stub call error) +-- SELECT spread(t1.value1), spread(t2.value1) FROM s3 t1 INNER JOIN s3 t2 ON (t1.value1 = t2.value1) where t1.value1 = 0.1; +-- select spread with having (explain) +-- EXPLAIN VERBOSE +-- SELECT spread(value1) FROM s3 HAVING spread(value1) > 100; +-- select spread with having (explain, cannot pushdown, stub call error) +-- SELECT spread(value1) FROM s3 HAVING spread(value1) > 100; +-- select abs with arithmetic and tag in the middle (explain) +--Testcase 41: +EXPLAIN VERBOSE +SELECT abs(value1) + 1, value2, tag1, sqrt(value2) FROM s3; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1020.00 rows=1000 width=52) + Output: (abs(value1) + '1'::double precision), value2, tag1, sqrt((value2)::double precision) + Local server startup cost: 10 + Remote query: SELECT `tag1`, `value1`, `value2` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- select abs with arithmetic and tag in the middle (result) +--Testcase 42: +SELECT abs(value1) + 1, value2, tag1, sqrt(value2) FROM s3; + ?column? | value2 | tag1 | sqrt +----------+--------+------+-------------------- + 1.1 | 100 | a | 10 + 1.2 | 100 | a | 10 + 1.3 | 100 | a | 10 + 2.1 | 200 | b | 14.142135623730951 + 3.2 | 200 | b | 14.142135623730951 + 4.3 | 200 | b | 14.142135623730951 +(6 rows) + +-- select with order by limit (explain) +--Testcase 43: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value3), sqrt(value2) FROM s3 ORDER BY abs(value3) LIMIT 1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Limit (cost=1025.00..1025.00 rows=1 width=24) + Output: (abs(value1)), (abs(value3)), (sqrt((value2)::double precision)) + -> Sort (cost=1025.00..1027.50 rows=1000 width=24) + Output: (abs(value1)), (abs(value3)), (sqrt((value2)::double precision)) + Sort Key: (abs(s3.value3)) + -> Foreign Scan on public.s3 (cost=10.00..1020.00 rows=1000 width=24) + Output: abs(value1), abs(value3), sqrt((value2)::double precision) + Local server startup cost: 10 + Remote query: SELECT `value1`, `value2`, `value3` FROM `mysql_fdw_regress`.`s3` +(9 rows) + +-- select with order by limit (explain) +--Testcase 44: +SELECT abs(value1), abs(value3), sqrt(value2) FROM s3 ORDER BY abs(value3) LIMIT 1; + abs | abs | sqrt +-----+-----+------ + 0.1 | 0.1 | 10 +(1 row) + +-- select mixing with non pushdown func (all not pushdown, explain) +--Testcase 45: +EXPLAIN VERBOSE +SELECT abs(value1), sqrt(value2), chr(id+40) FROM s3; + QUERY PLAN +------------------------------------------------------------------------------- + Foreign Scan on public.s3 (cost=10.00..1022.50 rows=1000 width=48) + Output: abs(value1), sqrt((value2)::double precision), chr((id + 40)) + Local server startup cost: 10 + Remote query: SELECT `id`, `value1`, `value2` FROM `mysql_fdw_regress`.`s3` +(4 rows) + +-- select mixing with non pushdown func (result) +--Testcase 46: +SELECT abs(value1), sqrt(value2), chr(id+40) FROM s3; + abs | sqrt | chr +-----+--------------------+----- + 0.1 | 10 | ( + 0.2 | 10 | ) + 0.3 | 10 | * + 1.1 | 14.142135623730951 | + + 2.2 | 14.142135623730951 | , + 3.3 | 14.142135623730951 | - +(6 rows) + +--Testcase 47: +DROP FOREIGN TABLE s3; +-- full text search table +--Testcase 48: +CREATE FOREIGN TABLE ftextsearch(id int, content text) SERVER server1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'ftextsearch'); +-- text search (pushdown, explain) +--Testcase 49: +EXPLAIN VERBOSE +SELECT MATCH_AGAINST(ARRAY[content, 'success catches']) AS score, content FROM ftextsearch WHERE MATCH_AGAINST(ARRAY[content, 'success catches','IN BOOLEAN MODE']) != 0; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ftextsearch (cost=10.00..1260.00 rows=1000 width=40) + Output: match_against(ARRAY[content, 'success catches'::text]), content + Local server startup cost: 10 + Remote query: SELECT `content` FROM `mysql_fdw_regress`.`ftextsearch` WHERE ((MATCH (`content`) AGAINST ( 'success catches' IN BOOLEAN MODE) <> 0)) +(4 rows) + +-- text search (pushdown, result) +--Testcase 50: +SELECT content FROM ( +SELECT MATCH_AGAINST(ARRAY[content, 'success catches']) AS score, content FROM ftextsearch WHERE MATCH_AGAINST(ARRAY[content, 'success catches','IN BOOLEAN MODE']) != 0 + ) AS t; + content +---------------------------------- + Failure teaches success. + The early bird catches the worm. +(2 rows) + +--Testcase 51: +DROP FOREIGN TABLE ftextsearch; +--Testcase 52: +DROP USER MAPPING FOR CURRENT_USER SERVER server1; +--Testcase 53: +DROP SERVER server1; +--Testcase 54: +DROP EXTENSION mysql_fdw; diff --git a/expected/server_options.out b/expected/server_options.out new file mode 100644 index 0000000..a320b8b --- /dev/null +++ b/expected/server_options.out @@ -0,0 +1,176 @@ +\set ECHO none +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +-- Validate extension, server and mapping details +--Testcase 4: +SELECT e.fdwname as "Extension", srvname AS "Server", s.srvoptions AS "Server_Options", u.umoptions AS "User_Mapping_Options" + 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; + Extension | Server | Server_Options | User_Mapping_Options +-----------+-----------+----------------------------+----------------------------- + mysql_fdw | mysql_svr | {host=localhost,port=3306} | {username=edb,password=edb} +(1 row) + +-- Create foreign table and perform basic SQL operations +--Testcase 5: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 6: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 +(1 row) + +--Testcase 7: +INSERT INTO f_mysql_test (a, b) VALUES (2, 2); +--Testcase 8: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 + 2 | 2 +(2 rows) + +--Testcase 9: +UPDATE f_mysql_test SET b = 3 WHERE a = 2; +--Testcase 10: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 + 2 | 3 +(2 rows) + +--Testcase 11: +DELETE FROM f_mysql_test WHERE a = 2; +--Testcase 12: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; + a | b +---+--- + 1 | 1 +(1 row) + +--Testcase 13: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 14: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 15: +DROP SERVER mysql_svr; +-- Server with init_command. +--Testcase 16: +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)'); +--Testcase 17: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 18: +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. +--Testcase 19: +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. +--Testcase 20: +CREATE FOREIGN TABLE f_init_command_check(a int) + SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'init_command_check'); +--Testcase 21: +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'); +--Testcase 22: +SELECT a, b FROM f_mysql_test; + a | b +---+--- + 1 | 1 +(1 row) + +--Testcase 23: +DROP FOREIGN TABLE f_init_command_check; +--Testcase 24: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 25: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 26: +DROP SERVER mysql_svr1; +-- Server with use_remote_estimate. +--Testcase 27: +CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, use_remote_estimate 'TRUE'); +--Testcase 28: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 29: +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. +--Testcase 30: +EXPLAIN (VERBOSE, COSTS OFF) SELECT a FROM f_mysql_test WHERE a < 2 ORDER BY 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Output: a + Sort Key: f_mysql_test.a + -> Foreign Scan on public.f_mysql_test + Output: a + Local server startup cost: 10 + Remote query: SELECT `a` FROM `mysql_fdw_regress`.`mysql_test` WHERE ((`a` < 2)) +(7 rows) + +--Testcase 31: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 32: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 33: +DROP SERVER mysql_svr1; +-- Create server with secure_auth. +--Testcase 34: +CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, secure_auth 'FALSE'); +--Testcase 35: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 36: +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. +--Testcase 37: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; +WARNING: MySQL secure authentication is off + a | b +---+--- + 1 | 1 +(1 row) + +--Testcase 38: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 39: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 40: +DROP SERVER mysql_svr1; +-- Cleanup +--Testcase 41: +DROP EXTENSION mysql_fdw; diff --git a/mysql_fdw--1.0.sql b/mysql_fdw--1.0.sql index aa1fd22..5d45893 100644 --- a/mysql_fdw--1.0.sql +++ b/mysql_fdw--1.0.sql @@ -4,8 +4,7 @@ * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.0.sql @@ -33,4 +32,3 @@ $$ LANGUAGE plpgsql IMMUTABLE; CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; - diff --git a/mysql_fdw--1.1.sql b/mysql_fdw--1.1.sql index 09f5d83..fb42abe 100644 --- a/mysql_fdw--1.1.sql +++ b/mysql_fdw--1.1.sql @@ -4,8 +4,7 @@ * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.1.sql @@ -28,13 +27,79 @@ CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; -CREATE FUNCTION MATCH_AGAINST(varidiadic text[]) RETURNS INT AS $$ -BEGIN - RETURN 1; -END -$$ LANGUAGE plpgsql IMMUTABLE; - CREATE OR REPLACE FUNCTION mysql_fdw_version() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; +CREATE PROCEDURE mysql_create_or_replace_stub(func_type text, name_arg text, return_type regtype) AS $$ +DECLARE + proname_raw text := split_part(name_arg, '(', 1); + proname text := ltrim(rtrim(proname_raw)); +BEGIN + IF lower(func_type) = 'aggregation' OR lower(func_type) = 'aggregate' OR lower(func_type) = 'agg' OR lower(func_type) = 'a' THEN + DECLARE + proargs_raw text := right(name_arg, length(name_arg) - length(proname_raw)); + proargs text := ltrim(rtrim(proargs_raw)); + proargs_types text := right(left(proargs, length(proargs) - 1), length(proargs) - 2); + aggproargs text := format('(%s, %s)', return_type, proargs_types); + BEGIN + BEGIN + EXECUTE format(' + CREATE FUNCTION %s_sfunc%s RETURNS %s IMMUTABLE AS $inner$ + BEGIN + RAISE EXCEPTION ''stub %s_sfunc%s is called''; + RETURN NULL; + END $inner$ LANGUAGE plpgsql;', + proname, aggproargs, return_type, proname, aggproargs); + EXCEPTION + WHEN duplicate_function THEN + RAISE DEBUG 'stub function for aggregation already exists (ignored)'; + END; + BEGIN + EXECUTE format(' + CREATE AGGREGATE %s + ( + sfunc = %s_sfunc, + stype = %s + );', name_arg, proname, return_type); + EXCEPTION + WHEN duplicate_function THEN + RAISE DEBUG 'stub aggregation already exists (ignored)'; + WHEN others THEN + RAISE EXCEPTION 'stub aggregation exception'; + END; + END; + ELSIF lower(func_type) = 'function' OR lower(func_type) = 'func' OR lower(func_type) = 'f' THEN + BEGIN + EXECUTE format(' + CREATE FUNCTION %s RETURNS %s IMMUTABLE AS $inner$ + BEGIN + RAISE EXCEPTION ''stub %s is called''; + RETURN NULL; + END $inner$ LANGUAGE plpgsql;', + name_arg, return_type, name_arg); + EXCEPTION + WHEN duplicate_function THEN + RAISE DEBUG 'stub already exists (ignored)'; + END; + ELSE + RAISE EXCEPTION 'not supported function type %', func_type; + BEGIN + EXECUTE format(' + CREATE FUNCTION %s_sfunc RETURNS %s AS $inner$ + BEGIN + RAISE EXCEPTION ''stub %s is called''; + RETURN NULL; + END $inner$ LANGUAGE plpgsql;', + name_arg, return_type, name_arg); + EXCEPTION + WHEN duplicate_function THEN + RAISE DEBUG 'stub already exists (ignored)'; + END; + END IF; +END +$$ LANGUAGE plpgsql; + +call mysql_create_or_replace_stub('f', 'match_against(varidiadic text[])', 'float'); +call mysql_create_or_replace_stub('f', 'log(bigint, bigint)', 'float8'); +call mysql_create_or_replace_stub('f', 'log(float8, float8)', 'float8'); diff --git a/mysql_fdw.c b/mysql_fdw.c index c233621..cf85730 100644 --- a/mysql_fdw.c +++ b/mysql_fdw.c @@ -4,97 +4,112 @@ * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, 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 - -#include -#include +#include "access/htup_details.h" +#include "access/sysattr.h" #include "access/reloptions.h" #if PG_VERSION_NUM >= 120000 - #include "access/table.h" +#include "access/table.h" #endif -#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 "commands/explain.h" -#include "commands/vacuum.h" -#include "foreign/fdwapi.h" -#include "foreign/foreign.h" -#include "nodes/makefuncs.h" -#include "optimizer/cost.h" -#include "optimizer/pathnode.h" -#include "optimizer/plancat.h" -#include "optimizer/planmain.h" -#include "optimizer/restrictinfo.h" -#include "storage/ipc.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/date.h" -#include "utils/hsearch.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" -#include "utils/timestamp.h" -#include "utils/formatting.h" -#include "utils/memutils.h" -#include "access/htup_details.h" -#include "access/sysattr.h" #include "commands/defrem.h" #include "commands/explain.h" -#include "commands/vacuum.h" #include "foreign/fdwapi.h" -#include "funcapi.h" #include "miscadmin.h" +#include "mysql_query.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/cost.h" #include "optimizer/pathnode.h" -#include "optimizer/paths.h" #include "optimizer/planmain.h" -#include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #if PG_VERSION_NUM < 120000 - #include "optimizer/var.h" +#include "optimizer/var.h" #else - #include "optimizer/optimizer.h" +#include "optimizer/optimizer.h" #endif +#include "optimizer/tlist.h" #include "parser/parsetree.h" +#include "storage/ipc.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" -#include "optimizer/pathnode.h" -#include "optimizer/restrictinfo.h" -#include "optimizer/planmain.h" +#include "utils/syscache.h" -#include "mysql_query.h" +/* Declarations for dynamic loading */ +PG_MODULE_MAGIC; + +int ((mysql_options) (MYSQL *mysql, enum mysql_option option, + const void *arg)); +int ((mysql_stmt_prepare) (MYSQL_STMT *stmt, const char *query, + unsigned long length)); +int ((mysql_stmt_execute) (MYSQL_STMT *stmt)); +int ((mysql_stmt_fetch) (MYSQL_STMT *stmt)); +int ((mysql_query) (MYSQL *mysql, const char *q)); +bool ((mysql_stmt_attr_set) (MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *attr)); +bool ((mysql_stmt_close) (MYSQL_STMT *stmt)); +bool ((mysql_stmt_reset) (MYSQL_STMT *stmt)); +bool ((mysql_free_result) (MYSQL_RES *result)); +bool ((mysql_stmt_bind_param) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); +bool ((mysql_stmt_bind_result) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); + +MYSQL_STMT *((mysql_stmt_init) (MYSQL *mysql)); +MYSQL_RES *((mysql_stmt_result_metadata) (MYSQL_STMT *stmt)); +int ((mysql_stmt_store_result) (MYSQL *mysql)); +MYSQL_ROW((mysql_fetch_row) (MYSQL_RES *result)); +MYSQL_FIELD *((mysql_fetch_field) (MYSQL_RES *result)); +MYSQL_FIELD *((mysql_fetch_fields) (MYSQL_RES *result)); +const char *((mysql_error) (MYSQL *mysql)); +void ((mysql_close) (MYSQL *sock)); +MYSQL_RES *((mysql_store_result) (MYSQL *mysql)); +MYSQL *((mysql_init) (MYSQL *mysql)); +bool ((mysql_ssl_set) (MYSQL *mysql, const char *key, const char *cert, + const char *ca, const char *capath, + const char *cipher)); +MYSQL *((mysql_real_connect) (MYSQL *mysql, const char *host, const char *user, + const char *passwd, const char *db, + unsigned int port, const char *unix_socket, + unsigned long clientflag)); + +const char *((mysql_get_host_info) (MYSQL *mysql)); +const char *((mysql_get_server_info) (MYSQL *mysql)); +int ((mysql_get_proto_info) (MYSQL *mysql)); + +unsigned int ((mysql_stmt_errno) (MYSQL_STMT *stmt)); +unsigned int ((mysql_errno) (MYSQL *mysql)); +unsigned int ((mysql_num_fields) (MYSQL_RES *result)); +unsigned int ((mysql_num_rows) (MYSQL_RES *result)); +unsigned int ((mysql_warning_count)(MYSQL *mysql)); #define DEFAULTE_NUM_ROWS 1000 /* * In PG 9.5.1 the number will be 90501, - * our version is 2.5.3 so number will be 20503 + * our version is 2.5.5 so number will be 20505 */ -#define CODE_VERSION 20503 - -PG_MODULE_MAGIC; - +#define CODE_VERSION 20505 typedef struct MySQLFdwRelationInfo { @@ -105,14 +120,12 @@ typedef struct MySQLFdwRelationInfo /* Bitmap of attr numbers we need to fetch from the remote server. */ Bitmapset *attrs_used; + /* Function pushdown surppot in target list */ + bool is_tlist_func_pushdown; } MySQLFdwRelationInfo; - -extern Datum mysql_fdw_handler(PG_FUNCTION_ARGS); extern PGDLLEXPORT void _PG_init(void); - -bool mysql_load_library(void); -static void mysql_fdw_exit(int code, Datum arg); +extern Datum mysql_fdw_handler(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(mysql_fdw_handler); PG_FUNCTION_INFO_V1(mysql_fdw_version); @@ -126,58 +139,97 @@ 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); -static void mysqlAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *target_rte, +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); +static void mysqlAddForeignUpdateTargets(Query *parsetree, + RangeTblEntry *target_rte, Relation target_relation); -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 *baserel, Oid foreigntableid, - ForeignPath *best_path, List * tlist, List *scan_clauses +static TupleTableSlot *mysqlExecForeignUpdate(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static TupleTableSlot *mysqlExecForeignDelete(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static void mysqlEndForeignModify(EState *estate, + ResultRelInfo *resultRelInfo); + +static void mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, + Oid foreigntableid); +static void mysqlGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, + Oid foreigntableid); +static bool mysqlAnalyzeForeignTable(Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages); #if PG_VERSION_NUM >= 90500 - ,Plan * outer_plan +static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, List *tlist, + List *scan_clauses, Plan *outer_plan); +#else +static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, List *tlist, + List *scan_clauses); #endif -); -static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, +static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, + Cost *startup_cost, Cost *total_cost, Oid foreigntableid); #if PG_VERSION_NUM >= 90500 -static List *mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); +static List *mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, + Oid serverOid); +#endif + +#if PG_VERSION_NUM >= 110000 +static void mysqlBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo); +static void mysqlEndForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo); #endif +/* + * Helper functions + */ +bool mysql_load_library(void); +static void mysql_fdw_exit(int code, Datum arg); static bool mysql_is_column_unique(Oid foreigntableid); static void prepare_query_params(PlanState *node, - List *fdw_exprs, - int numParams, - FmgrInfo **param_flinfo, - List **param_exprs, - const char ***param_values, - Oid **param_types); + 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); + FmgrInfo *param_flinfo, + List *param_exprs, + const char **param_values, + MYSQL_BIND **mysql_bind_buf, + Oid *param_types); -static void create_cursor(ForeignScanState *node); +static void bind_stmt_params_and_exec(ForeignScanState *node); -void* mysql_dll_handle = NULL; +void *mysql_dll_handle = NULL; static int wait_timeout = WAIT_TIMEOUT; static int interactive_timeout = INTERACTIVE_TIMEOUT; +static void mysql_error_print(MYSQL *conn); +static void mysql_stmt_error_print(MySQLFdwExecState *festate, + const char *msg); +static List *getUpdateTargetAttrs(RangeTblEntry *rte); /* * mysql_load_library function dynamically load the mysql's library @@ -209,14 +261,14 @@ 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 + * 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) + if (mysql_dll_handle == NULL) return false; _mysql_stmt_bind_param = dlsym(mysql_dll_handle, "mysql_stmt_bind_param"); @@ -249,7 +301,8 @@ mysql_load_library(void) _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"); - + _mysql_warning_count = dlsym(mysql_dll_handle, "mysql_warning_count"); + if (_mysql_stmt_bind_param == NULL || _mysql_stmt_bind_result == NULL || _mysql_stmt_init == NULL || @@ -279,8 +332,10 @@ mysql_load_library(void) _mysql_num_rows == NULL || _mysql_get_host_info == NULL || _mysql_get_server_info == NULL || - _mysql_get_proto_info == NULL) - return false; + _mysql_get_proto_info == NULL || + _mysql_warning_count == NULL) + return false; + return true; } @@ -293,9 +348,9 @@ _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"))); + (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", @@ -324,11 +379,13 @@ _PG_init(void) NULL, NULL, NULL); + on_proc_exit(&mysql_fdw_exit, PointerGetDatum(NULL)); } /* - * mysql_fdw_exit: Exit callback function. + * mysql_fdw_exit + * Exit callback function. */ static void mysql_fdw_exit(int code, Datum arg) @@ -345,99 +402,115 @@ mysql_fdw_handler(PG_FUNCTION_ARGS) { FdwRoutine *fdwroutine = makeNode(FdwRoutine); - /* Callback functions for readable FDW */ + /* Functions for scanning foreign tables */ fdwroutine->GetForeignRelSize = mysqlGetForeignRelSize; fdwroutine->GetForeignPaths = mysqlGetForeignPaths; - fdwroutine->AnalyzeForeignTable = mysqlAnalyzeForeignTable; fdwroutine->GetForeignPlan = mysqlGetForeignPlan; - fdwroutine->ExplainForeignScan = mysqlExplainForeignScan; fdwroutine->BeginForeignScan = mysqlBeginForeignScan; fdwroutine->IterateForeignScan = mysqlIterateForeignScan; fdwroutine->ReScanForeignScan = mysqlReScanForeignScan; fdwroutine->EndForeignScan = mysqlEndForeignScan; -#if PG_VERSION_NUM >= 90500 - fdwroutine->ImportForeignSchema = mysqlImportForeignSchema; -#endif - - /* Callback functions for writeable FDW */ - fdwroutine->ExecForeignInsert = mysqlExecForeignInsert; - fdwroutine->BeginForeignModify = mysqlBeginForeignModify; - fdwroutine->PlanForeignModify = mysqlPlanForeignModify; + /* 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; + /* Support functions for EXPLAIN */ + fdwroutine->ExplainForeignScan = mysqlExplainForeignScan; + + /* Support functions for ANALYZE */ + fdwroutine->AnalyzeForeignTable = mysqlAnalyzeForeignTable; + + /* Support functions for IMPORT FOREIGN SCHEMA */ +#if PG_VERSION_NUM >= 90500 + fdwroutine->ImportForeignSchema = mysqlImportForeignSchema; +#endif + +#if PG_VERSION_NUM >= 110000 + /* Partition routing and/or COPY from */ + fdwroutine->BeginForeignInsert = mysqlBeginForeignInsert; + fdwroutine->EndForeignInsert = mysqlEndForeignInsert; +#endif + PG_RETURN_POINTER(fdwroutine); } /* - * mysqlBeginForeignScan: Initiate access to the database + * 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 = NULL; - RangeTblEntry *rte; - MySQLFdwExecState *festate = NULL; - EState *estate = node->ss.ps.state; - ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; - mysql_opt *options; - ListCell *lc = NULL; - int atindex = 0; - unsigned long prefetch_rows = MYSQL_PREFETCH_ROWS; - unsigned long type = (unsigned long) CURSOR_TYPE_READ_ONLY; - Oid userid; - ForeignServer *server; - UserMapping *user; - ForeignTable *table; - char timeout[255]; - int numParams; - List *tlist; + 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 prefetch_rows = MYSQL_PREFETCH_ROWS; + unsigned long type = (unsigned long) CURSOR_TYPE_READ_ONLY; + Oid userid; + ForeignServer *server; + UserMapping *user; + ForeignTable *table; + char timeout[255]; + int numParams; + int rtindex; + /* * We'll save private state in node->fdw_state. */ - festate = (MySQLFdwExecState *) palloc(sizeof(MySQLFdwExecState)); + festate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); node->fdw_state = (void *) festate; /* * Identify which user to do the remote access as. This should match what * ExecCheckRTEPerms() does. */ - rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table); + if (fsplan->scan.scanrelid > 0) + rtindex = fsplan->scan.scanrelid; + else + rtindex = bms_next_member(fsplan->fs_relids, -1); + rte = exec_rt_fetch(rtindex, estate); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ - festate->rel = node->ss.ss_currentRelation; - table = GetForeignTable(RelationGetRelid(festate->rel)); + table = GetForeignTable(rte->relid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch the options */ - options = mysql_get_options(RelationGetRelid(node->ss.ss_currentRelation)); + options = mysql_get_options(rte->relid); /* - * Get the already connected connection, otherwise connect - * and get the connection handle. + * 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, 0)); festate->retrieved_attrs = list_nth(fsplan->fdw_private, 1); - - festate->is_tlist_pushdown = intVal(list_nth(fsplan->fdw_private, 2)); festate->conn = conn; - festate->cursor_exists = false; + festate->query_executed = false; +#if PG_VERSION_NUM >= 110000 festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", -#if PG_VERSION_NUM >= 110000 ALLOCSET_DEFAULT_SIZES); #else + festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, + "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); @@ -445,62 +518,39 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) if (wait_timeout > 0) { - /* Set the session timeout in seconds*/ + /* Set the session timeout in seconds */ sprintf(timeout, "SET wait_timeout = %d", wait_timeout); - _mysql_query(festate->conn, timeout); + mysql_query(festate->conn, timeout); } if (interactive_timeout > 0) { - /* Set the session timeout in seconds*/ + /* Set the session timeout in seconds */ sprintf(timeout, "SET interactive_timeout = %d", interactive_timeout); - _mysql_query(festate->conn, timeout); + mysql_query(festate->conn, timeout); } - _mysql_query(festate->conn, "SET sql_mode='ANSI_QUOTES'"); - + /* Change sql_mode to TRADITIONAL to catch warning "Division by 0" */ + mysql_query(festate->conn, "SET sql_mode='TRADITIONAL'"); /* Initialize the MySQL statement */ - festate->stmt = _mysql_stmt_init(festate->conn); + festate->stmt = mysql_stmt_init(festate->conn); if (festate->stmt == NULL) - { - char *err = pstrdup(_mysql_error(festate->conn)); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to initialize the mysql query: \n%s", err))); - } + 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) - { - switch(_mysql_stmt_errno(festate->stmt)) - { - case CR_NO_ERROR: - break; + if (mysql_stmt_prepare(festate->stmt, festate->query, + strlen(festate->query)) != 0) + mysql_stmt_error_print(festate, "failed to prepare the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(festate->conn)); - mysql_rel_connection(festate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to prepare the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(festate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to prepare the MySQL query: \n%s", err))); - } - break; - } - } + /* + * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; /* Prepare for output conversion of parameters used in remote query. */ numParams = list_length(fsplan->fdw_exprs); @@ -514,227 +564,156 @@ mysqlBeginForeignScan(ForeignScanState *node, int eflags) &festate->param_values, &festate->param_types); - /* - * If this is the first call after Begin or ReScan, we need to create the - * cursor on the remote side. - */ - if (!festate->cursor_exists) - create_cursor(node); - - /* int column_count = mysql_num_fields(festate->meta); */ + /* 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); + 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*) &prefetch_rows); + mysql_stmt_attr_set(festate->stmt, STMT_ATTR_PREFETCH_ROWS, + (void *) &prefetch_rows); - festate->table = (mysql_table*) palloc0(sizeof(mysql_table)); + 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) - { - char *err = pstrdup(_mysql_error(festate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to retrieve query result set metadata: \n%s", err))); - } + festate->table->mysql_bind = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * tupleDescriptor->natts); - festate->table->_mysql_fields = _mysql_fetch_fields(festate->table->_mysql_res); + 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)))); - if (festate->is_tlist_pushdown) - tlist = node->ss.ps.plan->targetlist; - else - tlist = festate->retrieved_attrs; + festate->table->mysql_fields = mysql_fetch_fields(festate->table->mysql_res); - atindex = 0; - foreach (lc, tlist) + foreach(lc, festate->retrieved_attrs) { - Oid pgtype; - int32 pgtypmod; - int attnum; - if (festate->is_tlist_pushdown) - attnum = atindex; - else - attnum = lfirst_int(lc) - 1; - pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; - pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; + int attnum = lfirst_int(lc) - 1; + Oid pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; + int32 pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; if (TupleDescAttr(tupleDescriptor, attnum)->attisdropped) continue; - festate->table->column[atindex]._mysql_bind = &festate->table->_mysql_bind[atindex]; + 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]); + 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) - { - switch(_mysql_stmt_errno(festate->stmt)) - { - case CR_NO_ERROR: - break; - - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(festate->conn)); - mysql_rel_connection(festate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to bind the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(festate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to bind the MySQL query: \n%s", err))); - } - break; - } - } - /* - * Finally execute the query and result will be placed in the - * array we already bind - */ - if (_mysql_stmt_execute(festate->stmt) != 0) - { - switch(_mysql_stmt_errno(festate->stmt)) - { - case CR_NO_ERROR: - break; - - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(festate->conn)); - mysql_rel_connection(festate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(festate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - } - } + 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 + * 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; - TupleDesc tupleDescriptor = tupleSlot->tts_tupleDescriptor; - int attid = 0; - ListCell *lc = NULL; - int rc = 0; + MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; + TupleTableSlot *tupleSlot = node->ss.ss_ScanTupleSlot; + TupleDesc tupleDescriptor = tupleSlot->tts_tupleDescriptor; + int attid; + ListCell *lc; + int rc = 0; - memset (tupleSlot->tts_values, 0, sizeof(Datum) * tupleDescriptor->natts); - memset (tupleSlot->tts_isnull, true, sizeof(bool) * tupleDescriptor->natts); + memset(tupleSlot->tts_values, 0, sizeof(Datum) * tupleDescriptor->natts); + memset(tupleSlot->tts_isnull, true, sizeof(bool) * tupleDescriptor->natts); 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 (0 == rc) + rc = mysql_stmt_fetch(festate->stmt); + if (rc == 0) { - List *tlist; - if (festate->is_tlist_pushdown) - tlist = node->ss.ps.plan->targetlist; - else - tlist = festate->retrieved_attrs; - - foreach(lc, tlist) + foreach(lc, festate->retrieved_attrs) { - int attnum; - Oid pgtype; - int32 pgtypmod; - if (festate->is_tlist_pushdown) - attnum = attid; - else - attnum = lfirst_int(lc) - 1; - pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; - pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; + int attnum = lfirst_int(lc) - 1; + Oid pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; + int32 pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; tupleSlot->tts_isnull[attnum] = festate->table->column[attid].is_null; if (!festate->table->column[attid].is_null) - tupleSlot->tts_values[attnum] = mysql_convert_to_pg(pgtype, pgtypmod, - &festate->table->column[attid]); + tupleSlot->tts_values[attnum] = mysql_convert_to_pg(pgtype, + pgtypmod, + &festate->table->column[attid]); + attid++; } + ExecStoreVirtualTuple(tupleSlot); } - else if (1 == rc) + else if (rc == 1) { - /* - Error occurred. Error code and message can be obtained - by calling mysql_stmt_errno() and mysql_stmt_error(). - */ + /* + * Error occurred. Error code and message can be obtained by calling + * mysql_stmt_errno() and mysql_stmt_error(). + */ } - else if (MYSQL_NO_DATA == rc) + else if (rc == MYSQL_NO_DATA) { - /* - No more rows/data exists - */ + /* + * No more rows/data exists + */ } - else if (MYSQL_DATA_TRUNCATED == rc) + 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. - */ + /* 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 + * mysqlExplainForeignScan + * Produce extra output for EXPLAIN */ static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; - mysql_opt *options; + mysql_opt *options; + ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; + int rtindex; + RangeTblEntry *rte; + EState *estate = node->ss.ps.state; + + if (fsplan->scan.scanrelid > 0) + rtindex = fsplan->scan.scanrelid; + else + rtindex = bms_next_member(fsplan->fs_relids, -1); + rte = exec_rt_fetch(rtindex, estate); /* Fetch options */ - options = mysql_get_options(RelationGetRelid(node->ss.ss_currentRelation)); + options = mysql_get_options(rte->relid); /* Give some possibly useful info about startup costs */ if (es->verbose) { - if (strcmp(options->svr_address, "127.0.0.1") == 0 || strcmp(options->svr_address, "localhost") == 0) + if (strcmp(options->svr_address, "127.0.0.1") == 0 || + strcmp(options->svr_address, "localhost") == 0) #if PG_VERSION_NUM >= 110000 ExplainPropertyInteger("Local server startup cost", NULL, 10, es); #else @@ -751,94 +730,64 @@ mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) } /* - * mysqlEndForeignScan: Finish scanning foreign table and dispose - * objects used for this scan + * 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) - { - if (festate->table->_mysql_res) { - _mysql_free_result(festate->table->_mysql_res); - festate->table->_mysql_res = NULL; - } - } + 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); + mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* - * mysqlReScanForeignScan: Rescan table, possibly with new parameters + * mysqlReScanForeignScan + * Rescan table, possibly with new parameters */ static void mysqlReScanForeignScan(ForeignScanState *node) { - MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; - if (_mysql_stmt_execute(festate->stmt) != 0) - { - switch(_mysql_stmt_errno(festate->stmt)) - { - case CR_NO_ERROR: - break; + 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; - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(festate->conn)); - mysql_rel_connection(festate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(festate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - } - } - return; } /* - * mysqlGetForeignRelSize: Create a FdwPlan for a scan on the foreign table + * mysqlGetForeignRelSize + * Create a FdwPlan for a scan on the foreign table */ static void -mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) +mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, + Oid foreigntableid) { - StringInfoData sql; - double rows = 0; - double filtered = 0; - MYSQL *conn = NULL; - MYSQL_RES *result = NULL; - MYSQL_ROW row; - Bitmapset *attrs_used = NULL; - List *retrieved_attrs = NULL; - mysql_opt *options = NULL; - Oid userid = GetUserId(); - ForeignServer *server; - UserMapping *user; - ForeignTable *table; + 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; - MYSQL_FIELD *field; - int i; - int num_fields; - List *params_list = NULL; + ListCell *lc; fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; @@ -853,12 +802,14 @@ mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntablei /* Connect to the server */ conn = mysql_get_connection(server, user, options); - _mysql_query(conn, "SET sql_mode='ANSI_QUOTES'"); + mysql_query(conn, "SET sql_mode='ANSI_QUOTES'"); #if PG_VERSION_NUM >= 90600 - pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &attrs_used); + pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, + &attrs_used); #else - pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &attrs_used); + pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, + &attrs_used); #endif foreach(lc, baserel->baserestrictinfo) @@ -872,109 +823,93 @@ mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntablei } #if PG_VERSION_NUM >= 90600 - pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &fpinfo->attrs_used); + pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, + &fpinfo->attrs_used); #else - pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &fpinfo->attrs_used); + pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, + &fpinfo->attrs_used); #endif + foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - pull_varattnos((Node *) rinfo->clause, baserel->relid, &fpinfo->attrs_used); + + 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; + List *params_list = NULL; + initStringInfo(&sql); appendStringInfo(&sql, "EXPLAIN "); - mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, - &retrieved_attrs, NULL); + mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, + options->svr_table, &retrieved_attrs, NULL); + if (fpinfo->remote_conds) - mysql_append_where_clause(&sql, root, baserel, fpinfo->remote_conds, - true, ¶ms_list); + mysql_append_where_clause(&sql, root, baserel, + fpinfo->remote_conds, true, + ¶ms_list); - if (_mysql_query(conn, sql.data) != 0) - { - switch(_mysql_errno(conn)) - { - case CR_NO_ERROR: - break; + if (mysql_query(conn, sql.data) != 0) + mysql_error_print(conn); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - case CR_UNKNOWN_ERROR: - { - char *err = pstrdup(_mysql_error(conn)); - mysql_rel_connection(conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - default: - { - char *err = pstrdup(_mysql_error(conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - } - } - result = _mysql_store_result(conn); + result = mysql_store_result(conn); if (result) { + int num_fields; + /* - * MySQL provide numbers of rows per table invole in - * the statment, but we don't have problem with it - * because we are sending separate query per table - * in FDW. + * 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); + 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 (strcmp(field->name, "rows") == 0) - { - if (row[i]) - rows = atof(row[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) - { - if (row[i]) - filtered = atof(row[i]); - } + filtered = atof(row[i]); } } - _mysql_free_result(result); + mysql_free_result(result); } } if (rows > 0) rows = ((rows + 1) * filtered) / 100; else - rows = DEFAULTE_NUM_ROWS; + rows = DEFAULTE_NUM_ROWS; baserel->rows = rows; baserel->tuples = rows; } - static bool mysql_is_column_unique(Oid foreigntableid) { - StringInfoData sql; - MYSQL *conn = NULL; - MYSQL_RES *result = NULL; - MYSQL_ROW row; - mysql_opt *options = NULL; - Oid userid = GetUserId(); - ForeignServer *server; - UserMapping *user; - ForeignTable *table; + 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); @@ -989,68 +924,52 @@ mysql_is_column_unique(Oid foreigntableid) /* Build the query */ initStringInfo(&sql); - appendStringInfo(&sql, "EXPLAIN %s", options->svr_table); - if (_mysql_query(conn, sql.data) != 0) - { - switch(_mysql_errno(conn)) - { - case CR_NO_ERROR: - break; - - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - case CR_UNKNOWN_ERROR: - { - char *err = pstrdup(_mysql_error(conn)); - mysql_rel_connection(conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - default: - { - char *err = pstrdup(_mysql_error(conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - } - } + /* + * Construct the query by prefixing the database name so that it can lookup + * in correct database. + */ + appendStringInfo(&sql, "EXPLAIN %s.%s", options->svr_database, + options->svr_table); + if (mysql_query(conn, sql.data) != 0) + mysql_error_print(conn); - result = _mysql_store_result(conn); + result = mysql_store_result(conn); if (result) { - int num_fields = _mysql_num_fields(result); - row = _mysql_fetch_row(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); + mysql_free_result(result); return true; } } - _mysql_free_result(result); + mysql_free_result(result); } + return false; } /* - * mysqlEstimateCosts: Estimate the remote query cost + * mysqlEstimateCosts + * Estimate the remote query cost */ static void -mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid) +mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, + Cost *total_cost, Oid foreigntableid) { - mysql_opt *options; + mysql_opt *options; /* Fetch options */ options = mysql_get_options(foreigntableid); /* Local databases are probably faster */ - if (strcmp(options->svr_address, "127.0.0.1") == 0 || strcmp(options->svr_address, "localhost") == 0) + if (strcmp(options->svr_address, "127.0.0.1") == 0 || + strcmp(options->svr_address, "localhost") == 0) *startup_cost = 10; else *startup_cost = 25; @@ -1058,30 +977,32 @@ mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, C *total_cost = baserel->rows + *startup_cost; } - /* - * mysqlGetForeignPaths: Get the foreign paths + * mysqlGetForeignPaths + * Get the foreign paths */ static void -mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid) +mysqlGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, + Oid foreigntableid) { - Cost startup_cost; - Cost total_cost; + Cost startup_cost; + Cost total_cost; /* Estimate costs */ - mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, foreigntableid); + mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, + foreigntableid); /* Create a ForeignPath node and add it as only possible path */ add_path(baserel, (Path *) create_foreignscan_path(root, baserel, #if PG_VERSION_NUM >= 90600 - NULL, /* default pathtarget */ + NULL, /* default pathtarget */ #endif baserel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ - NULL, /* no outer rel either */ + baserel->lateral_relids, #if PG_VERSION_NUM >= 90500 NULL, /* no extra plan */ #endif @@ -1090,37 +1011,40 @@ mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid) /* - * mysqlGetForeignPlan: Get a foreign scan plan node + * mysqlGetForeignPlan + * Get a foreign scan plan node */ -static ForeignScan * -mysqlGetForeignPlan( - PlannerInfo *root - ,RelOptInfo *baserel - ,Oid foreigntableid - ,ForeignPath *best_path - ,List * tlist - ,List *scan_clauses #if PG_VERSION_NUM >= 90500 - ,Plan * outer_plan +static ForeignScan * +mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, + Oid foreigntableid, ForeignPath *best_path, + List *tlist, List *scan_clauses, Plan *outer_plan) +#else +static ForeignScan * +mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, + Oid foreigntableid, ForeignPath *best_path, + List *tlist, List *scan_clauses) #endif -) { - MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) baserel->fdw_private; - Index scan_relid = baserel->relid; - List *fdw_private; - List *local_exprs = NULL; - List *remote_exprs = NULL; - List *params_list = NULL; - List *remote_conds = NIL; - + MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private; + Index scan_relid = foreignrel->relid; + List *fdw_private; + List *local_exprs = NIL; + List *remote_exprs = NIL; + List *params_list = NIL; + List *fdw_scan_tlist = NIL; + List *remote_conds = NIL; StringInfoData sql; - mysql_opt *options; - List *retrieved_attrs = NIL; - ListCell *lc; + mysql_opt *options; + List *retrieved_attrs; + ListCell *lc; /* Fetch options */ options = mysql_get_options(foreigntableid); + /* Decide to execute function pushdown support in the target list. */ + fpinfo->is_tlist_func_pushdown = mysql_is_foreign_function_tlist(root, foreignrel, tlist); + /* * Build the query string to be sent for execution, and identify * expressions to be sent as parameters. @@ -1165,7 +1089,7 @@ mysqlGetForeignPlan( } else if (list_member_ptr(fpinfo->local_conds, rinfo)) local_exprs = lappend(local_exprs, rinfo->clause); - else if (mysql_is_foreign_expr(root, baserel, rinfo->clause)) + else if (mysql_is_foreign_expr(root, foreignrel, rinfo->clause)) { remote_conds = lappend(remote_conds, rinfo); remote_exprs = lappend(remote_exprs, rinfo->clause); @@ -1174,30 +1098,47 @@ mysqlGetForeignPlan( local_exprs = lappend(local_exprs, rinfo->clause); } + if (fpinfo->is_tlist_func_pushdown == true) + { + /* + * Join relation or upper relation - set scan_relid to 0. + */ + scan_relid = 0; + + fdw_scan_tlist = copyObject(tlist); + foreach(lc, fpinfo->local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + fdw_scan_tlist = add_to_flat_tlist(fdw_scan_tlist, + pull_var_clause((Node *) rinfo->clause, + PVC_RECURSE_PLACEHOLDERS)); + } + } + /* Cannot compile this code for plain PostgreSQL, which doesn't have is_tlist_pushdown member */ - if (baserel->is_tlist_pushdown) - mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, NULL, tlist); - else - mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs, NULL); + mysql_deparse_select(&sql, root, foreignrel, fpinfo->attrs_used, + options->svr_table, &retrieved_attrs, fdw_scan_tlist); if (remote_conds) - mysql_append_where_clause(&sql, root, baserel, remote_conds, - true, ¶ms_list); + mysql_append_where_clause(&sql, root, foreignrel, remote_conds, + true, ¶ms_list); - if (baserel->relid == root->parse->resultRelation && + if (foreignrel->relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || - root->parse->commandType == CMD_DELETE)) - { - /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ - appendStringInfoString(&sql, " FOR 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_make3(makeString(sql.data), retrieved_attrs, makeInteger(baserel->is_tlist_pushdown)); + fdw_private = list_make3(makeString(sql.data), retrieved_attrs, fdw_scan_tlist); + /* * Create the ForeignScan node from target list, local filtering * expressions, remote parameter expressions, and FDW private information. @@ -1206,36 +1147,32 @@ mysqlGetForeignPlan( * 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 #if PG_VERSION_NUM >= 90500 - ,baserel->is_tlist_pushdown ? tlist : NIL - ,NIL - ,outer_plan + return make_foreignscan(tlist, local_exprs, scan_relid, params_list, + fdw_private, fdw_scan_tlist, NIL, outer_plan); +#else + return make_foreignscan(tlist, local_exprs, scan_relid, params_list, + fdw_private); #endif - ); } /* - * mysqlAnalyzeForeignTable: Implement stats collection + * mysqlAnalyzeForeignTable + * Implement stats collection */ static bool -mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages) +mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, + BlockNumber *totalpages) { StringInfoData sql; - double table_size = 0; - MYSQL *conn; - MYSQL_RES *result; - MYSQL_ROW row; - Oid foreignTableId = RelationGetRelid(relation); - mysql_opt *options; - char *relname; - ForeignServer *server; - UserMapping *user; - ForeignTable *table; + 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); @@ -1243,57 +1180,42 @@ mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNu /* Fetch options */ options = mysql_get_options(foreignTableId); + 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 no table name specified, use the foreign table name */ - relname = options->svr_table; - if ( relname == NULL) - relname = RelationGetRelationName(relation); + if (mysql_query(conn, sql.data) != 0) + mysql_error_print(conn); - mysql_deparse_analyze(&sql, options->svr_database, relname); + result = mysql_store_result(conn); - if (_mysql_query(conn, sql.data) != 0) - { - switch(_mysql_errno(conn)) - { - case CR_NO_ERROR: - break; + /* + * 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))); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(conn)); - mysql_rel_connection(conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - } - } - result = _mysql_store_result(conn); if (result) { - row = _mysql_fetch_row(result); + MYSQL_ROW row; + + row = mysql_fetch_row(result); table_size = atof(row[0]); - _mysql_free_result(result); + mysql_free_result(result); } + *totalpages = table_size / MYSQL_BLKSIZ; + return false; } @@ -1304,13 +1226,13 @@ mysqlPlanForeignModify(PlannerInfo *root, int subplan_index) { - CmdType operation = plan->operation; - RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); - Relation rel; - List *targetAttrs = NULL; - StringInfoData sql; - char *attname; - Oid foreignTableId; + CmdType operation = plan->operation; + RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); + Relation rel; + List *targetAttrs = NIL; + StringInfoData sql; + char *attname; + Oid foreignTableId; initStringInfo(&sql); @@ -1318,7 +1240,7 @@ mysqlPlanForeignModify(PlannerInfo *root, * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ -#if PG_VERSION_NUM < 120000 +#if PG_VERSION_NUM < 130000 rel = heap_open(rte->relid, NoLock); #else rel = table_open(rte->relid, NoLock); @@ -1327,12 +1249,35 @@ mysqlPlanForeignModify(PlannerInfo *root, foreignTableId = RelationGetRelid(rel); if (!mysql_is_column_unique(foreignTableId)) - elog(ERROR, "first column of remote table must be unique for INSERT/UPDATE/DELETE operation"); + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("first column of remote table must be unique for INSERT/UPDATE/DELETE operation"))); - if (operation == CMD_INSERT) + /* + * 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; + TupleDesc tupdesc = RelationGetDescr(rel); + int attnum; + + /* + * If it is an UPDATE operation, check for row identifier column in + * target attribute list by calling getUpdateTargetAttrs(). + */ + if (operation == CMD_UPDATE) + getUpdateTargetAttrs(rte); for (attnum = 1; attnum <= tupdesc->natts; attnum++) { @@ -1344,33 +1289,12 @@ mysqlPlanForeignModify(PlannerInfo *root, } else if (operation == CMD_UPDATE) { -#if PG_VERSION_NUM >= 90500 - Bitmapset *tmpset = bms_copy(rte->updatedCols); -#else - Bitmapset *tmpset = bms_copy(rte->modifiedCols); -#endif - AttrNumber col; - - while ((col = bms_first_member(tmpset)) >= 0) - { - col += FirstLowInvalidHeapAttributeNumber; - if (col <= InvalidAttrNumber) /* shouldn't happen */ - elog(ERROR, "system-column update is not supported"); - /* - * We also disallow updates to the first column - */ - if (col == 1) /* shouldn't happen */ - elog(ERROR, "row identifier column update is not supported"); - - targetAttrs = lappend_int(targetAttrs, col); - } + targetAttrs = getUpdateTargetAttrs(rte); /* We also want the rowid column to be available for the update */ targetAttrs = lcons_int(1, targetAttrs); } else - { targetAttrs = lcons_int(1, targetAttrs); - } #if PG_VERSION_NUM >= 110000 attname = get_attname(foreignTableId, 1, false); @@ -1387,7 +1311,8 @@ mysqlPlanForeignModify(PlannerInfo *root, mysql_deparse_insert(&sql, root, resultRelation, rel, targetAttrs); break; case CMD_UPDATE: - mysql_deparse_update(&sql, root, resultRelation, rel, targetAttrs, attname); + mysql_deparse_update(&sql, root, resultRelation, rel, targetAttrs, + attname); break; case CMD_DELETE: mysql_deparse_delete(&sql, root, resultRelation, rel, attname); @@ -1398,20 +1323,22 @@ mysqlPlanForeignModify(PlannerInfo *root, } if (plan->returningLists) - elog(ERROR, "RETURNING is not supported by this FDW"); + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("RETURNING is not supported by this FDW"))); -#if PG_VERSION_NUM < 120000 +#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 + * mysqlBeginForeignModify + * Begin an insert/update/delete operation on a foreign table */ static void mysqlBeginForeignModify(ModifyTableState *mtstate, @@ -1420,19 +1347,19 @@ mysqlBeginForeignModify(ModifyTableState *mtstate, int subplan_index, int eflags) { - MySQLFdwExecState *fmstate = NULL; - EState *estate = mtstate->ps.state; - Relation rel = resultRelInfo->ri_RelationDesc; - AttrNumber n_params = 0; - Oid typefnoid = InvalidOid; - bool isvarlena = false; - ListCell *lc = NULL; - Oid foreignTableId = InvalidOid; - RangeTblEntry *rte; - Oid userid; - ForeignServer *server; - UserMapping *user; - ForeignTable *table; + MySQLFdwExecState *fmstate; + EState *estate = mtstate->ps.state; + Relation rel = resultRelInfo->ri_RelationDesc; + AttrNumber n_params; + Oid typefnoid = InvalidOid; + bool isvarlena = false; + ListCell *lc; + Oid foreignTableId = InvalidOid; + RangeTblEntry *rte; + Oid userid; + ForeignServer *server; + UserMapping *user; + ForeignTable *table; rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); @@ -1450,12 +1377,13 @@ mysqlBeginForeignModify(ModifyTableState *mtstate, if (eflags & EXEC_FLAG_EXPLAIN_ONLY) return; - /* Begin constructing MongoFdwModifyState. */ + /* Begin constructing MySQLFdwExecState. */ fmstate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); fmstate->rel = rel; fmstate->mysqlFdwOptions = mysql_get_options(foreignTableId); - fmstate->conn = mysql_get_connection(server, user, fmstate->mysqlFdwOptions); + 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); @@ -1463,11 +1391,13 @@ mysqlBeginForeignModify(ModifyTableState *mtstate, n_params = list_length(fmstate->retrieved_attrs) + 1; fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params); fmstate->p_nums = 0; +#if PG_VERSION_NUM >= 110000 fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", -#if PG_VERSION_NUM >= 110000 ALLOCSET_DEFAULT_SIZES); #else + fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, + "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); @@ -1476,8 +1406,9 @@ mysqlBeginForeignModify(ModifyTableState *mtstate, /* 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); + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(rel), + attnum - 1); Assert(!attr->attisdropped); @@ -1490,53 +1421,24 @@ mysqlBeginForeignModify(ModifyTableState *mtstate, n_params = list_length(fmstate->retrieved_attrs); /* Initialize mysql statment */ - fmstate->stmt = _mysql_stmt_init(fmstate->conn); + fmstate->stmt = mysql_stmt_init(fmstate->conn); if (!fmstate->stmt) - { - char *err = pstrdup(_mysql_error(fmstate->conn)); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to initialize the MySQL query: \n%s", err) - )); - } + 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) - { - switch(_mysql_stmt_errno(fmstate->stmt)) - { - case CR_NO_ERROR: - break; + if (mysql_stmt_prepare(fmstate->stmt, fmstate->query, + strlen(fmstate->query)) != 0) + mysql_stmt_error_print(fmstate, "failed to prepare the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - mysql_rel_connection(fmstate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to prepare the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to prepare the MySQL query: \n%s", err))); - } - break; - } - } resultRelInfo->ri_FdwState = fmstate; } - /* - * mysqlExecForeignInsert: Insert one row into a foreign table + * mysqlExecForeignInsert + * Insert one row into a foreign table */ static TupleTableSlot * mysqlExecForeignInsert(EState *estate, @@ -1544,96 +1446,43 @@ mysqlExecForeignInsert(EState *estate, TupleTableSlot *slot, TupleTableSlot *planSlot) { - MySQLFdwExecState *fmstate; - MYSQL_BIND *mysql_bind_buffer = NULL; - ListCell *lc; - Datum value = 0; - int n_params = 0; - MemoryContext oldcontext; + MySQLFdwExecState *fmstate; + MYSQL_BIND *mysql_bind_buffer; + ListCell *lc; + int n_params; + MemoryContext oldcontext; + bool *isnull; fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; n_params = list_length(fmstate->retrieved_attrs); oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt); - mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * n_params); + mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * n_params); + isnull = (bool *) palloc0(sizeof(bool) * n_params); - _mysql_query(fmstate->conn, "SET sql_mode='ANSI_QUOTES'"); + mysql_query(fmstate->conn, "SET sql_mode='ANSI_QUOTES'"); foreach(lc, fmstate->retrieved_attrs) { - int attnum = lfirst_int(lc) - 1; - - bool *isnull = (bool*) palloc0(sizeof(bool) * n_params); - Oid type = TupleDescAttr(slot->tts_tupleDescriptor, attnum)->atttypid; + 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]); + 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) - { - switch(_mysql_stmt_errno(fmstate->stmt)) - { - case CR_NO_ERROR: - break; + if (mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) + mysql_stmt_error_print(fmstate, "failed to bind the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - mysql_rel_connection(fmstate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to bind the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to bind the MySQL query: \n%s", err))); - } - break; - } - } /* Execute the query */ - if (_mysql_stmt_execute(fmstate->stmt) != 0) - { - switch(_mysql_stmt_errno(fmstate->stmt)) - { - case CR_NO_ERROR: - break; + if (mysql_stmt_execute(fmstate->stmt) != 0) + mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - mysql_rel_connection(fmstate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - } - } MemoryContextSwitchTo(oldcontext); MemoryContextReset(fmstate->temp_cxt); return slot; @@ -1646,111 +1495,145 @@ mysqlExecForeignUpdate(EState *estate, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; - Relation rel = resultRelInfo->ri_RelationDesc; - MYSQL_BIND *mysql_bind_buffer = NULL; - Oid foreignTableId = RelationGetRelid(rel); - bool is_null = false; - ListCell *lc = NULL; - int bindnum = 0; - Oid typeoid; - Datum value = 0; - int n_params = 0; - bool *isnull = NULL; - int i = 0; + Relation rel = resultRelInfo->ri_RelationDesc; + MYSQL_BIND *mysql_bind_buffer; + Oid foreignTableId = RelationGetRelid(rel); + bool is_null = false; + ListCell *lc; + int bindnum = 0; + Oid typeoid; + Datum value; + int n_params; + bool *isnull; + Datum new_value; + HeapTuple tuple; + Form_pg_attribute attr; + bool found_row_id_col = false; n_params = list_length(fmstate->retrieved_attrs); - mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * n_params); - isnull = palloc0(sizeof(bool) * n_params); + 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; + int attnum = lfirst_int(lc); + Oid type; - /* first attribute cannot be in target list attribute */ + /* + * The first attribute cannot be in the target list attribute. Set the + * found_row_id_col to true once we find it so that we can fetch the + * value later. + */ if (attnum == 1) + { + found_row_id_col = true; continue; + } type = TupleDescAttr(slot->tts_tupleDescriptor, attnum - 1)->atttypid; - value = slot_getattr(slot, attnum, (bool*)(&isnull[i])); + value = slot_getattr(slot, attnum, (bool *) (&isnull[bindnum])); - mysql_bind_sql_var(type, bindnum, value, mysql_bind_buffer, &isnull[i]); + mysql_bind_sql_var(type, bindnum, value, mysql_bind_buffer, + &isnull[bindnum]); bindnum++; - i++; } - /* Get the id that was passed up as a resjunk column */ + /* + * 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, 1, &is_null); - typeoid = get_atttype(foreignTableId, 1); + + 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) - { - char *err = pstrdup(_mysql_error(fmstate->conn)); + 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", err) - )); - } + errmsg("failed to bind the MySQL query: %s", + mysql_error(fmstate->conn)))); - if (_mysql_stmt_execute(fmstate->stmt) != 0) - { - switch(_mysql_stmt_errno(fmstate->stmt)) - { - case CR_NO_ERROR: - break; + /* Execute the query */ + if (mysql_stmt_execute(fmstate->stmt) != 0) + mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - mysql_rel_connection(fmstate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - } - } /* 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. + * 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. */ static void mysqlAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation) { - Var *var = NULL; - const char *attrname = NULL; - TargetEntry *tle = NULL; + Var *var; + const char *attrname; + TargetEntry *tle; /* * What we need is the rowid which is the first column */ Form_pg_attribute attr = - TupleDescAttr(RelationGetDescr(target_relation), 0); + TupleDescAttr(RelationGetDescr(target_relation), 0); /* Make a Var representing the desired value */ var = makeVar(parsetree->resultRelation, @@ -1765,16 +1648,15 @@ mysqlAddForeignUpdateTargets(Query *parsetree, tle = makeTargetEntry((Expr *) var, list_length(parsetree->targetList) + 1, - pstrdup(attrname), - true); + pstrdup(attrname), true); /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); } - /* - * MongoExecForeignDelete: Delete one row from a foreign table + * mysqlExecForeignDelete + * Delete one row from a foreign table */ static TupleTableSlot * mysqlExecForeignDelete(EState *estate, @@ -1782,69 +1664,39 @@ mysqlExecForeignDelete(EState *estate, TupleTableSlot *slot, TupleTableSlot *planSlot) { - MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; - Relation rel = resultRelInfo->ri_RelationDesc; - MYSQL_BIND *mysql_bind_buffer = NULL; - Oid foreignTableId = RelationGetRelid(rel); - bool is_null = false; - int bindnum = 0; - Oid typeoid; - Datum value = 0; + 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*) palloc0(sizeof(MYSQL_BIND) * 1); + 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, bindnum, value, mysql_bind_buffer, &is_null); + mysql_bind_sql_var(typeoid, 0, value, mysql_bind_buffer, &is_null); - if (_mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) - { - char *err = pstrdup(_mysql_error(fmstate->conn)); + 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", err) - )); - } + errmsg("failed to execute the MySQL query: %s", + mysql_error(fmstate->conn)))); - if (_mysql_stmt_execute(fmstate->stmt) != 0) - { - switch(_mysql_stmt_errno(fmstate->stmt)) - { - case CR_NO_ERROR: - break; + /* Execute the query */ + if (mysql_stmt_execute(fmstate->stmt) != 0) + mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - mysql_rel_connection(fmstate->conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - case CR_COMMANDS_OUT_OF_SYNC: - case CR_UNKNOWN_ERROR: - default: - { - char *err = pstrdup(_mysql_error(fmstate->conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - break; - } - } /* Return NULL if nothing was updated on the remote end */ return slot; } /* - * MongoEndForeignModify + * mysqlEndForeignModify * Finish an insert/update/delete operation on a foreign table */ static void @@ -1854,281 +1706,266 @@ mysqlEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo) if (festate && festate->stmt) { - _mysql_stmt_close(festate->stmt); + mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* - * Import a foreign schema (9.5+) + * mysqlImportForeignSchema + * Import a foreign schema (9.5+) */ #if PG_VERSION_NUM >= 90500 static List * mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) { - List *commands = NIL; - bool import_default = false; - bool import_not_null = true; - ForeignServer *server; - UserMapping *user; - mysql_opt *options = NULL; - MYSQL *conn; - StringInfoData buf; - MYSQL_RES *volatile res = NULL; - MYSQL_ROW row; - ListCell *lc; - char *err = NULL; - - /* Parse statement options */ - foreach(lc, stmt->options) - { - DefElem *def = (DefElem *) lfirst(lc); - - if (strcmp(def->defname, "import_default") == 0) - import_default = defGetBoolean(def); - else if (strcmp(def->defname, "import_not_null") == 0) - import_not_null = defGetBoolean(def); - else - ereport(ERROR, - (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), - errmsg("invalid option \"%s\"", def->defname))); - } - - /* - * Get connection to the foreign server. Connection manager will - * establish new connection if necessary. - */ - server = GetForeignServer(serverOid); - user = GetUserMapping(GetUserId(), server->serverid); - options = mysql_get_options(serverOid); - 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) - { - switch(_mysql_errno(conn)) - { - case CR_NO_ERROR: - break; - - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - case CR_UNKNOWN_ERROR: - err = pstrdup(_mysql_error(conn)); - mysql_rel_connection(conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - break; - - case CR_COMMANDS_OUT_OF_SYNC: - default: - err = pstrdup(_mysql_error(conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - } - - 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(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'" - " ELSE c.DATA_TYPE" - " END," - " c.COLUMN_TYPE," - " IF(c.IS_NULLABLE = 'NO', 't', 'f')," - " c.COLUMN_DEFAULT" - " FROM" - " information_schema.TABLES AS t" - " JOIN" - " information_schema.COLUMNS AS c" - " ON" - " t.TABLE_CATALOG <=> c.TABLE_CATALOG AND t.TABLE_SCHEMA <=> c.TABLE_SCHEMA AND t.TABLE_NAME <=> c.TABLE_NAME" - " WHERE" - " t.TABLE_SCHEMA = '%s'", - stmt->remote_schema); - - /* Apply restrictions for LIMIT TO and EXCEPT */ - if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || - stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) - { - bool first_item = true; - - appendStringInfoString(&buf, " AND t.TABLE_NAME "); - if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) - appendStringInfoString(&buf, "NOT "); - appendStringInfoString(&buf, "IN ("); - - /* Append list of table names within IN clause */ - foreach(lc, stmt->table_list) - { - RangeVar *rv = (RangeVar *) lfirst(lc); - - if (first_item) - first_item = false; - else - appendStringInfoString(&buf, ", "); - - appendStringInfo(&buf, "'%s'", rv->relname); - } - appendStringInfoChar(&buf, ')'); - } - - /* Append ORDER BY at the end of query to ensure output ordering */ - appendStringInfo(&buf, " ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION"); - - /* Fetch the data */ - if (_mysql_query(conn, buf.data) != 0) - { - switch(_mysql_errno(conn)) - { - case CR_NO_ERROR: - break; - - case CR_OUT_OF_MEMORY: - case CR_SERVER_GONE_ERROR: - case CR_SERVER_LOST: - case CR_UNKNOWN_ERROR: - err = pstrdup(_mysql_error(conn)); - mysql_rel_connection(conn); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - break; - - case CR_COMMANDS_OUT_OF_SYNC: - default: - err = pstrdup(_mysql_error(conn)); - ereport(ERROR, - (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), - errmsg("failed to execute the MySQL query: \n%s", err))); - } - } - - res = _mysql_store_result(conn); - row = _mysql_fetch_row(res); - while (row) - { - char *tablename = row[0]; - bool first_item = true; - - 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 table has no columns, we'll see nulls here */ - if (row[1] == NULL) - continue; - - attname = row[1]; - typename = row[2]; - - if (strcmp(typename,"char") == 0 || strcmp(typename,"varchar") == 0) - typename = row[3]; - - typedfn = row[3]; - attnotnull = row[4]; - attdefault = row[5] == NULL ? (char *) NULL : row[5]; - - if (strncmp(typedfn, "enum(", 5) == 0) - ereport(NOTICE, (errmsg("If you encounter an error, you may need to execute the following first:\n" - "DO $$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))); - - if (first_item) - first_item = false; - else - appendStringInfoString(&buf, ",\n"); - - /* Print column name and type */ - appendStringInfo(&buf, " %s %s", - quote_identifier(attname), - typename); - - /* Add DEFAULT if needed */ - if (import_default && attdefault != NULL) - appendStringInfo(&buf, " DEFAULT %s", attdefault); - - /* Add NOT NULL if needed */ - if (import_not_null && attnotnull[0] == 't') - appendStringInfoString(&buf, " NOT NULL"); - } - while ((row = _mysql_fetch_row(res)) && - (strcmp(row[0], tablename) == 0)); - - /* - * 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_rel_connection(conn); - - return commands; + List *commands = NIL; + bool import_default = false; + bool import_not_null = true; + ForeignServer *server; + UserMapping *user; + mysql_opt *options; + MYSQL *conn; + StringInfoData buf; + MYSQL_RES *volatile res = NULL; + MYSQL_ROW row; + ListCell *lc; + + /* Parse statement options */ + foreach(lc, stmt->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "import_default") == 0) + import_default = defGetBoolean(def); + else if (strcmp(def->defname, "import_not_null") == 0) + import_not_null = defGetBoolean(def); + else + ereport(ERROR, + (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), + errmsg("invalid option \"%s\"", def->defname))); + } + + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + server = GetForeignServer(serverOid); + user = GetUserMapping(GetUserId(), server->serverid); + options = mysql_get_options(serverOid); + 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'" + " ELSE c.DATA_TYPE" + " END," + " c.COLUMN_TYPE," + " IF(c.IS_NULLABLE = 'NO', 't', 'f')," + " c.COLUMN_DEFAULT" + " FROM" + " information_schema.TABLES AS t" + " JOIN" + " information_schema.COLUMNS AS c" + " ON" + " t.TABLE_CATALOG <=> c.TABLE_CATALOG AND t.TABLE_SCHEMA <=> c.TABLE_SCHEMA AND t.TABLE_NAME <=> c.TABLE_NAME" + " WHERE" + " t.TABLE_SCHEMA = '%s'", + stmt->remote_schema); + + /* Apply restrictions for LIMIT TO and EXCEPT */ + if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || + stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) + { + bool first_item = true; + + appendStringInfoString(&buf, " AND t.TABLE_NAME "); + if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) + appendStringInfoString(&buf, "NOT "); + appendStringInfoString(&buf, "IN ("); + + /* Append list of table names within IN clause */ + foreach(lc, stmt->table_list) + { + RangeVar *rv = (RangeVar *) lfirst(lc); + + if (first_item) + first_item = false; + else + appendStringInfoString(&buf, ", "); + + appendStringInfo(&buf, "'%s'", rv->relname); + } + appendStringInfoChar(&buf, ')'); + } + + /* Append ORDER BY at the end of query to ensure output ordering */ + appendStringInfo(&buf, " ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION"); + + /* Fetch the data */ + if (mysql_query(conn, buf.data) != 0) + mysql_error_print(conn); + + res = mysql_store_result(conn); + row = mysql_fetch_row(res); + while (row) + { + char *tablename = row[0]; + bool first_item = true; + + 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 table has no columns, we'll see nulls here */ + if (row[1] == NULL) + continue; + + attname = row[1]; + typename = row[2]; + + if (strcmp(typename, "char") == 0 || strcmp(typename, "varchar") == 0) + typename = row[3]; + + typedfn = row[3]; + attnotnull = row[4]; + attdefault = row[5] == NULL ? (char *) NULL : row[5]; + + if (strncmp(typedfn, "enum(", 5) == 0) + ereport(NOTICE, + (errmsg("error while generating the table definition"), + errhint("If you encounter an error, you may need to execute the following first:\nDO $$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE typname = '%s') THEN CREATE TYPE %s AS %s; END IF; END$$;\n", + typename, typename, typedfn))); + + if (first_item) + first_item = false; + else + appendStringInfoString(&buf, ",\n"); + + /* Print column name and type */ + appendStringInfo(&buf, " %s %s", quote_identifier(attname), + typename); + + /* Add DEFAULT if needed */ + if (import_default && attdefault != NULL) + appendStringInfo(&buf, " DEFAULT %s", attdefault); + + /* Add NOT NULL if needed */ + if (import_not_null && attnotnull[0] == 't') + appendStringInfoString(&buf, " NOT NULL"); + } + while ((row = mysql_fetch_row(res)) && + (strcmp(row[0], tablename) == 0)); + + /* + * Add server name and table-level options. We specify remote + * database and table name as options (the latter to ensure that + * renaming the foreign table doesn't break the association). + */ + appendStringInfo(&buf, + "\n) SERVER %s OPTIONS (dbname '%s', table_name '%s');\n", + quote_identifier(server->servername), + stmt->remote_schema, + tablename); + + commands = lappend(commands, pstrdup(buf.data)); + } + + /* Clean up */ + mysql_free_result(res); + res = NULL; + resetStringInfo(&buf); + + mysql_release_connection(conn); + + return commands; +} +#endif + +#if PG_VERSION_NUM >= 110000 +/* + * mysqlBeginForeignInsert + * Prepare for an insert operation triggered by partition routing + * or COPY FROM. + * + * This is not yet supported, so raise an error. + */ +static void +mysqlBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo) +{ + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("COPY and foreign partition routing not supported in mysql_fdw"))); +} + +/* + * mysqlEndForeignInsert + * BeginForeignInsert() is not yet implemented, hence we do not + * have anything to cleanup as of now. We throw an error here just + * to make sure when we do that we do not forget to cleanup + * resources. + */ +static void +mysqlEndForeignInsert(EState *estate, ResultRelInfo *resultRelInfo) +{ + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("COPY and foreign partition routing not supported in mysql_fdw"))); } #endif @@ -2197,12 +2034,9 @@ process_query_params(ExprContext *econtext, MYSQL_BIND **mysql_bind_buf, Oid *param_types) { -// int nestlevel; int i; ListCell *lc; -// nestlevel = set_transmission_modes(); - i = 0; foreach(lc, param_exprs) { @@ -2216,7 +2050,8 @@ process_query_params(ExprContext *econtext, #else expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL); #endif - mysql_bind_sql_var(param_types[i], i, expr_value, *mysql_bind_buf, &isNull); + mysql_bind_sql_var(param_types[i], i, expr_value, *mysql_bind_buf, + &isNull); /* * Get string representation of each parameter value by invoking @@ -2228,17 +2063,16 @@ process_query_params(ExprContext *econtext, param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value); i++; } - -// reset_transmission_modes(nestlevel); } /* - * Create cursor for node's query with current parameter values. + * Process the query params and bind the same with the statement, if any. + * Also, execute the statement. */ static void -create_cursor(ForeignScanState *node) +bind_stmt_params_and_exec(ForeignScanState *node) { - MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; + MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; ExprContext *econtext = node->ss.ps.ps_ExprContext; int numParams = festate->numParams; const char **values = festate->param_values; @@ -2255,7 +2089,7 @@ create_cursor(ForeignScanState *node) oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * numParams); + mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * numParams); process_query_params(econtext, festate->param_flinfo, @@ -2264,13 +2098,63 @@ create_cursor(ForeignScanState *node) &mysql_bind_buffer, festate->param_types); - _mysql_stmt_bind_param(festate->stmt, mysql_bind_buffer); - - /* Mark the cursor as created, and show no tuples have been retrieved */ - festate->cursor_exists = true; + 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"); + } + else + { + /* Check the results of query has warning or not */ + if(mysql_warning_count(festate->conn) > 0) + { + MYSQL_RES *result = NULL; + + if (mysql_query(festate->conn, "SHOW WARNINGS")) + { + mysql_error_print(festate->conn); + } + result = mysql_store_result(festate->conn); + if (result) + { + /* + * MySQL provide numbers of rows per table invole in + * the statment, but we don't have problem with it + * because we are sending separate query per table + * in FDW. + */ + MYSQL_ROW row; + unsigned int num_fields; + unsigned int i; + + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + for(i = 0; i < num_fields; i++) + { + /* Check warning of query */ + if (strcmp(row[i], "Division by 0") == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + } + } + mysql_free_result(result); + } + } + } + + + /* Mark the query as executed */ + festate->query_executed = true; } Datum @@ -2278,3 +2162,92 @@ mysql_fdw_version(PG_FUNCTION_ARGS) { PG_RETURN_INT32(CODE_VERSION); } + +static void +mysql_error_print(MYSQL *conn) +{ + switch (mysql_errno(conn)) + { + case CR_NO_ERROR: + /* Should not happen, though give some message */ + elog(ERROR, "unexpected error code"); + break; + case CR_OUT_OF_MEMORY: + case CR_SERVER_GONE_ERROR: + case CR_SERVER_LOST: + case CR_UNKNOWN_ERROR: + mysql_release_connection(conn); + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("failed to execute the MySQL query: \n%s", + mysql_error(conn)))); + break; + case CR_COMMANDS_OUT_OF_SYNC: + default: + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("failed to execute the MySQL query: \n%s", + mysql_error(conn)))); + } +} + +static void +mysql_stmt_error_print(MySQLFdwExecState *festate, const char *msg) +{ + switch (mysql_stmt_errno(festate->stmt)) + { + case CR_NO_ERROR: + /* Should not happen, though give some message */ + elog(ERROR, "unexpected error code"); + break; + case CR_OUT_OF_MEMORY: + case CR_SERVER_GONE_ERROR: + case CR_SERVER_LOST: + case CR_UNKNOWN_ERROR: + mysql_release_connection(festate->conn); + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("%s: \n%s", msg, mysql_error(festate->conn)))); + break; + case CR_COMMANDS_OUT_OF_SYNC: + default: + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("%s: \n%s", msg, mysql_error(festate->conn)))); + break; + } +} + +/* + * getUpdateTargetAttrs + * Returns the list of attribute numbers of the columns being updated. + */ +static List * +getUpdateTargetAttrs(RangeTblEntry *rte) +{ + List *targetAttrs = NIL; + +#if PG_VERSION_NUM >= 90500 + Bitmapset *tmpset = bms_copy(rte->updatedCols); +#else + Bitmapset *tmpset = bms_copy(rte->modifiedCols); +#endif + AttrNumber col; + + while ((col = bms_first_member(tmpset)) >= 0) + { + col += FirstLowInvalidHeapAttributeNumber; + if (col <= InvalidAttrNumber) /* shouldn't happen */ + elog(ERROR, "system-column update is not supported"); + + /* We also disallow updates to the first column */ + if (col == 1) + ereport(ERROR, + (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), + errmsg("row identifier column update is not supported"))); + + targetAttrs = lappend_int(targetAttrs, col); + } + + return targetAttrs; +} diff --git a/mysql_fdw.control b/mysql_fdw.control index 6f703e5..044dd83 100644 --- a/mysql_fdw.control +++ b/mysql_fdw.control @@ -4,8 +4,7 @@ # Foreign-data wrapper for remote MySQL servers # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group -# -# Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. +# Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. # # IDENTIFICATION # mysql_fdw.control diff --git a/mysql_fdw.h b/mysql_fdw.h index 176cea3..9eaaf6d 100644 --- a/mysql_fdw.h +++ b/mysql_fdw.h @@ -4,15 +4,13 @@ * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw.h * *------------------------------------------------------------------------- */ - #ifndef MYSQL_FDW_H #define MYSQL_FDW_H @@ -26,14 +24,14 @@ #undef list_free #include "access/tupdesc.h" +#include "fmgr.h" #include "foreign/foreign.h" #include "lib/stringinfo.h" #if PG_VERSION_NUM < 120000 - #include "nodes/relation.h" +#include "nodes/relation.h" #else - #include "nodes/pathnodes.h" +#include "nodes/pathnodes.h" #endif - #include "utils/rel.h" #define MYSQL_PREFETCH_ROWS 100 @@ -44,166 +42,209 @@ #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) +#define mysql_warning_count (*_mysql_warning_count) + /* * 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 */ - - // 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 */ + 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 */ + + /* 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; + Datum value; unsigned long length; - bool is_null; - bool error; - MYSQL_BIND *_mysql_bind; + bool is_null; + bool error; + MYSQL_BIND *mysql_bind; } mysql_column; typedef struct mysql_table { - MYSQL_RES *_mysql_res; - MYSQL_FIELD *_mysql_fields; - + MYSQL_RES *mysql_res; + MYSQL_FIELD *mysql_fields; mysql_column *column; - MYSQL_BIND *_mysql_bind; + MYSQL_BIND *mysql_bind; } mysql_table; /* - * FDW-specific information for ForeignScanState + * 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 */ - Relation rel; /* relcache entry for the foreign table */ - List *retrieved_attrs; /* list of target attribute numbers */ - - bool cursor_exists; /* have we created the cursor? */ - 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 */ - - List *attr_list; /* query attribute list */ - List *column_list; /* Column list of MySQL Column structures */ - bool is_tlist_pushdown; /* pushdown target list or not */ + MYSQL *conn; /* MySQL connection handle */ + MYSQL_STMT *stmt; /* MySQL prepared stament handle */ + mysql_table *table; + char *query; /* Query string */ + Relation rel; /* relcache entry for the foreign table */ + 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 */ + + List *attr_list; /* query attribute list */ + List *column_list; /* Column list of MySQL Column structures */ + bool is_tlist_pushdown; /* pushdown target list or not */ /* working memory context */ - MemoryContext temp_cxt; /* context for per-tuple temporary data */ + MemoryContext temp_cxt; /* context for per-tuple temporary data */ } MySQLFdwExecState; /* MySQL Column List */ typedef struct MySQLColumn { - int attnum; /* Attribute number */ - char *attname; /* Attribute name */ - int atttype; /* Attribute type */ + int attnum; /* Attribute number */ + char *attname; /* Attribute name */ + int atttype; /* Attribute type */ } MySQLColumn; -extern bool mysql_is_foreign_expr(PlannerInfo *root, - RelOptInfo *baserel, - Expr *expr); - - -int ((*_mysql_options)(MYSQL *mysql,enum mysql_option option, const void *arg)); -int ((*_mysql_stmt_prepare)(MYSQL_STMT *stmt, const char *query, unsigned long length)); -int ((*_mysql_stmt_execute)(MYSQL_STMT *stmt)); -int ((*_mysql_stmt_fetch)(MYSQL_STMT *stmt)); -int ((*_mysql_query)(MYSQL *mysql, const char *q)); -bool ((*_mysql_stmt_attr_set)(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr)); -bool ((*_mysql_stmt_close)(MYSQL_STMT * stmt)); -bool ((*_mysql_stmt_reset)(MYSQL_STMT * stmt)); -bool ((*_mysql_free_result)(MYSQL_RES *result)); -bool ((*_mysql_stmt_bind_param)(MYSQL_STMT *stmt, MYSQL_BIND * bnd)); -bool ((*_mysql_stmt_bind_result)(MYSQL_STMT *stmt, MYSQL_BIND * bnd)); - -MYSQL_STMT *((*_mysql_stmt_init)(MYSQL *mysql)); -MYSQL_RES *((*_mysql_stmt_result_metadata)(MYSQL_STMT *stmt)); -int ((*_mysql_stmt_store_result)(MYSQL *mysql)); -MYSQL_ROW ((*_mysql_fetch_row)(MYSQL_RES *result)); -MYSQL_FIELD *((*_mysql_fetch_field)(MYSQL_RES *result)); -MYSQL_FIELD *((*_mysql_fetch_fields)(MYSQL_RES *result)); -const char *((*_mysql_error)(MYSQL *mysql)); -void ((*_mysql_close)(MYSQL *sock)); -MYSQL_RES* ((*_mysql_store_result)(MYSQL *mysql)); - -MYSQL *((*_mysql_init)(MYSQL *mysql)); -bool ((*_mysql_ssl_set)(MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher)); -MYSQL *((*_mysql_real_connect)(MYSQL *mysql, - const char *host, - const char *user, - const char *passwd, - const char *db, - unsigned int port, - const char *unix_socket, - unsigned long clientflag)); - -const char *((*_mysql_get_host_info)(MYSQL *mysql)); -const char *((*_mysql_get_server_info)(MYSQL *mysql)); -int ((*_mysql_get_proto_info)(MYSQL *mysql)); - -unsigned int ((*_mysql_stmt_errno)(MYSQL_STMT *stmt)); -unsigned int ((*_mysql_errno)(MYSQL *mysql)); -unsigned int ((*_mysql_num_fields)(MYSQL_RES *result)); -unsigned int ((*_mysql_num_rows)(MYSQL_RES *result)); +extern bool mysql_is_foreign_function_tlist(PlannerInfo *root, + RelOptInfo *baserel, + List *tlist); +extern int ((mysql_options) (MYSQL *mysql, enum mysql_option option, + const void *arg)); +extern int ((mysql_stmt_prepare) (MYSQL_STMT *stmt, const char *query, + unsigned long length)); +extern int ((mysql_stmt_execute) (MYSQL_STMT *stmt)); +extern int ((mysql_stmt_fetch) (MYSQL_STMT *stmt)); +extern int ((mysql_query) (MYSQL *mysql, const char *q)); +extern bool ((mysql_stmt_attr_set) (MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *attr)); +extern bool ((mysql_stmt_close) (MYSQL_STMT *stmt)); +extern bool ((mysql_stmt_reset) (MYSQL_STMT *stmt)); +extern bool ((mysql_free_result) (MYSQL_RES *result)); +extern bool ((mysql_stmt_bind_param) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); +extern bool ((mysql_stmt_bind_result) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); + +extern MYSQL_STMT *((mysql_stmt_init) (MYSQL *mysql)); +extern MYSQL_RES *((mysql_stmt_result_metadata) (MYSQL_STMT *stmt)); +extern int ((mysql_stmt_store_result) (MYSQL *mysql)); +extern MYSQL_ROW((mysql_fetch_row) (MYSQL_RES *result)); +extern MYSQL_FIELD *((mysql_fetch_field) (MYSQL_RES *result)); +extern MYSQL_FIELD *((mysql_fetch_fields) (MYSQL_RES *result)); +extern const char *((mysql_error) (MYSQL *mysql)); +extern void ((mysql_close) (MYSQL *sock)); +extern MYSQL_RES *((mysql_store_result) (MYSQL *mysql)); +extern MYSQL *((mysql_init) (MYSQL *mysql)); +extern bool ((mysql_ssl_set) (MYSQL *mysql, const char *key, const char *cert, + const char *ca, const char *capath, + const char *cipher)); +extern MYSQL *((mysql_real_connect) (MYSQL *mysql, const char *host, + const char *user, const char *passwd, + const char *db, unsigned int port, + const char *unix_socket, + unsigned long clientflag)); + +extern const char *((mysql_get_host_info) (MYSQL *mysql)); +extern const char *((mysql_get_server_info) (MYSQL *mysql)); +extern int ((mysql_get_proto_info) (MYSQL *mysql)); + +extern unsigned int ((mysql_stmt_errno) (MYSQL_STMT *stmt)); +extern unsigned int ((mysql_errno) (MYSQL *mysql)); +extern unsigned int ((mysql_num_fields) (MYSQL_RES *result)); +extern unsigned int ((mysql_num_rows) (MYSQL_RES *result)); +extern unsigned int ((mysql_warning_count)(MYSQL *mysql)); /* option.c headers */ extern bool mysql_is_valid_option(const char *option, Oid context); extern mysql_opt *mysql_get_options(Oid foreigntableid); /* depare.c headers */ -extern void mysql_deparse_select(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, - Bitmapset *attrs_used, char *svr_table, List **retrieved_attrs, List *tlist); -extern void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs); -extern void mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, char *attname); -extern void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, char *name); -extern void mysql_append_where_clause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, - bool is_first,List **params); +extern void mysql_deparse_select(StringInfo buf, PlannerInfo *root, + RelOptInfo *baserel, Bitmapset *attrs_used, + char *svr_table, List **retrieved_attrs, List *tlist); +extern void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + List *targetAttrs); +extern void mysql_deparse_update(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + List *targetAttrs, char *attname); +extern void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, char *name); +extern void mysql_append_where_clause(StringInfo buf, PlannerInfo *root, + RelOptInfo *baserel, List *exprs, + bool is_first, List **params); extern void mysql_deparse_analyze(StringInfo buf, char *dbname, char *relname); +extern bool mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, + Expr *expr); /* connection.c headers */ -MYSQL *mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt); -MYSQL *mysql_connect(char *svr_address, char *svr_username, char *svr_password, char *svr_database, - int svr_port, bool svr_sa, char *svr_init_command, - char *ssl_key, char *ssl_cert, char *ssl_ca, char *ssl_capath, - char *ssl_cipher); -void mysql_cleanup_connection(void); -void mysql_rel_connection(MYSQL *conn); - -#if PG_VERSION_NUM < 110000 /* TupleDescAttr is defined from PG version 11 */ - #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) +MYSQL *mysql_get_connection(ForeignServer *server, UserMapping *user, + mysql_opt *opt); +MYSQL *mysql_connect(mysql_opt *opt); +void mysql_cleanup_connection(void); +void mysql_release_connection(MYSQL *conn); + +#if PG_VERSION_NUM < 110000 /* TupleDescAttr is defined from PG version 11 */ +#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif -#endif /* MYSQL_FDW_H */ +#endif /* MYSQL_FDW_H */ diff --git a/mysql_init.sh b/mysql_init.sh index a970f19..87a1a27 100755 --- a/mysql_init.sh +++ b/mysql_init.sh @@ -1,7 +1,63 @@ #!/bin/sh -export MYSQL_PWD="bar" -mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE department(department_id int, department_name text, PRIMARY KEY (department_id))" -mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE employee(emp_id int, emp_name text, emp_dept_id int, PRIMARY KEY (emp_id))" -mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE empdata (emp_id int, emp_dat blob, PRIMARY KEY (emp_id))" -mysql -h 127.0.0.1 -u foo -D testdb -e "CREATE TABLE numbers (a int PRIMARY KEY, b varchar(255))" +export MYSQL_PWD="edb" +MYSQL_HOST="localhost" +MYSQL_PORT="3306" +MYSQL_USER_NAME="edb" +# Below commands must be run first time to create mysql_fdw_regress and mysql_fdw_regress1 databases +# used in regression tests with edb user and edb password. +# --connect to mysql with root user +# mysql -u root -p + +# --run below +# CREATE DATABASE mysql_fdw_regress; +# CREATE DATABASE mysql_fdw_regress1; +# SET GLOBAL validate_password.policy = LOW; +# SET GLOBAL validate_password.length = 1; +# SET GLOBAL validate_password.mixed_case_count = 0; +# SET GLOBAL validate_password.number_count = 0; +# SET GLOBAL validate_password.special_char_count = 0; +# CREATE USER 'edb'@'localhost' IDENTIFIED BY 'edb'; +# GRANT ALL PRIVILEGES ON mysql_fdw_regress.* TO 'edb'@'localhost'; +# GRANT ALL PRIVILEGES ON mysql_fdw_regress1.* TO 'edb'@'localhost'; + +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS mysql_test;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS empdata;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS numbers;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test_tbl2;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test_tbl1;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS student;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS numbers;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS enum_t1;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS student1;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS enum_t2;" + +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE mysql_test(a int primary key, b int);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO mysql_test(a,b) VALUES (1,1);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE empdata (emp_id int, emp_dat blob, PRIMARY KEY (emp_id));" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE numbers (a int PRIMARY KEY, b varchar(255));" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test_tbl1 (c1 INT primary key, c2 VARCHAR(10), c3 CHAR(9), c4 MEDIUMINT, c5 DATE, c6 DECIMAL(10,5), c7 INT, c8 SMALLINT);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test_tbl2 (c1 INT primary key, c2 TEXT, c3 TEXT);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE student (stu_id int PRIMARY KEY, stu_name text, stu_dept int);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE numbers (a int, b varchar(255));" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE enum_t1 (id int PRIMARY KEY, size ENUM('small', 'medium', 'large'));" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE student1 (stu_id varchar(10) PRIMARY KEY, stu_name text, stu_dept int);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE enum_t2 (id int PRIMARY KEY, size ENUM('S', 'M', 'L'));" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO enum_t2 VALUES (10, 'S'),(20, 'M'),(30, 'M');" + +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS s3;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE s3(id int PRIMARY KEY, tag1 text, value1 float, value2 int, value3 float, value4 int, str1 text, str2 text);" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (0, 'a', 0.1, 100, -0.1, -100, '---XYZ---', ' XYZ ');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (1, 'a', 0.2, 100, -0.2, -100, '---XYZ---', ' XYZ ');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (2, 'a', 0.3, 100, -0.3, -100, '---XYZ---', ' XYZ ');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (3, 'b', 1.1, 200, -1.1, -200, '---XYZ---', ' XYZ ');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (4, 'b', 2.2, 200, -2.2, -200, '---XYZ---', ' XYZ ');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO s3 VALUES (5, 'b', 3.3, 200, -3.3, -200, '---XYZ---', ' XYZ ');" + +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS ftextsearch;" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE ftextsearch(id int UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, content TEXT, FULLTEXT (content));" + +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO ftextsearch (content) VALUES ('So many men, so many minds.');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO ftextsearch (content) VALUES ('Failure teaches success.');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO ftextsearch (content) VALUES ('It is no use cring over spilt mik.');" +mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -D $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO ftextsearch (content) VALUES ('The early bird catches the worm.');" diff --git a/mysql_query.c b/mysql_query.c index 8c25f5c..e3c3ecb 100644 --- a/mysql_query.c +++ b/mysql_query.c @@ -1,95 +1,42 @@ /*------------------------------------------------------------------------- * * mysql_query.c - * Foreign-data wrapper for remote MySQL servers + * Type handling for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, 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 -#include - -#include "access/reloptions.h" -#include "catalog/pg_type.h" -#include "commands/defrem.h" -#include "commands/explain.h" -#include "commands/vacuum.h" -#include "foreign/fdwapi.h" -#include "foreign/foreign.h" -#include "nodes/makefuncs.h" -#include "optimizer/cost.h" -#include "optimizer/pathnode.h" -#include "optimizer/plancat.h" -#include "optimizer/planmain.h" -#include "optimizer/restrictinfo.h" -#include "storage/ipc.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/numeric.h" -#include "utils/date.h" -#include "utils/hsearch.h" -#include "utils/syscache.h" -#include "utils/lsyscache.h" -#include "utils/rel.h" -#include "utils/timestamp.h" -#include "utils/formatting.h" -#include "utils/memutils.h" #include "access/htup_details.h" -#include "access/sysattr.h" -#include "commands/defrem.h" -#include "commands/explain.h" -#include "commands/vacuum.h" -#include "foreign/fdwapi.h" -#include "funcapi.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "nodes/nodeFuncs.h" -#include "optimizer/cost.h" -#include "optimizer/pathnode.h" -#include "optimizer/paths.h" -#include "optimizer/planmain.h" -#include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" +#include "catalog/pg_type.h" +#include "mysql_query.h" #if PG_VERSION_NUM < 120000 - #include "optimizer/var.h" +#include "optimizer/var.h" #else - #include "optimizer/optimizer.h" +#include "optimizer/optimizer.h" #endif -#include "parser/parsetree.h" -#include "utils/builtins.h" -#include "utils/guc.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "optimizer/pathnode.h" -#include "optimizer/restrictinfo.h" -#include "optimizer/planmain.h" -#include "catalog/pg_type.h" -#include "funcapi.h" - -#include "miscadmin.h" -#include "postmaster/syslogger.h" -#include "storage/fd.h" #include "utils/builtins.h" +#include "utils/date.h" #include "utils/datetime.h" - - -#include "mysql_fdw.h" -#include "mysql_query.h" - +#include "utils/lsyscache.h" +#include "utils/syscache.h" #define DATE_MYSQL_PG(x, y) \ do { \ @@ -101,41 +48,41 @@ x->minute = y.tm_min; \ x->second = y.tm_sec; \ } while(0); - static int32 mysql_from_pgtyp(Oid type); -static int dec_bin(int n); -static int bin_dec(int n); +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 + * 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 = 0; - Datum valueDatum = 0; - regproc typeinput; - HeapTuple tuple; - int typemod; - char str[MAXDATELEN]; + Datum value_datum; + Datum valueDatum; + regproc typeinput; + HeapTuple tuple; + int typemod; + char str[MAXDATELEN]; /* get the type's output function */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(pgtyp)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for type%u", pgtyp); - typeinput = ((Form_pg_type)GETSTRUCT(tuple))->typinput; - typemod = ((Form_pg_type)GETSTRUCT(tuple))->typtypmod; + typeinput = ((Form_pg_type) GETSTRUCT(tuple))->typinput; + typemod = ((Form_pg_type) GETSTRUCT(tuple))->typtypmod; ReleaseSysCache(tuple); switch (pgtyp) { /* - * MySQL gives BIT / BIT(n) data type as decimal value. The only way to - * retrieve this value is to use BIN, OCT or HEX function in MySQL, otherwise - * mysql client shows the actual decimal value, which could be a non - printable character. - * For exmple in MySQL + * 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'; @@ -146,349 +93,382 @@ mysql_convert_to_pg(Oid pgtyp, int pgtypmod, mysql_column *column) * | 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. + * 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: SET_VARSIZE(column->value, column->length + VARHDRSZ); return PointerGetDatum(column->value); case BITOID: - sprintf(str, "%d", dec_bin(*((int*)column->value))); - valueDatum = CStringGetDatum((char*)str); - break; + sprintf(str, "%d", dec_bin(*((int *) column->value))); + valueDatum = CStringGetDatum((char *) str); + break; default: - valueDatum = CStringGetDatum((char*)column->value); + valueDatum = CStringGetDatum((char *) column->value); } - value_datum = OidFunctionCall3(typeinput, valueDatum, ObjectIdGetDatum(InvalidOid), Int32GetDatum(typemod)); + + value_datum = OidFunctionCall3(typeinput, valueDatum, + ObjectIdGetDatum(pgtyp), + Int32GetDatum(typemod)); + return value_datum; } - /* - * mysql_from_pgtyp: Give MySQL data type for PG type + * mysql_from_pgtyp: + * Give MySQL data type for PG type */ static int32 mysql_from_pgtyp(Oid type) { - switch(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; - + /* TODO: We may have to add more type of array */ + case INT2ARRAYOID: + case TEXTARRAYOID: 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))); + 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) + * 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) +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)); - binds[attnum].buffer_type = mysql_from_pgtyp(type); +#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; - switch(type) + /* + * 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 = palloc0(sizeof(int16)); - memcpy(bufptr, (char*)&dat, sizeof(int16)); + { + int16 dat = DatumGetInt16(value); + int16 *bufptr = palloc(sizeof(int16)); + + memcpy(bufptr, (char *) &dat, sizeof(int16)); - binds[attnum].buffer = bufptr; + binds[attnum].buffer = bufptr; + } break; - } case INT4OID: - { - int32 dat = DatumGetInt32(value); - int32 *bufptr = palloc0(sizeof(int32)); - memcpy(bufptr, (char*)&dat, sizeof(int32)); + { + int32 dat = DatumGetInt32(value); + int32 *bufptr = palloc(sizeof(int32)); + + memcpy(bufptr, (char *) &dat, sizeof(int32)); - binds[attnum].buffer = bufptr; + binds[attnum].buffer = bufptr; + } break; - } case INT8OID: - { - int64 dat = DatumGetInt64(value); - int64 *bufptr = palloc0(sizeof(int64)); - memcpy(bufptr, (char*)&dat, sizeof(int64)); + { + int64 dat = DatumGetInt64(value); + int64 *bufptr = palloc(sizeof(int64)); - binds[attnum].buffer = bufptr; + memcpy(bufptr, (char *) &dat, sizeof(int64)); + + binds[attnum].buffer = bufptr; + } break; - } case FLOAT4OID: - { - float4 dat = DatumGetFloat4(value); - float4 *bufptr = palloc0(sizeof(float4)); - memcpy(bufptr, (char*)&dat, sizeof(float4)); + { + float4 dat = DatumGetFloat4(value); + float4 *bufptr = palloc(sizeof(float4)); - binds[attnum].buffer = bufptr; + memcpy(bufptr, (char *) &dat, sizeof(float4)); + + binds[attnum].buffer = bufptr; + } break; - } case FLOAT8OID: - { - float8 dat = DatumGetFloat8(value); - float8 *bufptr = palloc0(sizeof(float8)); - memcpy(bufptr, (char*)&dat, sizeof(float8)); + { + float8 dat = DatumGetFloat8(value); + float8 *bufptr = palloc(sizeof(float8)); + + memcpy(bufptr, (char *) &dat, sizeof(float8)); - binds[attnum].buffer = bufptr; + binds[attnum].buffer = bufptr; + } break; - } case NUMERICOID: - { - Datum valueDatum = DirectFunctionCall1(numeric_float8, value); - float8 dat = DatumGetFloat8(valueDatum); - float8 *bufptr = palloc0(sizeof(float8)); - memcpy(bufptr, (char*)&dat, sizeof(float8)); + { + 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; + binds[attnum].buffer = bufptr; + } break; - } case BOOLOID: - { - int32 dat = DatumGetInt32(value); - int32 *bufptr = palloc0(sizeof(int32)); - memcpy(bufptr, (char*)&dat, sizeof(int32)); + { + int32 dat = DatumGetInt32(value); + int32 *bufptr = palloc(sizeof(int32)); - binds[attnum].buffer = bufptr; - break; - } + memcpy(bufptr, (char *) &dat, sizeof(int32)); + binds[attnum].buffer = bufptr; + } + break; + /* TODO: We may have to add more type of array */ + case INT2ARRAYOID: + case TEXTARRAYOID: case BPCHAROID: case VARCHAROID: case TEXTOID: case JSONOID: - { - 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)); + case ANYENUMOID: + { + char *outputString = NULL; + Oid outputFunctionId = InvalidOid; + bool typeVarLength = false; - timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, pg_tzset("UTC")); + getTypeOutputInfo(type, &outputFunctionId, &typeVarLength); + outputString = OidOutputFunctionCall(outputFunctionId, value); - DATE_MYSQL_PG(ts, tt); + binds[attnum].buffer = outputString; + binds[attnum].buffer_length = strlen(outputString); + } + break; + case NAMEOID: + { + char *outputString = NULL; + Oid outputFunctionId = InvalidOid; + bool typeVarLength = false; - binds[attnum].buffer = ts; - binds[attnum].buffer_length=sizeof(MYSQL_TIME); + 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")); + { + 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; - DATE_MYSQL_PG(ts, tt); + timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, + pg_tzset("UTC")); - binds[attnum].buffer = ts; - binds[attnum].buffer_length = sizeof(MYSQL_TIME); + 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); + 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; } - else + break; + case BYTEAOID: { - len = VARSIZE_4B(result) - VARHDRSZ; - dat = VARDATA_4B(result); + 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; } - bufptr = palloc0(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))); + 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) + * 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_result(Oid pgtyp, int pgtypmod, MYSQL_FIELD *field, + mysql_column *column) { - MYSQL_BIND *mbind = column->_mysql_bind; + MYSQL_BIND *mbind = column->mysql_bind; + +#if MYSQL_VERSION_ID < 80000 || MARIADB_VERSION_ID >= 100000 + mbind->is_null = (my_bool *) &column->is_null; + mbind->error = (my_bool *) &column->error; +#else mbind->is_null = &column->is_null; - mbind->length = &column->length; mbind->error = &column->error; +#endif + mbind->length = &column->length; switch (pgtyp) { - case BYTEAOID: - mbind->buffer_type = MYSQL_TYPE_BLOB; - /* leave room at front for bytea buffer length prefix */ - column->value = (Datum) palloc0(MAX_BLOB_WIDTH + VARHDRSZ); - mbind->buffer = VARDATA(column->value); - mbind->buffer_length = MAX_BLOB_WIDTH; - break; - - default: - mbind->buffer_type = MYSQL_TYPE_VAR_STRING; - column->value = (Datum) palloc0(MAXDATALEN); - mbind->buffer = (char *) column->value; - mbind->buffer_length = MAXDATALEN; + case BYTEAOID: + mbind->buffer_type = MYSQL_TYPE_BLOB; + /* Leave room at front for bytea buffer length prefix */ + column->value = (Datum) palloc0(MAX_BLOB_WIDTH + VARHDRSZ); + mbind->buffer = VARDATA(column->value); + mbind->buffer_length = MAX_BLOB_WIDTH; + break; + default: + mbind->buffer_type = MYSQL_TYPE_VAR_STRING; + column->value = (Datum) palloc0(MAXDATALEN); + mbind->buffer = (char *) column->value; + mbind->buffer_length = MAXDATALEN; } } -static -int dec_bin(int n) +static int +dec_bin(int number) { - int rem, i = 1; - int bin = 0; + int rem; + int i = 1; + int bin = 0; - while (n != 0) + while (number != 0) { - rem = n % 2; - n /= 2; + rem = number % 2; + number /= 2; bin += rem * i; i *= 10; } + return bin; } static int -bin_dec(int n) +bin_dec(int binarynumber) { - int dec = 0; - int i = 0; - int rem; + int dec = 0; + int i = 0; + int rem; - while (n != 0) + while (binarynumber != 0) { - rem = n % 10; - n /= 10; - dec += rem * pow(2 , i); + rem = binarynumber % 10; + binarynumber /= 10; + dec += rem * pow(2, i); ++i; } + return dec; } diff --git a/mysql_query.h b/mysql_query.h index 76b2f60..b35c7bb 100644 --- a/mysql_query.h +++ b/mysql_query.h @@ -4,8 +4,7 @@ * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_query.h @@ -19,15 +18,17 @@ #include "foreign/foreign.h" #include "lib/stringinfo.h" #if PG_VERSION_NUM < 120000 - #include "nodes/relation.h" +#include "nodes/relation.h" #else - #include "nodes/pathnodes.h" +#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); +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 */ +#endif /* MYSQL_QUERY_H */ diff --git a/option.c b/option.c index 880d984..30563d0 100644 --- a/option.c +++ b/option.c @@ -1,91 +1,67 @@ /*------------------------------------------------------------------------- * - * options.c - * Foreign-data wrapper for remote MySQL servers + * option.c + * FDW option handling for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group - * - * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. + * Portions Copyright (c) 2004-2020, EnterpriseDB Corporation. * * IDENTIFICATION - * options.c + * option.c * *------------------------------------------------------------------------- */ - #include "postgres.h" -#include "mysql_fdw.h" - -#include -#include -#include - -#include "funcapi.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 "commands/explain.h" -#include "foreign/fdwapi.h" -#include "foreign/foreign.h" #include "miscadmin.h" -#include "mb/pg_wchar.h" -#include "optimizer/cost.h" -#include "storage/fd.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/rel.h" +#include "mysql_fdw.h" #include "utils/lsyscache.h" -#include "optimizer/pathnode.h" -#include "optimizer/restrictinfo.h" -#include "optimizer/planmain.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 */ + 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 }, - { "ssl_key", ForeignServerRelationId }, - { "ssl_cert", ForeignServerRelationId }, - { "ssl_ca", ForeignServerRelationId }, - { "ssl_capath", ForeignServerRelationId }, - { "ssl_cipher", ForeignServerRelationId }, + {"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}, + {"ssl_key", ForeignServerRelationId}, + {"ssl_cert", ForeignServerRelationId}, + {"ssl_ca", ForeignServerRelationId}, + {"ssl_capath", ForeignServerRelationId}, + {"ssl_cipher", ForeignServerRelationId}, /* Sentinel */ - { NULL, InvalidOid } + {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. @@ -95,17 +71,17 @@ PG_FUNCTION_INFO_V1(mysql_fdw_validator); Datum mysql_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); - ListCell *cell; + ListCell *cell; /* - * Check that only options supported by mysql_fdw, - * and allowed for the current object type, are given. + * 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); + DefElem *def = (DefElem *) lfirst(cell); if (!mysql_is_valid_option(def->defname, catalog)) { @@ -121,20 +97,20 @@ mysql_fdw_validator(PG_FUNCTION_ARGS) { if (catalog == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", - opt->optname); + 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 : "") - )); + 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 : ""))); } } + 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. @@ -149,24 +125,24 @@ mysql_is_valid_option(const char *option, Oid context) 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_opt * mysql_get_options(Oid foreignoid) { - ForeignTable *f_table = NULL; - ForeignServer *f_server = NULL; + ForeignTable *f_table; + ForeignServer *f_server; UserMapping *f_mapping; - List *options; - ListCell *lc; - mysql_opt *opt; + List *options; + ListCell *lc; + mysql_opt *opt; - opt = (mysql_opt*) palloc(sizeof(mysql_opt)); - memset(opt, 0, sizeof(mysql_opt)); + opt = (mysql_opt *) palloc0(sizeof(mysql_opt)); /* * Extract options from FDW objects. @@ -188,6 +164,7 @@ mysql_get_options(Oid foreignoid) options = NIL; if (f_table) options = list_concat(options, f_table->options); + options = list_concat(options, f_server->options); options = list_concat(options, f_mapping->options); @@ -196,10 +173,10 @@ mysql_get_options(Oid foreignoid) opt->use_remote_estimate = false; - /* Loop through the options, and get the server/port */ + /* Loop through the options */ foreach(lc, options) { - DefElem *def = (DefElem *) lfirst(lc); + DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "host") == 0) opt->svr_address = defGetString(def); @@ -221,16 +198,16 @@ mysql_get_options(Oid foreignoid) 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); + 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, "ssl_key") == 0) opt->ssl_key = defGetString(def); @@ -245,8 +222,8 @@ mysql_get_options(Oid foreignoid) 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"; @@ -254,10 +231,19 @@ mysql_get_options(Oid foreignoid) if (!opt->svr_port) opt->svr_port = MYSQL_PORT; - if (!opt->svr_table && f_table) - opt->svr_table = get_rel_name(foreignoid); + /* + * 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)); + } return opt; } - - diff --git a/sql/connection_validation.sql b/sql/connection_validation.sql new file mode 100644 index 0000000..8e6b492 --- /dev/null +++ b/sql/connection_validation.sql @@ -0,0 +1,63 @@ +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. + +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +-- Create foreign table and Validate +--Testcase 4: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 5: +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. +ALTER SERVER mysql_svr OPTIONS (SET host 'localhos'); +--Testcase 6: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + +-- Set the correct host-name, next operation should succeed. +ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST); +--Testcase 7: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + +-- Alter USER MAPPING option. +-- Set wrong user-name and password, next operation should fail. +ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr + OPTIONS (SET username 'foo1', SET password 'bar1'); +--Testcase 8: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + +-- 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); +--Testcase 9: +SELECT * FROM f_mysql_test ORDER BY 1, 2; + +-- Cleanup +--Testcase 10: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 11: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 12: +DROP SERVER mysql_svr; +--Testcase 13: +DROP EXTENSION mysql_fdw; diff --git a/sql/dml.sql b/sql/dml.sql new file mode 100644 index 0000000..d02b20c --- /dev/null +++ b/sql/dml.sql @@ -0,0 +1,248 @@ +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. + +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +-- Create foreign tables +--Testcase 4: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 5: +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'); +--Testcase 6: +CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) + SERVER mysql_svr OPTIONS (table_name 'student'); +--Testcase 7: +CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'numbers'); +--Testcase 8: +CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'nosuchtable'); +--Testcase 9: +CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress2', table_name 'numbers'); +--Testcase 10: +CREATE FOREIGN TABLE fdw126_ft6(stu_id int, stu_name varchar(255)) + SERVER mysql_svr OPTIONS (table_name 'mysql_fdw_regress1.student'); +--Testcase 11: +CREATE FOREIGN TABLE f_empdata(emp_id int, emp_dat bytea) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'empdata'); +--Testcase 40: +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. +--Testcase 12: +INSERT INTO f_empdata VALUES (1, decode ('01234567', 'hex')); +--Testcase 13: +SELECT count(*) FROM f_empdata ORDER BY 1; +--Testcase 14: +SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; +--Testcase 15: +UPDATE f_empdata SET emp_dat = decode ('0123', 'hex'); +--Testcase 16: +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. +--Testcase 17: +INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); +--Testcase 18: +UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1; +--Testcase 19: +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. +--Testcase 20: +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. +--Testcase 21: +INSERT INTO fdw126_ft2 VALUES(2, 'Two'); + +-- 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. +--Testcase 22: +INSERT INTO fdw126_ft3 VALUES(1, 'One'); +--Testcase 23: +SELECT a, b FROM fdw126_ft3 ORDER BY 1, 2 LIMIT 1; +--Testcase 24: +UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1; +--Testcase 25: +DELETE FROM fdw126_ft3 WHERE a = 1; + +-- Check when table_name is given in database.table form in foreign table +-- should error out as syntax error +--Testcase 26: +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. +--Testcase 41: +CREATE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ +BEGIN + NEW.stu_name := NEW.stu_name || ' trigger updated!'; + RETURN NEW; + END +$$ language plpgsql; + +--Testcase 42: +CREATE TRIGGER before_row_update_trig +BEFORE UPDATE ON fdw126_ft1 +FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); + +--Testcase 43: +INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); +--Testcase 44: +EXPLAIN (verbose, costs off) +UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; +--Testcase 45: +UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; +--Testcase 46: +SELECT * FROM fdw126_ft1 ORDER BY stu_id; + +-- Throw an error when target list has row identifier column. +--Testcase 47: +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. +--Testcase 48: +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; + +--Testcase 49: +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. +--Testcase 50: +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; + +--Testcase 51: +CREATE TRIGGER before_row_update_trig1 +BEFORE UPDATE ON fdw193_ft1 +FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); + +--Testcase 52: +INSERT INTO fdw193_ft1 VALUES('aa', 'One', 101); +--Testcase 53: +EXPLAIN (verbose, costs off) +UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; +--Testcase 54: +UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; +--Testcase 55: +SELECT * FROM fdw193_ft1 ORDER BY stu_id; + +-- Throw an error when before row update trigger modify the row identifier +-- column (varchar column) value. +--Testcase 56: +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; + +--Testcase 57: +UPDATE fdw193_ft1 SET stu_dept = 301 WHERE stu_id = 'aa'; + +-- Verify the NULL assignment scenario. +--Testcase 58: +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; + +--Testcase 59: +UPDATE fdw193_ft1 SET stu_dept = 401 WHERE stu_id = 'aa'; + +-- Cleanup +--Testcase 27: +DELETE FROM fdw126_ft1; +--Testcase 28: +DELETE FROM f_empdata; +--Testcase 60: +DELETE FROM fdw193_ft1; +--Testcase 29: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 30: +DROP FOREIGN TABLE fdw126_ft1; +--Testcase 31: +DROP FOREIGN TABLE fdw126_ft2; +--Testcase 32: +DROP FOREIGN TABLE fdw126_ft3; +--Testcase 33: +DROP FOREIGN TABLE fdw126_ft4; +--Testcase 34: +DROP FOREIGN TABLE fdw126_ft5; +--Testcase 35: +DROP FOREIGN TABLE fdw126_ft6; +--Testcase 36: +DROP FOREIGN TABLE f_empdata; +--Testcase 61: +DROP FOREIGN TABLE fdw193_ft1; +--Testcase 62: +DROP FUNCTION before_row_update_func(); +--Testcase 37: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 38: +DROP SERVER mysql_svr; +--Testcase 39: +DROP EXTENSION mysql_fdw; diff --git a/sql/mysql_fdw.sql b/sql/mysql_fdw.sql deleted file mode 100644 index 78efca8..0000000 --- a/sql/mysql_fdw.sql +++ /dev/null @@ -1,99 +0,0 @@ -\c postgres postgres -CREATE EXTENSION mysql_fdw; -CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw; -CREATE USER MAPPING FOR postgres SERVER mysql_svr OPTIONS(username 'foo', password 'bar'); - -CREATE FOREIGN TABLE department(department_id int, department_name text) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'department'); -CREATE FOREIGN TABLE employee(emp_id int, emp_name text, emp_dept_id int) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'employee'); -CREATE FOREIGN TABLE empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS(dbname 'testdb', table_name 'empdata'); -CREATE FOREIGN TABLE numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'testdb', table_name 'numbers'); - -SELECT * FROM department LIMIT 10; -SELECT * FROM employee LIMIT 10; -SELECT * FROM empdata LIMIT 10; - -INSERT INTO department VALUES(generate_series(1,100), 'dept - ' || generate_series(1,100)); -INSERT INTO employee VALUES(generate_series(1,100), 'emp - ' || generate_series(1,100), generate_series(1,100)); -INSERT INTO empdata VALUES(1, decode ('01234567', 'hex')); - -insert into numbers values(1, 'One'); -insert into numbers values(2, 'Two'); -insert into numbers values(3, 'Three'); -insert into numbers values(4, 'Four'); -insert into numbers values(5, 'Five'); -insert into numbers values(6, 'Six'); -insert into numbers values(7, 'Seven'); -insert into numbers values(8, 'Eight'); -insert into numbers values(9, 'Nine'); - -SELECT count(*) FROM department; -SELECT count(*) FROM employee; -SELECT count(*) FROM empdata; - -EXPLAIN (COSTS FALSE) SELECT * FROM department d, employee e WHERE d.department_id = e.emp_dept_id LIMIT 10; - -EXPLAIN (COSTS FALSE) SELECT * FROM department d, employee e WHERE d.department_id IN (SELECT department_id FROM department) LIMIT 10; - -SELECT * FROM department d, employee e WHERE d.department_id = e.emp_dept_id LIMIT 10; -SELECT * FROM department d, employee e WHERE d.department_id IN (SELECT department_id FROM department) LIMIT 10; -SELECT * FROM empdata; - -DELETE FROM employee WHERE emp_id = 10; - -SELECT COUNT(*) FROM department LIMIT 10; -SELECT COUNT(*) FROM employee WHERE emp_id = 10; - -UPDATE employee SET emp_name = 'Updated emp' WHERE emp_id = 20; -SELECT emp_id, emp_name FROM employee WHERE emp_name like 'Updated emp'; - -UPDATE empdata SET emp_dat = decode ('0123', 'hex'); -SELECT * FROM empdata; - -SELECT * FROM employee LIMIT 10; -SELECT * FROM employee WHERE emp_id IN (1); -SELECT * FROM employee WHERE emp_id IN (1,3,4,5); -SELECT * FROM employee WHERE emp_id IN (10000,1000); - -SELECT * FROM employee WHERE emp_id NOT IN (1) LIMIT 5; -SELECT * FROM employee WHERE emp_id NOT IN (1,3,4,5) LIMIT 5; -SELECT * FROM employee WHERE emp_id NOT IN (10000,1000) LIMIT 5; - -SELECT * FROM employee WHERE emp_id NOT IN (SELECT emp_id FROM employee WHERE emp_id IN (1,10)); -SELECT * FROM employee WHERE emp_name NOT IN ('emp - 1', 'emp - 2') LIMIT 5; -SELECT * FROM employee WHERE emp_name NOT IN ('emp - 10') LIMIT 5; - -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 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(integer, text) returns integer as ' - select a from numbers where a=$1 and b=$2; -' LANGUAGE sql; - -SELECT test_param_where2(1, 'One'); - -DELETE FROM employee; -DELETE FROM department; -DELETE FROM empdata; -DELETE FROM numbers; - -DROP FUNCTION test_param_where(); -DROP FUNCTION test_param_where2(integer, text); -DROP FOREIGN TABLE numbers; - -DROP FOREIGN TABLE department; -DROP FOREIGN TABLE employee; -DROP FOREIGN TABLE empdata; -DROP USER MAPPING FOR postgres SERVER mysql_svr; -DROP SERVER mysql_svr; -DROP EXTENSION mysql_fdw CASCADE; diff --git a/sql/parameters.conf b/sql/parameters.conf new file mode 100644 index 0000000..e59a7db --- /dev/null +++ b/sql/parameters.conf @@ -0,0 +1,4 @@ +\set MYSQL_HOST '\'localhost\'' +\set MYSQL_PORT '\'3306\'' +\set MYSQL_USER_NAME '\'edb\'' +\set MYSQL_PASS '\'edb\'' \ No newline at end of file diff --git a/sql/pushdown.sql b/sql/pushdown.sql new file mode 100644 index 0000000..f02731a --- /dev/null +++ b/sql/pushdown.sql @@ -0,0 +1,183 @@ +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +-- Before running this file User must create database mysql_fdw_regress on +-- mysql with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. + +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +-- Create foreign tables +--Testcase 4: +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'); +--Testcase 5: +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 +--Testcase 6: +INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); +--Testcase 7: +INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); +--Testcase 8: +INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); +--Testcase 9: +INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); +--Testcase 10: +INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); +--Testcase 11: +INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); +--Testcase 12: +INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); +--Testcase 13: +INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); +--Testcase 14: +INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); +--Testcase 15: +INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); +--Testcase 16: +INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); +--Testcase 17: +INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); +--Testcase 18: +INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); +--Testcase 19: +INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); +--Testcase 20: +INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); +--Testcase 21: +INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); +--Testcase 22: +INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); +--Testcase 23: +INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); + +SET datestyle TO ISO; + +-- WHERE clause pushdown + +--Testcase 24: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e + WHERE c6 IN (800,2450) + ORDER BY c1; +--Testcase 25: +SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e + WHERE c6 IN (800,2450) + ORDER BY c1; + +--Testcase 26: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT * FROM f_test_tbl1 e + WHERE c6 > 3000 + ORDER BY c1; +--Testcase 27: +SELECT * FROM f_test_tbl1 e + WHERE c6 > 3000 + ORDER BY c1; + +--Testcase 28: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 = 1500 + ORDER BY c1; +--Testcase 29: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 = 1500 + ORDER BY c1; + +--Testcase 30: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 BETWEEN 1000 AND 4000 + ORDER BY c1; +--Testcase 31: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c6 BETWEEN 1000 AND 4000 + ORDER BY c1; + +--Testcase 32: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IS NOT NULL + ORDER BY c1; +--Testcase 33: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IS NOT NULL + ORDER BY c1; + +--Testcase 34: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT * FROM f_test_tbl1 e + WHERE c5 <= '1980-12-17' + ORDER BY c1; +--Testcase 35: +SELECT * FROM f_test_tbl1 e + WHERE c5 <= '1980-12-17' + ORDER BY c1; + +--Testcase 36: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; +--Testcase 37: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; + +--Testcase 38: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; +--Testcase 39: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') + ORDER BY c1; + +--Testcase 40: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'SALESMAN' + ORDER BY c1; +--Testcase 41: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'SALESMAN' + ORDER BY c1; + +--Testcase 42: +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'MANA%' + ORDER BY c1; +--Testcase 43: +SELECT c1, c2, c6, c8 FROM f_test_tbl1 e + WHERE c3 LIKE 'MANA%' + ORDER BY c1; + +-- Cleanup +--Testcase 44: +DELETE FROM f_test_tbl1; +--Testcase 45: +DELETE FROM f_test_tbl2; +--Testcase 46: +DROP FOREIGN TABLE f_test_tbl1; +--Testcase 47: +DROP FOREIGN TABLE f_test_tbl2; +--Testcase 48: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 49: +DROP SERVER mysql_svr; +--Testcase 50: +DROP EXTENSION mysql_fdw; diff --git a/sql/select.sql b/sql/select.sql new file mode 100644 index 0000000..06b7a30 --- /dev/null +++ b/sql/select.sql @@ -0,0 +1,550 @@ +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. + +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR PUBLIC SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +-- Check version +--Testcase 4: +SELECT mysql_fdw_version(); + +-- Create foreign tables +--Testcase 5: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 6: +CREATE FOREIGN TABLE f_numbers(a int, b varchar(255)) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'numbers'); +--Testcase 7: +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'); +--Testcase 8: +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'); +--Testcase 9: +CREATE TYPE size_t AS enum('small','medium','large'); +--Testcase 10: +CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); + +-- Insert data in MySQL db using foreign tables +--Testcase 11: +INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); +--Testcase 12: +INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); +--Testcase 13: +INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); +--Testcase 14: +INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); +--Testcase 15: +INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); +--Testcase 16: +INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); +--Testcase 17: +INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); +--Testcase 18: +INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); +--Testcase 19: +INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); +--Testcase 20: +INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); +--Testcase 21: +INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); +--Testcase 22: +INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); +--Testcase 23: +INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); +--Testcase 24: +INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); +--Testcase 25: +INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); +--Testcase 26: +INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); +--Testcase 27: +INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); +--Testcase 28: +INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); + +SET datestyle TO ISO; + +-- Retrieve Data from Foreign Table using SELECT Statement. +--Testcase 29: +SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 + ORDER BY c1 DESC, c8; +--Testcase 30: +SELECT DISTINCT c8 FROM f_test_tbl1 ORDER BY 1; +--Testcase 31: +SELECT c2 AS "Employee Name" FROM f_test_tbl1 ORDER BY 1; +--Testcase 32: +SELECT c8, c6, c7 FROM f_test_tbl1 ORDER BY 1, 2, 3; +--Testcase 33: +SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 + WHERE c1 = 100 ORDER BY 1; +--Testcase 34: +SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 + WHERE c1 = 100 OR c1 = 700 ORDER BY 1; +--Testcase 35: +SELECT * FROM f_test_tbl1 WHERE c3 like 'SALESMAN' ORDER BY 1; +--Testcase 36: +SELECT * FROM f_test_tbl1 WHERE c1 IN (100, 700) ORDER BY 1; +--Testcase 37: +SELECT * FROM f_test_tbl1 WHERE c1 NOT IN (100, 700) ORDER BY 1 LIMIT 5; +--Testcase 38: +SELECT * FROM f_test_tbl1 WHERE c8 BETWEEN 10 AND 20 ORDER BY 1; +--Testcase 39: +SELECT * FROM f_test_tbl1 ORDER BY 1 OFFSET 5; + +-- Retrieve Data from Foreign Table using Group By Clause. +--Testcase 40: +SELECT c8 "Department", COUNT(c1) "Total Employees" FROM f_test_tbl1 + GROUP BY c8 ORDER BY c8; +--Testcase 41: +SELECT c8, SUM(c6) FROM f_test_tbl1 + GROUP BY c8 HAVING c8 IN (10, 30) ORDER BY c8; +--Testcase 42: +SELECT c8, SUM(c6) FROM f_test_tbl1 + GROUP BY c8 HAVING SUM(c6) > 9400 ORDER BY c8; + +-- Row Level Functions +--Testcase 43: +SELECT UPPER(c2), LOWER(c2) FROM f_test_tbl2 ORDER BY 1, 2; + +-- Retrieve Data from Foreign Table using Sub Queries. +--Testcase 44: +SELECT * FROM f_test_tbl1 + WHERE c8 <> ALL (SELECT c1 FROM f_test_tbl2 WHERE c1 IN (10, 30, 40)) + ORDER BY c1; +--Testcase 45: +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; +--Testcase 46: +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. +--Testcase 47: +SELECT c1, c2 FROM f_test_tbl2 UNION +SELECT c1, c2 FROM f_test_tbl1 ORDER BY c1; + +--Testcase 48: +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. +--Testcase 49: +SELECT c2 FROM f_test_tbl1 WHERE c1 >= 800 INTERSECT +SELECT c2 FROM f_test_tbl1 WHERE c1 >= 400 ORDER BY c2; + +--Testcase 50: +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. +--Testcase 51: +SELECT c2 FROM f_test_tbl1 EXCEPT +SELECT c2 FROM f_test_tbl1 WHERE c1 > 900 ORDER BY c2; + +--Testcase 52: +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). +--Testcase 53: +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; + +--Testcase 54: +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. +--Testcase 55: +SELECT c8, c1, c6, AVG(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 + ORDER BY c8, c1; +--Testcase 56: +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; +--Testcase 57: +SELECT c8, c1, c6, SUM(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 + ORDER BY c8, c1; + +-- Views +--Testcase 58: +CREATE VIEW smpl_vw AS + SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 + ORDER BY c1; +--Testcase 59: +SELECT * FROM smpl_vw ORDER BY 1; + +--Testcase 60: +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; +--Testcase 61: +SELECT * FROM comp_vw ORDER BY 1; + +--Testcase 62: +CREATE TEMPORARY VIEW ttest_tbl1_vw AS + SELECT c1, c2, c3 FROM f_test_tbl2; +--Testcase 63: +SELECT * FROM ttest_tbl1_vw ORDER BY 1, 2; + +--Testcase 64: +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; +--Testcase 65: +SELECT * FROM mul_tbl_view ORDER BY 1, 2,3; + +-- Insert Some records in numbers table. +--Testcase 66: +INSERT INTO f_numbers VALUES (1, 'One'); +--Testcase 67: +INSERT INTO f_numbers VALUES (2, 'Two'); +--Testcase 68: +INSERT INTO f_numbers VALUES (3, 'Three'); +--Testcase 69: +INSERT INTO f_numbers VALUES (4, 'Four'); +--Testcase 70: +INSERT INTO f_numbers VALUES (5, 'Five'); +--Testcase 71: +INSERT INTO f_numbers VALUES (6, 'Six'); +--Testcase 72: +INSERT INTO f_numbers VALUES (7, 'Seven'); +--Testcase 73: +INSERT INTO f_numbers VALUES (8, 'Eight'); +--Testcase 74: +INSERT INTO f_numbers VALUES (9, 'Nine'); + +-- Retrieve Data From foreign tables in functions. +--Testcase 75: +CREATE OR REPLACE FUNCTION test_param_where() RETURNS void AS $$ +DECLARE + n varchar; +BEGIN + FOR x IN 1..9 LOOP +--Testcase 76: + SELECT b INTO n FROM f_numbers WHERE a = x; + RAISE NOTICE 'Found number %', n; + END LOOP; + return; +END +$$ LANGUAGE plpgsql; + +--Testcase 77: +SELECT test_param_where(); + +--Testcase 78: +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; + +--Testcase 79: +SELECT test_param_where2(1, 'One'); + +-- Foreign-Foreign table joins + +-- CROSS JOIN. +--Testcase 80: +SELECT f_test_tbl2.c2, f_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN f_test_tbl1 ORDER BY 1, 2; +-- INNER JOIN. +--Testcase 81: +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; +--Testcase 82: +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. +--Testcase 83: +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; +--Testcase 84: +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; +--Testcase 85: +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. +--Testcase 86: +CREATE TABLE l_test_tbl1 AS + SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1; +--Testcase 87: +CREATE TABLE l_test_tbl2 AS + SELECT c1, c2, c3 FROM f_test_tbl2; + +-- CROSS JOIN. +--Testcase 88: +SELECT f_test_tbl2.c2, l_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN l_test_tbl1 ORDER BY 1, 2; +-- INNER JOIN. +--Testcase 89: +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; +--Testcase 90: +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. +--Testcase 91: +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; +--Testcase 92: +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; +--Testcase 93: +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 +--Testcase 121: +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; +--Testcase 122: +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; +--Testcase 141: +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; +--Testcase 142: +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; +--Testcase 143: +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 +--Testcase 144: +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 +--Testcase 145: +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. +--Testcase 146: +INSERT INTO f_test_tbl2 VALUES (50, 'TEMP1', NULL); +--Testcase 147: +INSERT INTO f_test_tbl2 VALUES (60, 'TEMP2', NULL); +ALTER SERVER mysql_svr OPTIONS (use_remote_estimate 'true'); +--Testcase 148: +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; +--Testcase 149: +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; +--Testcase 150: +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. +--Testcase 151: +INSERT INTO f_enum_t1 + VALUES (1, 'small'), (2, 'medium'), (3, 'medium'), (4, 'small'); +--Testcase 152: +SELECT * FROM f_enum_t1 WHERE id = 4; +--Testcase 153: +UPDATE f_enum_t1 SET size = 'large' WHERE id = 4; +--Testcase 154: +SELECT * FROM f_enum_t1 WHERE id = 4; +--Testcase 155: +DELETE FROM f_enum_t1 WHERE size = 'large'; +--Testcase 156: +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. +--Testcase 157: +DROP FOREIGN TABLE f_enum_t1; +--Testcase 158: +DROP TYPE size_t; +-- Create the type with extra enum values. +--Testcase 159: +CREATE TYPE size_t AS enum('small', 'medium', 'large', 'largest', ''); +--Testcase 160: +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. +--Testcase 161: +INSERT INTO f_enum_t1 VALUES (4, 'largest'); +--Testcase 162: +SELECT * from f_enum_t1; +--Testcase 163: +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. +--Testcase 164: +INSERT INTO f_enum_t1 VALUES (4, 'big'); + +-- FDW-155: Enum data type can be handled correctly in select statements on +-- foreign table. +--Testcase 94: +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) +--Testcase 95: +EXPLAIN (VERBOSE, COSTS OFF) +SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; +--Testcase 96: +SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; + +-- 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. +--Testcase 123: +CREATE TYPE enum_t1_size_t AS enum('small', 'medium', 'large'); +--Testcase 124: +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; +--Testcase 125: +SELECT attrelid::regclass, atttypid::regtype FROM pg_attribute + WHERE (attrelid = 'enum_t1'::regclass OR attrelid = 'enum_t2'::regclass) AND + attnum > 1 ORDER BY 1; +--Testcase 126: +SELECT * FROM enum_t1 ORDER BY id; +--Testcase 127: +SELECT * FROM enum_t2 ORDER BY id; +--Testcase 128: +DROP FOREIGN TABLE enum_t1; +--Testcase 129: +DROP FOREIGN TABLE enum_t2; + +-- Parameterized queries should work correctly. +--Testcase 130: +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; +--Testcase 131: +SELECT c1, c2 FROM f_test_tbl1 + WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) + ORDER BY c1; + +--Testcase 132: +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. +--Testcase 133: +CREATE FOREIGN TABLE f_test_tbl3 (c1 INTEGER, c2 text, c3 text) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); +--Testcase 134: +CREATE TABLE local_t1 (c1 INTEGER, c2 text); +--Testcase 135: +INSERT INTO local_t1 VALUES (1, 'SALES'); + +--Testcase 136: +SELECT c1, c2 FROM f_test_tbl3 WHERE c3 = (SELECT 'PUNE'::text) ORDER BY c1; +--Testcase 137: +SELECT c1, c2 FROM f_test_tbl2 WHERE c3 = (SELECT 'PUNE'::varchar) ORDER BY c1; + +--Testcase 138: +SELECT * FROM local_t1 lt1 WHERE lt1.c1 = + (SELECT count(*) FROM f_test_tbl3 ft1 WHERE ft1.c2 = lt1.c2) ORDER BY lt1.c1; + +--Testcase 165: +EXPLAIN (VERBOSE, COSTS OFF) +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; +--Testcase 166: +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; + +--Testcase 167: +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; +--Testcase 168: +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; + +-- Cleanup +--Testcase 99: +DROP TABLE l_test_tbl1; +--Testcase 100: +DROP TABLE l_test_tbl2; +--Testcase 101: +DROP TABLE local_t1; +--Testcase 119: +DROP VIEW smpl_vw; +--Testcase 102: +DROP VIEW comp_vw; +--Testcase 103: +DROP VIEW ttest_tbl1_vw; +--Testcase 104: +DROP VIEW mul_tbl_view; +--Testcase 105: +DELETE FROM f_test_tbl1; +--Testcase 106: +DELETE FROM f_test_tbl2; +--Testcase 107: +DELETE FROM f_numbers; +--Testcase 108: +DELETE FROM f_enum_t1; +--Testcase 169: +DROP FOREIGN TABLE f_test_tbl1; +--Testcase 109: +DROP FOREIGN TABLE f_test_tbl2; +--Testcase 110: +DROP FOREIGN TABLE f_numbers; +--Testcase 111: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 112: +DROP FOREIGN TABLE f_enum_t1; +--Testcase 113: +DROP FOREIGN TABLE f_test_tbl3; +--Testcase 120: +DROP TYPE size_t; +--Testcase 139: +DROP TYPE enum_t1_size_t; +--Testcase 140: +DROP TYPE enum_t2_size_t; +--Testcase 114: +DROP FUNCTION test_param_where(); +--Testcase 115: +DROP FUNCTION test_param_where2(int, text); +--Testcase 116: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 117: +DROP SERVER mysql_svr; +--Testcase 118: +DROP EXTENSION mysql_fdw; diff --git a/sql/selectfunc.sql b/sql/selectfunc.sql new file mode 100644 index 0000000..9507ad3 --- /dev/null +++ b/sql/selectfunc.sql @@ -0,0 +1,284 @@ +SET datestyle=ISO; +SET timezone='Japan'; +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +--Testcase 1: +CREATE EXTENSION mysql_fdw; +--Testcase 2: +CREATE SERVER server1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR CURRENT_USER SERVER server1 + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +--IMPORT FOREIGN SCHEMA public FROM SERVER server1 INTO public OPTIONS(import_time_text 'false'); +--Testcase 4: +CREATE FOREIGN TABLE s3(id int, tag1 text, value1 float, value2 int, value3 float, value4 int, str1 text, str2 text) SERVER server1 OPTIONS(dbname 'mysql_fdw_regress', table_name 's3'); + +-- s3 (value1 as float8, value2 as bigint) +--Testcase 5: +\d s3; +--Testcase 6: +SELECT * FROM s3; + +-- select float8() (not pushdown, remove float8, explain) +--Testcase 7: +EXPLAIN VERBOSE +SELECT float8(value1), float8(value2), float8(value3), float8(value4) FROM s3; + +-- select float8() (not pushdown, remove float8, result) +--Testcase 8: +SELECT float8(value1), float8(value2), float8(value3), float8(value4) FROM s3; + +-- select sqrt (builtin function, explain) +--Testcase 9: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3; + +-- select sqrt (buitin function, result) +--Testcase 10: +SELECT sqrt(value1), sqrt(value2) FROM s3; + +-- select sqrt (builtin function,, not pushdown constraints, explain) +--Testcase 11: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE to_hex(value2) != '64'; + +-- select sqrt (builtin function, not pushdown constraints, result) +--Testcase 12: +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE to_hex(value2) != '64'; + +-- select sqrt (builtin function, pushdown constraints, explain) +--Testcase 13: +EXPLAIN VERBOSE +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE value2 != 200; + +-- select sqrt (builtin function, pushdown constraints, result) +--Testcase 14: +SELECT sqrt(value1), sqrt(value2) FROM s3 WHERE value2 != 200; + +-- select abs (builtin function, explain) +--Testcase 15: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3; + +-- ABS() returns negative values if integer (https://github.com/influxdata/influxdb/issues/10261) +-- select abs (buitin function, result) +--Testcase 16: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3; + +-- select abs (builtin function, not pushdown constraints, explain) +--Testcase 17: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE to_hex(value2) != '64'; + +-- select abs (builtin function, not pushdown constraints, result) +--Testcase 18: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE to_hex(value2) != '64'; + +-- select abs (builtin function, pushdown constraints, explain) +--Testcase 19: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE value2 != 200; + +-- select abs (builtin function, pushdown constraints, result) +--Testcase 20: +SELECT abs(value1), abs(value2), abs(value3), abs(value4) FROM s3 WHERE value2 != 200; + +-- select log (builtin function, need to swap arguments, numeric cast, explain) +-- log_(v) : postgresql (base, v), influxdb (v, base), mysql (base, v) +--Testcase 21: +EXPLAIN VERBOSE +SELECT log(value1::numeric, value2::numeric) FROM s3 WHERE value1 != 1; + +-- select log (builtin function, need to swap arguments, numeric cast, result) +--Testcase 22: +SELECT log(value1::numeric, value2::numeric) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, float8, explain) +--Testcase 23: +EXPLAIN VERBOSE +SELECT log(value1, 0.1) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, float8, result) +--Testcase 24: +SELECT log(value1, 0.1) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, bigint, explain) +--Testcase 25: +EXPLAIN VERBOSE +SELECT log(value2, 3) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, bigint, result) +--Testcase 26: +SELECT log(value2, 3) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, mix type, explain) +--Testcase 27: +EXPLAIN VERBOSE +SELECT log(value1, value2) FROM s3 WHERE value1 != 1; + +-- select log (stub function, need to swap arguments, mix type, result) +--Testcase 28: +SELECT log(value1, value2) FROM s3 WHERE value1 != 1; + +-- select log2 (stub function, explain) +-- EXPLAIN VERBOSE +-- SELECT log2(value1),log2(value2) FROM s3; + +-- select log2 (stub function, result) +-- SELECT log2(value1),log2(value2) FROM s3; + +-- select spread (stub agg function, explain) +-- EXPLAIN VERBOSE +-- SELECT spread(value1),spread(value2),spread(value3),spread(value4) FROM s3; + +-- select spread (stub agg function, result) +-- SELECT spread(value1),spread(value2),spread(value3),spread(value4) FROM s3; + +-- select spread (stub agg function, raise exception if not expected type) +-- SELECT spread(value1::numeric),spread(value2::numeric),spread(value3::numeric),spread(value4::numeric) FROM s3; + +-- select abs as nest function with agg (pushdown, explain) +--Testcase 29: +EXPLAIN VERBOSE +SELECT sum(value3),abs(sum(value3)) FROM s3; + +-- select abs as nest function with agg (pushdown, result) +--Testcase 30: +SELECT sum(value3),abs(sum(value3)) FROM s3; + +-- select abs as nest with log2 (pushdown, explain) +-- EXPLAIN VERBOSE +-- SELECT abs(log2(value1)),abs(log2(1/value1)) FROM s3; + +-- select abs as nest with log2 (pushdown, result) +-- SELECT abs(log2(value1)),abs(log2(1/value1)) FROM s3; + +-- select abs with non pushdown func and explicit constant (explain) +--Testcase 31: +EXPLAIN VERBOSE +SELECT abs(value3), pi(), 4.1 FROM s3; + +-- select abs with non pushdown func and explicit constant (result) +--Testcase 32: +SELECT abs(value3), pi(), 4.1 FROM s3; + +-- select sqrt as nest function with agg and explicit constant (pushdown, explain) +--Testcase 33: +EXPLAIN VERBOSE +SELECT sqrt(count(value1)), pi(), 4.1 FROM s3; + +-- select sqrt as nest function with agg and explicit constant (pushdown, result) +--Testcase 34: +SELECT sqrt(count(value1)), pi(), 4.1 FROM s3; + +-- select sqrt as nest function with agg and explicit constant and tag (error, explain) +--Testcase 35: +EXPLAIN VERBOSE +SELECT sqrt(count(value1)), pi(), 4.1, tag1 FROM s3; + +-- select spread (stub agg function and group by influx_time() and tag) (explain) +-- EXPLAIN VERBOSE +-- SELECT spread("value1"),influx_time(time, interval '1s'),tag1 FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY influx_time(time, interval '1s'), tag1; + +-- select spread (stub agg function and group by influx_time() and tag) (result) +-- SELECT spread("value1"),influx_time(time, interval '1s'),tag1 FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY influx_time(time, interval '1s'), tag1; + +-- select spread (stub agg function and group by tag only) (result) +-- SELECT tag1,spread("value1") FROM s3 WHERE time >= to_timestamp(0) and time <= to_timestamp(4) GROUP BY tag1; + +-- select spread (stub agg function and other aggs) (result) +-- SELECT sum("value1"),spread("value1"),count("value1") FROM s3; + +-- select abs with order by (explain) +--Testcase 36: +EXPLAIN VERBOSE +SELECT value1, abs(1-value1) FROM s3 order by abs(1-value1); + +-- select abs with order by (result) +--Testcase 37: +SELECT value1, abs(1-value1) FROM s3 order by abs(1-value1); + +-- select abs with order by index (result) +--Testcase 38: +SELECT value1, abs(1-value1) FROM s3 order by 2,1; + +-- select abs with order by index (result) +--Testcase 39: +SELECT value1, abs(1-value1) FROM s3 order by 1,2; + +-- select abs and as +--Testcase 40: +SELECT abs(value3) as abs1 FROM s3; + +-- select spread over join query (explain) +-- EXPLAIN VERBOSE +-- SELECT spread(t1.value1), spread(t2.value1) FROM s3 t1 INNER JOIN s3 t2 ON (t1.value1 = t2.value1) where t1.value1 = 0.1; + +-- select spread over join query (result, stub call error) +-- SELECT spread(t1.value1), spread(t2.value1) FROM s3 t1 INNER JOIN s3 t2 ON (t1.value1 = t2.value1) where t1.value1 = 0.1; + +-- select spread with having (explain) +-- EXPLAIN VERBOSE +-- SELECT spread(value1) FROM s3 HAVING spread(value1) > 100; + +-- select spread with having (explain, cannot pushdown, stub call error) +-- SELECT spread(value1) FROM s3 HAVING spread(value1) > 100; + +-- select abs with arithmetic and tag in the middle (explain) +--Testcase 41: +EXPLAIN VERBOSE +SELECT abs(value1) + 1, value2, tag1, sqrt(value2) FROM s3; + +-- select abs with arithmetic and tag in the middle (result) +--Testcase 42: +SELECT abs(value1) + 1, value2, tag1, sqrt(value2) FROM s3; + +-- select with order by limit (explain) +--Testcase 43: +EXPLAIN VERBOSE +SELECT abs(value1), abs(value3), sqrt(value2) FROM s3 ORDER BY abs(value3) LIMIT 1; + +-- select with order by limit (explain) +--Testcase 44: +SELECT abs(value1), abs(value3), sqrt(value2) FROM s3 ORDER BY abs(value3) LIMIT 1; + +-- select mixing with non pushdown func (all not pushdown, explain) +--Testcase 45: +EXPLAIN VERBOSE +SELECT abs(value1), sqrt(value2), chr(id+40) FROM s3; + +-- select mixing with non pushdown func (result) +--Testcase 46: +SELECT abs(value1), sqrt(value2), chr(id+40) FROM s3; + +--Testcase 47: +DROP FOREIGN TABLE s3; + +-- full text search table +--Testcase 48: +CREATE FOREIGN TABLE ftextsearch(id int, content text) SERVER server1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'ftextsearch'); + +-- text search (pushdown, explain) +--Testcase 49: +EXPLAIN VERBOSE +SELECT MATCH_AGAINST(ARRAY[content, 'success catches']) AS score, content FROM ftextsearch WHERE MATCH_AGAINST(ARRAY[content, 'success catches','IN BOOLEAN MODE']) != 0; + +-- text search (pushdown, result) +--Testcase 50: +SELECT content FROM ( +SELECT MATCH_AGAINST(ARRAY[content, 'success catches']) AS score, content FROM ftextsearch WHERE MATCH_AGAINST(ARRAY[content, 'success catches','IN BOOLEAN MODE']) != 0 + ) AS t; + +--Testcase 51: +DROP FOREIGN TABLE ftextsearch; + +--Testcase 52: +DROP USER MAPPING FOR CURRENT_USER SERVER server1; +--Testcase 53: +DROP SERVER server1; +--Testcase 54: +DROP EXTENSION mysql_fdw; diff --git a/sql/server_options.sql b/sql/server_options.sql new file mode 100644 index 0000000..1ec24b0 --- /dev/null +++ b/sql/server_options.sql @@ -0,0 +1,134 @@ +\set ECHO none +\ir sql/parameters.conf +\set ECHO all + +-- Before running this file User must create database mysql_fdw_regress on +-- MySQL with all permission for 'edb' user with 'edb' password and ran +-- mysql_init.sh file to create tables. + +\c contrib_regression +--Testcase 1: +CREATE EXTENSION IF NOT EXISTS mysql_fdw; +--Testcase 2: +CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw + OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); +--Testcase 3: +CREATE USER MAPPING FOR public SERVER mysql_svr + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); + +-- Validate extension, server and mapping details +--Testcase 4: +SELECT e.fdwname as "Extension", srvname AS "Server", s.srvoptions AS "Server_Options", u.umoptions AS "User_Mapping_Options" + 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; + +-- Create foreign table and perform basic SQL operations +--Testcase 5: +CREATE FOREIGN TABLE f_mysql_test(a int, b int) + SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); +--Testcase 6: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; +--Testcase 7: +INSERT INTO f_mysql_test (a, b) VALUES (2, 2); +--Testcase 8: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; +--Testcase 9: +UPDATE f_mysql_test SET b = 3 WHERE a = 2; +--Testcase 10: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; +--Testcase 11: +DELETE FROM f_mysql_test WHERE a = 2; +--Testcase 12: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; + +--Testcase 13: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 14: +DROP USER MAPPING FOR public SERVER mysql_svr; +--Testcase 15: +DROP SERVER mysql_svr; + +-- Server with init_command. +--Testcase 16: +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)'); +--Testcase 17: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 18: +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. +--Testcase 19: +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. +--Testcase 20: +CREATE FOREIGN TABLE f_init_command_check(a int) + SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'init_command_check'); +--Testcase 21: +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'); +--Testcase 22: +SELECT a, b FROM f_mysql_test; + +--Testcase 23: +DROP FOREIGN TABLE f_init_command_check; +--Testcase 24: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 25: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 26: +DROP SERVER mysql_svr1; + +-- Server with use_remote_estimate. +--Testcase 27: +CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, use_remote_estimate 'TRUE'); +--Testcase 28: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 29: +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. +--Testcase 30: +EXPLAIN (VERBOSE, COSTS OFF) SELECT a FROM f_mysql_test WHERE a < 2 ORDER BY 1; + +--Testcase 31: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 32: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 33: +DROP SERVER mysql_svr1; + +-- Create server with secure_auth. +--Testcase 34: +CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw + OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, secure_auth 'FALSE'); +--Testcase 35: +CREATE USER MAPPING FOR public SERVER mysql_svr1 + OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); +--Testcase 36: +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. +--Testcase 37: +SELECT a, b FROM f_mysql_test ORDER BY 1, 2; +--Testcase 38: +DROP FOREIGN TABLE f_mysql_test; +--Testcase 39: +DROP USER MAPPING FOR public SERVER mysql_svr1; +--Testcase 40: +DROP SERVER mysql_svr1; + +-- Cleanup +--Testcase 41: +DROP EXTENSION mysql_fdw;