pax_global_header00006660000000000000000000000064126503705250014517gustar00rootroot0000000000000052 comment=04b8a35fcf062675aeeae2fd43dc0cddd01483d0 mysql_fdw-REL-2_1_2/000077500000000000000000000000001265037052500143105ustar00rootroot00000000000000mysql_fdw-REL-2_1_2/.gitignore000066400000000000000000000000561265037052500163010ustar00rootroot00000000000000# Generated subdirectories /results/ *.o *.so mysql_fdw-REL-2_1_2/CONTRIBUTING.md000066400000000000000000000000001265037052500165270ustar00rootroot00000000000000mysql_fdw-REL-2_1_2/LICENSE000066400000000000000000000020411265037052500153120ustar00rootroot00000000000000MySQL Foreign Data Wrapper for PostgreSQL Copyright (c) 2011 - 2014, EnterpriseDB Corporation Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL ENTERPRISEDB CORPORATION BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ENTERPRISEDB CORPORATION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ENTERPRISEDB CORPORATION SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND ENTERPRISEDB CORPORATION HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. mysql_fdw-REL-2_1_2/META.json000066400000000000000000000022031265037052500157260ustar00rootroot00000000000000{ "name": "mysql_fdw", "abstract": "MySQL FDW for PostgreSQL 9.3+", "description": "This extension implements a Foreign Data Wrapper for MySQL. It is supported on PostgreSQL 9.3 and above.", "version": "2.1.2", "maintainer": [ "mysql_fdw@enterprisedb.com" ], "license": "postgresql", "provides": { "mysql_fdw": { "abstract": "MySQL FDW for PostgreSQL 9.3+", "file": "mysql_fdw--1.0.sql", "docfile": "README.md", "version": "2.1.2" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.3.0" } } }, "resources": { "bugtracker": { "web": "https://github.com/EnterpriseDB/mysql_fdw/issues" }, "repository": { "url": "https://github.com/EnterpriseDB/mysql_fdw.git", "web": "https://github.com/EnterpriseDB/mysql_fdw", "type": "git" } }, "generated_by": "mysql_fdw@enterprisedb.com", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "fdw", "mysql", "foreign data wrapper" ] } mysql_fdw-REL-2_1_2/Makefile000066400000000000000000000030261265037052500157510ustar00rootroot00000000000000######################################################################------------------------------------------------------------------------- # # mysql_fdw.c # Foreign-data wrapper for remote MySQL servers # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # # 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 EXTENSION = mysql_fdw DATA = mysql_fdw--1.0.sql REGRESS = mysql_fdw MYSQL_CONFIG = mysql_config PG_CPPFLAGS := $(shell $(MYSQL_CONFIG) --include) LIB := $(shell $(MYSQL_CONFIG) --libs) # In Debian based distros, libmariadbclient-dev provides mariadbclient (rather than mysqlclient) ifneq ($(findstring mariadbclient,$(LIB)),) MYSQL_LIB = mariadbclient else MYSQL_LIB = mysqlclient endif UNAME = uname OS := $(shell $(UNAME)) ifeq ($(OS), Darwin) DLSUFFIX = .dylib else DLSUFFIX = .so endif PG_CPPFLAGS += -D _MYSQL_LIBNAME=\"lib$(MYSQL_LIB)$(DLSUFFIX)\" ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) ifndef MAJORVERSION MAJORVERSION := $(basename $(VERSION)) endif ifeq (,$(findstring $(MAJORVERSION), 9.3 9.4 9.5 9.6)) $(error PostgreSQL 9.3, 9.4, 9.5 or 9.6 is required to compile this extension) endif else subdir = contrib/mysql_fdw top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif mysql_fdw-REL-2_1_2/README.md000066400000000000000000000220051265037052500155660ustar00rootroot00000000000000MySQL 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 only works with PostgreSQL Version 9.3 and greater, for previous version support please download from PG_92 branch. We have added a number of significant enhancements to the mysql fdw, some of the major enhancements are listed in the “enhancements” section of this document. 1. Installation --------------- To compile the [MySQL][1] foreign data wrapper, MySQL's C client library is needed. This library can be downloaded from the official [MySQL website][1]. 1. To build on POSIX-compliant systems you need to ensure the `pg_config` executable is in your path when you run `make`. This executable is typically in your PostgreSQL installation's `bin` directory. For example: ``` $ export PATH=/usr/local/pgsql/bin/:$PATH ``` 2. The `mysql_config` must also be in the path, it resides in the MySQL `bin` directory. ``` $ export PATH=/usr/local/mysql/bin/:$PATH ``` 3. Compile the code using make. ``` $ make USE_PGXS=1 ``` 4. Finally install the foreign data wrapper. ``` $ make USE_PGXS=1 install ``` Not that we have tested the `mysql_fdw` extension only on MacOS and Ubuntu systems, but other \*NIX's should also work. Enhancements ------------ The following enhancements are added to the latest version of `mysql_fdw`: ### Write-able FDW The previous version was only read-only, the latest version provides the write capability. The user can now issue insert/update and delete statements for the foreign tables using the mysql FDW. It uses the PG type casting mechanism to provide opposite type casting between mysql and PG data types. ### Connection Pooling The latest version comes with a connection pooler that utilises the same mysql database connection for all the queries in the same session. The previous version would open a new mysql database connection for every query. This is a performance enhancement. ### Where clause push-down The latest version will push-down the foreign table where clause to the foreign server. The where condition on the foreign table will be executed on the foreign server hence there will be fewer rows to to bring across to PostgreSQL. This is a performance feature. ### Column push-down The previous version was fetching all the columns from the target foreign table. The latest version does the column push-down and only brings back the columns that are part of the select target list. This is a performance feature. ### Prepared Statment (Refactoring for `select` queries to use prepared statement) 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` * `port`: Port number of the MySQL server. Defaults to `3306` * `secure_auth`: Enable or disable secure authentication. Default is `true` 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. 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. -- load extension first time after install CREATE EXTENSION mysql_fdw; -- create server object CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host '127.0.0.1', port '3306'); -- create user mapping CREATE USER MAPPING FOR postgres SERVER mysql_server OPTIONS (username 'foo', password 'bar'); -- create foreign table CREATE FOREIGN TABLE warehouse( warehouse_id int, warehouse_name text, warehouse_created datetime) 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()); -- select from table 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 -- delete row from table DELETE FROM warehouse where warehouse_id = 3; -- update a row of table UPDATE warehouse set warehouse_name = 'UPS_NEW' where warehouse_id = 1; -- explain a table 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) 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]. Support ------- This project will be modified to maintain compatibility with new PostgreSQL releases. As with many open source projects, you may be able to obtain support via the public mailing list (mysql_fdw @ enterprisedb.com). 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.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 License ------- Copyright (c) 2011 - 2014, EnterpriseDB Corporation Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. See the [`LICENSE`][5] file for full details. [1]: http://www.mysql.com [3]: https://github.com/EnterpriseDB/mysql_fdw/issues/new [4]: CONTRIBUTING.md [5]: LICENSE mysql_fdw-REL-2_1_2/connection.c000066400000000000000000000114041265037052500166130ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * connection.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * connection.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "mysql_fdw.h" #include "access/xact.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "utils/hsearch.h" #include "utils/memutils.h" #include "utils/resowner.h" /* Length of host */ #define HOST_LEN 256 /* * Connection cache hash table entry * * The lookup key in this hash table is the foreign server OID plus the user * mapping OID. (We use just one connection per user per foreign server, * so that we can ensure all scans use the same snapshot during a query.) */ typedef struct ConnCacheKey { Oid serverid; /* OID of foreign server */ Oid userid; /* OID of local user whose mapping we use */ } ConnCacheKey; typedef struct ConnCacheEntry { ConnCacheKey key; /* hash key (must be first) */ MYSQL *conn; /* connection to foreign server, or NULL */ } ConnCacheEntry; /* * Connection cache (initialized on first use) */ static HTAB *ConnectionHash = NULL; /* * mysql_get_connection: * Get a connection which can be used to execute queries on * the remote MySQL server with the user's authorization. A new connection * is established if we don't already have a suitable one. */ MYSQL* mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt) { bool found; ConnCacheEntry *entry; ConnCacheKey key; /* First time through, initialize connection cache hashtable */ if (ConnectionHash == NULL) { HASHCTL ctl; MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(ConnCacheKey); ctl.entrysize = sizeof(ConnCacheEntry); ctl.hash = tag_hash; /* allocate ConnectionHash in the cache context */ ctl.hcxt = CacheMemoryContext; ConnectionHash = hash_create("mysql_fdw connections", 8, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); } /* Create hash key for the entry. Assume no pad bytes in key struct */ key.serverid = server->serverid; key.userid = user->userid; /* * Find or create cached entry for requested connection. */ entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); if (!found) { /* initialize new hashtable entry (key is already filled in) */ entry->conn = NULL; } if (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); elog(DEBUG3, "new mysql_fdw connection %p for server \"%s\"", entry->conn, server->servername); } return entry->conn; } /* * cleanup_connection: * Delete all the cache entries on backend exists. */ void mysql_cleanup_connection(void) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; if (ConnectionHash == NULL) return; hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { if (entry->conn == NULL) continue; elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); _mysql_close(entry->conn); entry->conn = NULL; } } /* * Release connection created by calling GetConnection. */ void mysql_rel_connection(MYSQL *conn) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; if (ConnectionHash == NULL) return; hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { if (entry->conn == NULL) continue; if (entry->conn == conn) { elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); _mysql_close(entry->conn); entry->conn = NULL; hash_seq_term(&scan); break; } } } MYSQL* mysql_connect(char *svr_address, char *svr_username, char *svr_password, char *svr_database, int svr_port, bool svr_sa, char *svr_init_command) { MYSQL *conn = NULL; my_bool secure_auth = svr_sa; /* Connect to the server */ conn = _mysql_init(NULL); if (!conn) ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("failed to initialise the MySQL connection object") )); _mysql_options(conn, MYSQL_SET_CHARSET_NAME, GetDatabaseEncodingName()); _mysql_options(conn, MYSQL_SECURE_AUTH, &secure_auth); if (!svr_sa) elog(WARNING, "MySQL secure authentication is off"); if (svr_init_command != NULL) _mysql_options(conn, MYSQL_INIT_COMMAND, svr_init_command); if (!_mysql_real_connect(conn, svr_address, svr_username, svr_password, svr_database, svr_port, NULL, 0)) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), errmsg("failed to connect to MySQL: %s", _mysql_error(conn)) )); return conn; } mysql_fdw-REL-2_1_2/deparse.c000066400000000000000000001212621265037052500161030ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * deparse.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * deparse.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "mysql_fdw.h" #include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" #include "access/transam.h" #include "catalog/pg_collation.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/var.h" #include "parser/parsetree.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" static char *mysql_quote_identifier(const char *s, char q); /* * Global context for foreign_expr_walker's search of an expression tree. */ typedef struct foreign_glob_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ } foreign_glob_cxt; /* * Local (per-tree-level) context for foreign_expr_walker's search. * This is concerned with identifying collations used in the expression. */ typedef enum { FDW_COLLATE_NONE, /* expression is of a noncollatable type */ FDW_COLLATE_SAFE, /* collation derives from a foreign Var */ FDW_COLLATE_UNSAFE /* collation derives from something else */ } FDWCollateState; typedef struct foreign_loc_cxt { Oid collation; /* OID of current collation, if any */ FDWCollateState state; /* state of current collation choice */ } foreign_loc_cxt; /* * Context for deparseExpr */ typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; /* * Functions to construct string representation of a node tree. */ static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void mysql_deparse_var(Var *node, deparse_expr_cxt *context); static void mysql_deparse_const(Const *node, deparse_expr_cxt *context); static void mysql_deparse_param(Param *node, deparse_expr_cxt *context); static void mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context); static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context); static void mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context); static void mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform); static void mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context); static void mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *context); static void mysql_deparse_relabel_type(RelabelType *node, deparse_expr_cxt *context); static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context); static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context); static void mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context); static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void mysql_deparse_relation(StringInfo buf, Relation rel); static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs); static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root); /* * 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; /* obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); /* * Use value of FDW options if any, instead of the name of object itself. */ foreach(lc, table->options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "dbname") == 0) nspname = defGetString(def); else if (strcmp(def->defname, "table_name") == 0) relname = defGetString(def); } /* * Note: we could skip printing the schema name if it's pg_catalog, but * that doesn't seem worth the trouble. */ if (nspname == NULL) nspname = get_namespace_name(RelationGetNamespace(rel)); if (relname == NULL) relname = RelationGetRelationName(rel); appendStringInfo(buf, "%s.%s", mysql_quote_identifier(nspname, '`'), mysql_quote_identifier(relname, '`')); } static char * mysql_quote_identifier(const char *s , char q) { char *result = palloc(strlen(s) * 2 + 3); char *r = result; *r++ = q; while (*s) { if (*s == q) *r++ = *s; *r++ = *s; s++; } *r++ = q; *r++ = '\0'; return result; } /* * Deparese SELECT statment */ void mysql_deparse_select(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, char *svr_table, List **retrieved_attrs) { RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); Relation rel; /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ rel = heap_open(rte->relid, NoLock); appendStringInfoString(buf, "SELECT "); mysql_deparse_target_list(buf, root, baserel->relid, rel, attrs_used, retrieved_attrs); /* * Construct FROM clause */ appendStringInfoString(buf, " FROM "); mysql_deparse_relation(buf, rel); heap_close(rel, NoLock); } /* * 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) { AttrNumber pindex; bool first; ListCell *lc; appendStringInfoString(buf, "INSERT INTO "); mysql_deparse_relation(buf, rel); if (targetAttrs) { appendStringInfoChar(buf, '('); first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, attnum, root); } appendStringInfoString(buf, ") VALUES ("); pindex = 1; first = true; foreach(lc, targetAttrs) { if (!first) appendStringInfoString(buf, ", "); first = false; appendStringInfo(buf, "?"); pindex++; } appendStringInfoChar(buf, ')'); } else appendStringInfoString(buf, " DEFAULT VALUES"); } void mysql_deparse_analyze(StringInfo sql, char *dbname, char *relname) { appendStringInfo(sql, "SELECT"); appendStringInfo(sql, " round(((data_length + index_length)), 2)"); appendStringInfo(sql, " FROM information_schema.TABLES"); appendStringInfo(sql, " WHERE table_schema = '%s' AND table_name = '%s'", dbname, relname); } /* * Emit a target list that retrieves the columns specified in attrs_used. * This is used for both SELECT and RETURNING targetlists. */ static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs) { TupleDesc tupdesc = RelationGetDescr(rel); bool have_wholerow; bool first; int i; /* If there's a whole-row reference, we'll need all the columns. */ have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); first = true; *retrieved_attrs = NIL; for (i = 1; i <= tupdesc->natts; i++) { Form_pg_attribute attr = tupdesc->attrs[i - 1]; /* Ignore dropped attributes. */ if (attr->attisdropped) continue; if (have_wholerow || bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used)) { if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, i, root); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } } /* 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. * * baserel is the foreign table we're planning for. * * If no WHERE clause already exists in the buffer, is_first should be true. * * If params is not NULL, it receives a list of Params and other-relation Vars * used in the clauses; these values must be transmitted to the remote server * as parameter values. * * If params is NULL, we're generating the query for EXPLAIN purposes, * 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) { deparse_expr_cxt context; ListCell *lc; if (params) *params = NIL; /* initialize result list to empty */ /* Set up context struct for recursion */ context.root = root; context.foreignrel = baserel; context.buf = buf; context.params_list = params; foreach(lc, exprs) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); /* Connect expressions with "AND" and parenthesize each condition. */ if (is_first) appendStringInfoString(buf, " WHERE "); else appendStringInfoString(buf, " AND "); appendStringInfoChar(buf, '('); deparseExpr(ri->clause, &context); appendStringInfoChar(buf, ')'); is_first = false; } } /* * 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) { RangeTblEntry *rte; char *colname = NULL; List *options; ListCell *lc; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ Assert(!IS_SPECIAL_VARNO(varno)); /* Get RangeTblEntry from array in PlannerInfo. */ rte = planner_rt_fetch(varno, root); /* * If it's a column of a foreign table, and it has the column_name FDW * option, use that value. */ options = GetForeignColumnOptions(rte->relid, varattno); foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "column_name") == 0) { colname = defGetString(def); break; } } /* * If it's a column of a regular table or it doesn't have column_name FDW * option, use attribute name. */ if (colname == NULL) colname = get_relid_attribute_name(rte->relid, varattno); appendStringInfoString(buf, mysql_quote_identifier(colname, '`')); } static void mysql_deparse_string(StringInfo buf, const char *val, bool isstr) { const char *valptr; int i = -1; for (valptr = val; *valptr; valptr++) { char ch = *valptr; i++; if (i == 0 && isstr) appendStringInfoChar(buf, '\''); /* * Remove '{', '}' and \" character from the string. Because * this syntax is not recognize by the remote MySQL server. */ if ((ch == '{' && i == 0) || (ch == '}' && (i == (strlen(val) - 1))) || ch == '\"') continue; if (ch == ',' && isstr) { appendStringInfoChar(buf, '\''); appendStringInfoChar(buf, ch); appendStringInfoChar(buf, ' '); appendStringInfoChar(buf, '\''); continue; } appendStringInfoChar(buf, ch); } if (isstr) appendStringInfoChar(buf, '\''); } /* * Append a SQL string literal representing "val" to buf. */ static void mysql_deparse_string_literal(StringInfo buf, const char *val) { const char *valptr; appendStringInfoChar(buf, '\''); for (valptr = val; *valptr; valptr++) { char ch = *valptr; if (SQL_STR_DOUBLE(ch, true)) appendStringInfoChar(buf, ch); appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); } /* * Deparse given expression into context->buf. * * This function must support all the same node types that foreign_expr_walker * accepts. * * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization * scheme: anything more complex than a Var, Const, function call or cast * should be self-parenthesized. */ static void deparseExpr(Expr *node, deparse_expr_cxt *context) { if (node == NULL) return; switch (nodeTag(node)) { case T_Var: mysql_deparse_var((Var *) node, context); break; case T_Const: mysql_deparse_const((Const *) node, context); break; case T_Param: mysql_deparse_param((Param *) node, context); break; case T_ArrayRef: mysql_deparse_array_ref((ArrayRef *) node, context); break; case T_FuncExpr: mysql_deparse_func_expr((FuncExpr *) node, context); break; case T_OpExpr: mysql_deparse_op_expr((OpExpr *) node, context); break; case T_DistinctExpr: mysql_deparse_distinct_expr((DistinctExpr *) node, context); break; case T_ScalarArrayOpExpr: mysql_deparse_scalar_array_op_expr((ScalarArrayOpExpr *) node, context); break; case T_RelabelType: mysql_deparse_relabel_type((RelabelType *) node, context); break; case T_BoolExpr: mysql_deparse_bool_expr((BoolExpr *) node, context); break; case T_NullTest: mysql_deparse_null_test((NullTest *) node, context); break; case T_ArrayExpr: mysql_deparse_array_expr((ArrayExpr *) node, context); break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); break; } } /* * deparse remote UPDATE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, char *attname) { AttrNumber pindex; bool first; ListCell *lc; appendStringInfoString(buf, "UPDATE "); mysql_deparse_relation(buf, rel); appendStringInfoString(buf, " SET "); pindex = 2; first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (attnum == 1) continue; if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, attnum, root); appendStringInfo(buf, " = ?"); pindex++; } appendStringInfo(buf, " WHERE %s = ?", attname); } /* * deparse remote DELETE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, char *name) { appendStringInfoString(buf, "DELETE FROM "); mysql_deparse_relation(buf, rel); appendStringInfo(buf, " WHERE %s = ?", name); } /* * Deparse given Var node into context->buf. * * If the Var belongs to the foreign relation, just print its remote name. * Otherwise, it's effectively a Param (and will in fact be a Param at * run time). Handle it the same way we handle plain Params --- see * deparseParam for comments. */ static void mysql_deparse_var(Var *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; if (node->varno == context->foreignrel->relid && node->varlevelsup == 0) { /* Var belongs to foreign table */ 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; /* find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } mysql_print_remote_param(pindex, node->vartype, node->vartypmod, context); } else { mysql_print_remote_placeholder(node->vartype, node->vartypmod, context); } } } /* * Deparse given constant value into context->buf. * * This function has to be kept in sync with ruleutils.c's get_const_expr. */ static void mysql_deparse_const(Const *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; Oid typoutput; bool typIsVarlena; char *extval; if (node->constisnull) { appendStringInfoString(buf, "NULL"); return; } getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); extval = OidOutputFunctionCall(typoutput, node->constvalue); switch (node->consttype) { case INT2OID: case INT4OID: case INT8OID: case OIDOID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: { /* * No need to quote unless it's a special value such as 'NaN'. * See comments in get_const_expr(). */ if (strspn(extval, "0123456789+-eE.") == strlen(extval)) { if (extval[0] == '+' || extval[0] == '-') appendStringInfo(buf, "(%s)", extval); else appendStringInfoString(buf, extval); } else appendStringInfo(buf, "'%s'", extval); } break; case BITOID: case VARBITOID: appendStringInfo(buf, "B'%s'", extval); break; case BOOLOID: if (strcmp(extval, "t") == 0) appendStringInfoString(buf, "true"); else appendStringInfoString(buf, "false"); break; default: mysql_deparse_string_literal(buf, extval); break; } } /* * Deparse given Param node. * * If we're generating the query "for real", add the Param to * context->params_list if it's not already present, and then use its index * in that list as the remote parameter number. During EXPLAIN, there's * no need to identify a parameter number. */ static void mysql_deparse_param(Param *node, deparse_expr_cxt *context) { if (context->params_list) { int pindex = 0; ListCell *lc; /* find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } mysql_print_remote_param(pindex, node->paramtype, node->paramtypmod, context); } else { mysql_print_remote_placeholder(node->paramtype, node->paramtypmod, context); } } /* * Deparse an array subscript expression. */ static void mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; ListCell *lowlist_item; ListCell *uplist_item; /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* * Deparse referenced array expression first. If that expression includes * a cast, we have to parenthesize to prevent the array subscript from * being taken as typename decoration. We can avoid that in the typical * case of subscripting a Var, but otherwise do it. */ if (IsA(node->refexpr, Var)) deparseExpr(node->refexpr, context); else { appendStringInfoChar(buf, '('); deparseExpr(node->refexpr, context); appendStringInfoChar(buf, ')'); } /* Deparse subscript expressions. */ lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ foreach(uplist_item, node->refupperindexpr) { appendStringInfoChar(buf, '['); if (lowlist_item) { deparseExpr(lfirst(lowlist_item), context); appendStringInfoChar(buf, ':'); lowlist_item = lnext(lowlist_item); } deparseExpr(lfirst(uplist_item), context); appendStringInfoChar(buf, ']'); } appendStringInfoChar(buf, ')'); } /* * This possible that name of function in PostgreSQL and * mysql differ, so return the mysql equelent function name */ static char* mysql_replace_function(char *in) { if (strcmp(in, "btrim") == 0) { return "trim"; } return in; } /* * Deparse a function call. */ static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple proctup; Form_pg_proc procform; const char *proname; bool first; ListCell *arg; /* * Normal function: display as proname(args). */ 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 */ proname = mysql_replace_function(NameStr(procform->proname)); /* 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); } /* * Deparse given operator expression. To avoid problems around * priority of operations, we always parenthesize the arguments. */ static void mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Form_pg_operator form; char oprkind; ListCell *arg; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); oprkind = form->oprkind; /* Sanity check. */ Assert((oprkind == 'r' && list_length(node->args) == 1) || (oprkind == 'l' && list_length(node->args) == 1) || (oprkind == 'b' && list_length(node->args) == 2)); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* Deparse left operand. */ if (oprkind == 'r' || oprkind == 'b') { arg = list_head(node->args); deparseExpr(lfirst(arg), context); appendStringInfoChar(buf, ' '); } /* Deparse operator name. */ mysql_deparse_operator_name(buf, form); /* Deparse right operand. */ if (oprkind == 'l' || oprkind == 'b') { arg = list_tail(node->args); appendStringInfoChar(buf, ' '); deparseExpr(lfirst(arg), context); } appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } /* * Print the name of an operator. */ static void mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform) { char *opname; /* opname is not a SQL identifier, so we should not quote it. */ opname = NameStr(opform->oprname); /* Print schema name only if it's not pg_catalog */ if (opform->oprnamespace != PG_CATALOG_NAMESPACE) { const char *opnspname; opnspname = get_namespace_name(opform->oprnamespace); /* Print fully qualified operator name. */ appendStringInfo(buf, "OPERATOR(%s.%s)", mysql_quote_identifier(opnspname, '`'), opname); } else { if (strcmp(opname, "~~") == 0) { appendStringInfoString(buf, "LIKE BINARY"); } else if (strcmp(opname, "~~*") == 0) { appendStringInfoString(buf, "LIKE"); } else if (strcmp(opname, "!~~") == 0) { appendStringInfoString(buf, "NOT LIKE BINARY"); } else if (strcmp(opname, "!~~*") == 0) { appendStringInfoString(buf, "NOT LIKE"); } else if (strcmp(opname, "~") == 0) { appendStringInfoString(buf, "REGEXP BINARY"); } else if (strcmp(opname, "~*") == 0) { appendStringInfoString(buf, "REGEXP"); } else if (strcmp(opname, "!~") == 0) { appendStringInfoString(buf, "NOT REGEXP BINARY"); } else if (strcmp(opname, "!~*") == 0) { appendStringInfoString(buf, "NOT REGEXP"); } else { appendStringInfoString(buf, opname); } } } /* * Deparse IS DISTINCT FROM. */ static void mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; Assert(list_length(node->args) == 2); appendStringInfoChar(buf, '('); deparseExpr(linitial(node->args), context); appendStringInfoString(buf, " IS DISTINCT FROM "); deparseExpr(lsecond(node->args), context); appendStringInfoChar(buf, ')'); } /* * Deparse given ScalarArrayOpExpr expression. To avoid problems * around priority of operations, we always parenthesize the arguments. */ static void mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Expr *arg1; Expr *arg2; Form_pg_operator form; char *opname; Oid typoutput; bool typIsVarlena; char *extval; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); /* Sanity check. */ Assert(list_length(node->args) == 2); /* Deparse left operand. */ arg1 = linitial(node->args); deparseExpr(arg1, context); appendStringInfoChar(buf, ' '); opname = NameStr(form->oprname); if (strcmp(opname, "<>") == 0) appendStringInfo(buf, " NOT "); /* Deparse operator name plus decoration. */ appendStringInfo(buf, " IN ("); /* Deparse right operand. */ arg2 = lsecond(node->args); switch (nodeTag((Node*)arg2)) { case T_Const: { Const *c = (Const*)arg2; if (!c->constisnull) { getTypeOutputInfo(c->consttype, &typoutput, &typIsVarlena); extval = OidOutputFunctionCall(typoutput, c->constvalue); switch (c->consttype) { case INT4ARRAYOID: case OIDARRAYOID: mysql_deparse_string(buf, extval, false); break; default: mysql_deparse_string(buf, extval, true); break; } } else { appendStringInfoString(buf, " NULL"); return; } } break; default: deparseExpr(arg2, context); break; } appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } /* * Deparse a RelabelType (binary-compatible cast) node. */ static void mysql_deparse_relabel_type(RelabelType *node, deparse_expr_cxt *context) { deparseExpr(node->arg, context); } /* * Deparse a BoolExpr node. * * Note: by the time we get here, AND and OR expressions have been flattened * into N-argument form, so we'd better be prepared to deal with that. */ static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; const char *op = NULL; /* keep compiler quiet */ bool first; ListCell *lc; switch (node->boolop) { case AND_EXPR: op = "AND"; break; case OR_EXPR: op = "OR"; break; case NOT_EXPR: appendStringInfoString(buf, "(NOT "); deparseExpr(linitial(node->args), context); appendStringInfoChar(buf, ')'); return; } appendStringInfoChar(buf, '('); first = true; foreach(lc, node->args) { if (!first) appendStringInfo(buf, " %s ", op); deparseExpr((Expr *) lfirst(lc), context); first = false; } appendStringInfoChar(buf, ')'); } /* * Deparse IS [NOT] NULL expression. */ static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfoChar(buf, '('); deparseExpr(node->arg, context); if (node->nulltesttype == IS_NULL) appendStringInfoString(buf, " IS NULL)"); else appendStringInfoString(buf, " IS NOT NULL)"); } /* * Deparse ARRAY[...] construct. */ static void mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool first = true; ListCell *lc; appendStringInfoString(buf, "ARRAY["); foreach(lc, node->elements) { if (!first) appendStringInfoString(buf, ", "); deparseExpr(lfirst(lc), context); first = false; } appendStringInfoChar(buf, ']'); } /* * Print the representation of a parameter to be sent to the remote side. * * Note: we always label the Param's type explicitly rather than relying on * transmitting a numeric type OID in PQexecParams(). This allows us to * avoid assuming that types have the same OIDs on the remote side as they * do locally --- they need only have the same names. */ static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfo(buf, "?"); } static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfo(buf, "(SELECT null)"); } /* * Return true if given object is one of PostgreSQL's built-in objects. * * We use FirstBootstrapObjectId as the cutoff, so that we only consider * objects with hand-assigned OIDs to be "built in", not for instance any * function or type defined in the information_schema. * * Our constraints for dealing with types are tighter than they are for * functions or operators: we want to accept only types that are in pg_catalog, * else format_type might incorrectly fail to schema-qualify their names. * (This could be fixed with some changes to format_type, but for now there's * no need.) Thus we must exclude information_schema types. * * XXX there is a problem with this, which is that the set of built-in * objects expands over time. Something that is built-in to us might not * be known to the remote server, if it's of an older version. But keeping * track of that would be a huge exercise. */ static bool is_builtin(Oid oid) { return (oid < FirstBootstrapObjectId); } /* * Check if expression is safe to execute remotely, and return true if so. * * In addition, *outer_cxt is updated with collation information. * * We must check that the expression contains only node types we can deparse, * that all types/functions/operators are safe to send (which we approximate * as being built-in), and that all collations used in the expression derive * from Vars of the foreign table. Because of the latter, the logic is * pretty close to assign_collations_walker() in parse_collate.c, though we * can assume here that the given expression is valid. */ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt) { bool check_type = true; foreign_loc_cxt inner_cxt; Oid collation; FDWCollateState state; /* Need do nothing for empty subexpressions */ if (node == NULL) return true; /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; inner_cxt.state = FDW_COLLATE_NONE; switch (nodeTag(node)) { case T_Var: { Var *var = (Var *) node; /* * If the Var is from the foreign table, we consider its * collation (if any) safe to use. If it is from another * table, we treat its collation the same way as we would a * Param's collation, ie it's not safe for it to have a * non-default collation. */ if (var->varno == glob_cxt->foreignrel->relid && var->varlevelsup == 0) { /* Var belongs to foreign table */ collation = var->varcollid; state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; } else { /* Var belongs to some other table */ if (var->varcollid != InvalidOid && var->varcollid != DEFAULT_COLLATION_OID) return false; /* We can consider that it doesn't set collation */ collation = InvalidOid; state = FDW_COLLATE_NONE; } } break; case T_Const: { Const *c = (Const *) node; /* * If the constant has nondefault collation, either it's of a * non-builtin type, or it reflects folding of a CollateExpr; * either way, it's unsafe to send to the remote. */ if (c->constcollid != InvalidOid && c->constcollid != DEFAULT_COLLATION_OID) return false; /* Otherwise, we can consider that it doesn't set collation */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_Param: { /* We are not supporting param push down*/ return false; } break; case T_ArrayRef: { ArrayRef *ar = (ArrayRef *) node;; /* Assignment should not be in restrictions. */ if (ar->refassgnexpr != NULL) return false; /* * Recurse to remaining subexpressions. Since the array * subscripts must yield (noncollatable) integers, they won't * affect the inner_cxt state. */ if (!foreign_expr_walker((Node *) ar->refupperindexpr, glob_cxt, &inner_cxt)) return false; if (!foreign_expr_walker((Node *) ar->reflowerindexpr, glob_cxt, &inner_cxt)) return false; if (!foreign_expr_walker((Node *) ar->refexpr, glob_cxt, &inner_cxt)) return false; /* * Array subscripting should yield same collation as input, * but for safety use same logic as for function nodes. */ collation = ar->refcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_FuncExpr: { FuncExpr *fe = (FuncExpr *) node; /* * If function used by the expression is not built-in, it * can't be sent to remote because it might have incompatible * semantics on remote side. */ if (!is_builtin(fe->funcid)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) fe->args, glob_cxt, &inner_cxt)) return false; /* * If function's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (fe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || fe->inputcollid != inner_cxt.collation) return false; /* * Detect whether node is introducing a collation not derived * from a foreign Var. (If so, we just mark it unsafe for now * rather than immediately returning false, since the parent * node might not care.) */ collation = fe->funccollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_OpExpr: case T_DistinctExpr: /* struct-equivalent to OpExpr */ { OpExpr *oe = (OpExpr *) node; /* * Similarly, only built-in operators can be sent to remote. * (If the operator is, surely its underlying function is * too.) */ if (!is_builtin(oe->opno)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt)) return false; /* * If operator's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (oe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) return false; /* Result-collation handling is same as for functions */ collation = oe->opcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_ScalarArrayOpExpr: { ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; /* * Again, only built-in operators can be sent to remote. */ if (!is_builtin(oe->opno)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt)) return false; /* * If operator's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (oe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_RelabelType: { RelabelType *r = (RelabelType *) node; /* * Recurse to input subexpression. */ if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt)) return false; /* * RelabelType must not introduce a collation not derived from * an input foreign Var. */ collation = r->resultcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_BoolExpr: { BoolExpr *b = (BoolExpr *) node; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) b->args, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_NullTest: { NullTest *nt = (NullTest *) node; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) nt->arg, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_ArrayExpr: { ArrayExpr *a = (ArrayExpr *) node; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) a->elements, glob_cxt, &inner_cxt)) return false; /* * ArrayExpr must not introduce a collation not derived from * an input foreign Var. */ collation = a->array_collid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_List: { List *l = (List *) node; ListCell *lc; /* * Recurse to component subexpressions. */ foreach(lc, l) { if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt)) return false; } /* * When processing a list, collation state just bubbles up * from the list elements. */ collation = inner_cxt.collation; state = inner_cxt.state; /* Don't apply exprType() to the list. */ check_type = false; } break; default: /* * If it's anything else, assume it's unsafe. This list can be * expanded later, but don't forget to add deparse support below. */ return false; } /* * If result type of given expression is not built-in, it can't be sent to * remote because it might have incompatible semantics on remote side. */ if (check_type && !is_builtin(exprType(node))) return false; /* * Now, merge my collation information into my parent's state. */ if (state > outer_cxt->state) { /* Override previous parent state */ outer_cxt->collation = collation; outer_cxt->state = state; } else if (state == outer_cxt->state) { /* Merge, or detect error if there's a collation conflict */ switch (state) { case FDW_COLLATE_NONE: /* Nothing + nothing is still nothing */ break; case FDW_COLLATE_SAFE: if (collation != outer_cxt->collation) { /* * Non-default collation always beats default. */ if (outer_cxt->collation == DEFAULT_COLLATION_OID) { /* Override previous parent state */ outer_cxt->collation = collation; } else if (collation != DEFAULT_COLLATION_OID) { /* * Conflict; show state as indeterminate. We don't * want to "return false" right away, since parent * node might not care about collation. */ outer_cxt->state = FDW_COLLATE_UNSAFE; } } break; case FDW_COLLATE_UNSAFE: /* We're still conflicted ... */ break; } } /* It looks OK */ return true; } /* * Returns true if given expr is safe to evaluate on the foreign server. */ bool 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; } mysql_fdw-REL-2_1_2/expected/000077500000000000000000000000001265037052500161115ustar00rootroot00000000000000mysql_fdw-REL-2_1_2/expected/mysql_fdw.out000066400000000000000000000262021265037052500206510ustar00rootroot00000000000000\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'); 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')); 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) DELETE FROM employee; DELETE FROM department; DELETE FROM empdata; 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; mysql_fdw-REL-2_1_2/mysql_fdw--1.0.sql000066400000000000000000000013251265037052500174100ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw--1.0.sql * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.0.sql * *------------------------------------------------------------------------- */ CREATE FUNCTION mysql_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION mysql_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; mysql_fdw-REL-2_1_2/mysql_fdw.c000066400000000000000000001640271265037052500164730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "mysql_fdw.h" #include #include #include #include #include #include #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 "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 "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 "optimizer/var.h" #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 "mysql_query.h" #define DEFAULTE_NUM_ROWS 1000 PG_MODULE_MAGIC; typedef struct MySQLFdwRelationInfo { /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */ List *remote_conds; List *local_conds; /* Bitmap of attr numbers we need to fetch from the remote server. */ Bitmapset *attrs_used; } 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); PG_FUNCTION_INFO_V1(mysql_fdw_handler); /* * FDW callback routines */ static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es); static void mysqlBeginForeignScan(ForeignScanState *node, int eflags); static TupleTableSlot *mysqlIterateForeignScan(ForeignScanState *node); static void mysqlReScanForeignScan(ForeignScanState *node); static void mysqlEndForeignScan(ForeignScanState *node); static List *mysqlPlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index); static void mysqlBeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, List *fdw_private, int subplan_index, int eflags); static TupleTableSlot *mysqlExecForeignInsert(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot); 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 #if PG_VERSION_NUM >= 90500 ,Plan * outer_plan #endif ); 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); #endif static bool mysql_is_column_unique(Oid foreigntableid); void* mysql_dll_handle = NULL; static int wait_timeout = WAIT_TIMEOUT; static int interactive_timeout = INTERACTIVE_TIMEOUT; /* * mysql_load_library function dynamically load the mysql's library * libmysqlclient.so. The only reason to load the library using dlopen * is that, mysql and postgres both have function with same name like * "list_delete", "list_delete" and "list_free" which cause compiler * error "duplicate function name" and erroneously linking with a function. * This port of the code is used to avoid the compiler error. * * #define list_delete mysql_list_delete * #include * #undef list_delete * * But system crashed on function mysql_stmt_close function because * mysql_stmt_close internally calling "list_delete" function which * wrongly binds to postgres' "list_delete" function. * * The dlopen function provides a parameter "RTLD_DEEPBIND" which * solved the binding issue. * * RTLD_DEEPBIND: * Place the lookup scope of the symbols in this library ahead of the * global scope. This means that a self-contained library will use its * own symbols in preference to global symbols with the same name contained * in libraries that have already been loaded. */ bool mysql_load_library(void) { #if defined(__APPLE__) /* * Mac OS/BSD does not support RTLD_DEEPBIND, but it still * works without the RTLD_DEEPBIND */ mysql_dll_handle = dlopen(_MYSQL_LIBNAME, RTLD_LAZY); #else mysql_dll_handle = dlopen(_MYSQL_LIBNAME, RTLD_LAZY | RTLD_DEEPBIND); #endif if(mysql_dll_handle == NULL) return false; _mysql_stmt_bind_param = dlsym(mysql_dll_handle, "mysql_stmt_bind_param"); _mysql_stmt_bind_result = dlsym(mysql_dll_handle, "mysql_stmt_bind_result"); _mysql_stmt_init = dlsym(mysql_dll_handle, "mysql_stmt_init"); _mysql_stmt_prepare = dlsym(mysql_dll_handle, "mysql_stmt_prepare"); _mysql_stmt_execute = dlsym(mysql_dll_handle, "mysql_stmt_execute"); _mysql_stmt_fetch = dlsym(mysql_dll_handle, "mysql_stmt_fetch"); _mysql_query = dlsym(mysql_dll_handle, "mysql_query"); _mysql_stmt_result_metadata = dlsym(mysql_dll_handle, "mysql_stmt_result_metadata"); _mysql_stmt_store_result = dlsym(mysql_dll_handle, "mysql_stmt_store_result"); _mysql_fetch_row = dlsym(mysql_dll_handle, "mysql_fetch_row"); _mysql_fetch_field = dlsym(mysql_dll_handle, "mysql_fetch_field"); _mysql_fetch_fields = dlsym(mysql_dll_handle, "mysql_fetch_fields"); _mysql_stmt_close = dlsym(mysql_dll_handle, "mysql_stmt_close"); _mysql_stmt_reset = dlsym(mysql_dll_handle, "mysql_stmt_reset"); _mysql_free_result = dlsym(mysql_dll_handle, "mysql_free_result"); _mysql_error = dlsym(mysql_dll_handle, "mysql_error"); _mysql_options = dlsym(mysql_dll_handle, "mysql_options"); _mysql_real_connect = dlsym(mysql_dll_handle, "mysql_real_connect"); _mysql_close = dlsym(mysql_dll_handle, "mysql_close"); _mysql_init = dlsym(mysql_dll_handle, "mysql_init"); _mysql_stmt_attr_set = dlsym(mysql_dll_handle, "mysql_stmt_attr_set"); _mysql_store_result = dlsym(mysql_dll_handle, "mysql_store_result"); _mysql_stmt_errno = dlsym(mysql_dll_handle, "mysql_stmt_errno"); _mysql_errno = dlsym(mysql_dll_handle, "mysql_errno"); _mysql_num_fields = dlsym(mysql_dll_handle, "mysql_num_fields"); _mysql_num_rows = dlsym(mysql_dll_handle, "mysql_num_rows"); if (_mysql_stmt_bind_param == NULL || _mysql_stmt_bind_result == NULL || _mysql_stmt_init == NULL || _mysql_stmt_prepare == NULL || _mysql_stmt_execute == NULL || _mysql_stmt_fetch == NULL || _mysql_query == NULL || _mysql_stmt_result_metadata == NULL || _mysql_stmt_store_result == NULL || _mysql_fetch_row == NULL || _mysql_fetch_field == NULL || _mysql_fetch_fields == NULL || _mysql_stmt_close == NULL || _mysql_stmt_reset == NULL || _mysql_free_result == NULL || _mysql_error == NULL || _mysql_options == NULL || _mysql_real_connect == NULL || _mysql_close == NULL || _mysql_init == NULL || _mysql_stmt_attr_set == NULL || _mysql_store_result == NULL || _mysql_stmt_errno == NULL || _mysql_errno == NULL || _mysql_num_fields == NULL || _mysql_num_rows == NULL) return false; return true; } /* * Library load-time initialization, sets on_proc_exit() callback for * backend shutdown. */ void _PG_init(void) { if (!mysql_load_library()) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to load the mysql query: \n%s", dlerror()), errhint("export LD_LIBRARY_PATH to locate the library"))); DefineCustomIntVariable("mysql_fdw.wait_timeout", "Server-side wait_timeout", "Set the maximum wait_timeout" "use to set the MySQL session timeout", &wait_timeout, WAIT_TIMEOUT, 0, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomIntVariable("mysql_fdw.interactive_timeout", "Server-side interactive timeout", "Set the maximum interactive timeout" "use to set the MySQL session timeout", &interactive_timeout, INTERACTIVE_TIMEOUT, 0, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL); on_proc_exit(&mysql_fdw_exit, PointerGetDatum(NULL)); } /* * mysql_fdw_exit: Exit callback function. */ static void mysql_fdw_exit(int code, Datum arg) { mysql_cleanup_connection(); } /* * Foreign-data wrapper handler function: return * a struct with pointers to my callback routines. */ Datum mysql_fdw_handler(PG_FUNCTION_ARGS) { FdwRoutine *fdwroutine = makeNode(FdwRoutine); /* Callback functions for readable FDW */ 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; fdwroutine->AddForeignUpdateTargets = mysqlAddForeignUpdateTargets; fdwroutine->ExecForeignUpdate = mysqlExecForeignUpdate; fdwroutine->ExecForeignDelete = mysqlExecForeignDelete; fdwroutine->EndForeignModify = mysqlEndForeignModify; PG_RETURN_POINTER(fdwroutine); } /* * mysqlBeginForeignScan: Initiate access to the database */ static void mysqlBeginForeignScan(ForeignScanState *node, int eflags) { TupleTableSlot *tupleSlot = node->ss.ss_ScanTupleSlot; TupleDesc tupleDescriptor = tupleSlot->tts_tupleDescriptor; MYSQL *conn = 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]; /* * We'll save private state in node->fdw_state. */ festate = (MySQLFdwExecState *) palloc(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); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ festate->rel = node->ss.ss_currentRelation; table = GetForeignTable(RelationGetRelid(festate->rel)); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch the options */ options = mysql_get_options(RelationGetRelid(node->ss.ss_currentRelation)); /* * 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->conn = conn; festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); if (wait_timeout > 0) { /* Set the session timeout in seconds*/ sprintf(timeout, "SET wait_timeout = %d", wait_timeout); _mysql_query(festate->conn, timeout); } if (interactive_timeout > 0) { /* Set the session timeout in seconds*/ sprintf(timeout, "SET interactive_timeout = %d", interactive_timeout); _mysql_query(festate->conn, timeout); } _mysql_query(festate->conn, "SET time_zone = '+00:00'"); _mysql_query(festate->conn, "SET sql_mode='ANSI_QUOTES'"); /* Initialize the MySQL statement */ 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))); } /* 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; 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; } } /* int column_count = mysql_num_fields(festate->meta); */ /* Set the statement as cursor type */ _mysql_stmt_attr_set(festate->stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); /* Set the pre-fetch rows */ _mysql_stmt_attr_set(festate->stmt, STMT_ATTR_PREFETCH_ROWS, (void*) &prefetch_rows); 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_fields = _mysql_fetch_fields(festate->table->_mysql_res); foreach(lc, festate->retrieved_attrs) { int attnum = lfirst_int(lc) - 1; Oid pgtype = tupleDescriptor->attrs[attnum]->atttypid; int32 pgtypmod = tupleDescriptor->attrs[attnum]->atttypmod; if (tupleDescriptor->attrs[attnum]->attisdropped) continue; festate->table->column[atindex]._mysql_bind = &festate->table->_mysql_bind[atindex]; mysql_bind_result(pgtype, pgtypmod, &festate->table->_mysql_fields[atindex], &festate->table->column[atindex]); atindex++; } /* Bind the results pointers for the prepare statements */ if (_mysql_stmt_bind_result(festate->stmt, festate->table->_mysql_bind) != 0) { 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(" 7 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; } } } /* * 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; memset (tupleSlot->tts_values, 0, sizeof(Datum) * tupleDescriptor->natts); memset (tupleSlot->tts_isnull, true, sizeof(bool) * tupleDescriptor->natts); ExecClearTuple(tupleSlot); attid = 0; rc = _mysql_stmt_fetch(festate->stmt); if (0 == rc) { foreach(lc, festate->retrieved_attrs) { int attnum = lfirst_int(lc) - 1; Oid pgtype = tupleDescriptor->attrs[attnum]->atttypid; int32 pgtypmod = tupleDescriptor->attrs[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]); attid++; } ExecStoreVirtualTuple(tupleSlot); } else if (1 == rc) { /* Error occurred. Error code and message can be obtained by calling mysql_stmt_errno() and mysql_stmt_error(). */ } else if (MYSQL_NO_DATA == rc) { /* No more rows/data exists */ } else if (MYSQL_DATA_TRUNCATED == rc) { /* Data truncation occurred */ /* MYSQL_DATA_TRUNCATED is returned when truncation reporting is enabled. To determine which column values were truncated when this value is returned, check the error members of the MYSQL_BIND structures used for fetching values. Truncation reporting is enabled by default, but can be controlled by calling mysql_options() with the MYSQL_REPORT_DATA_TRUNCATION option. */ } return tupleSlot; } /* * mysqlExplainForeignScan: Produce extra output for EXPLAIN */ static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; mysql_opt *options; /* Fetch options */ options = mysql_get_options(RelationGetRelid(node->ss.ss_currentRelation)); /* 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) ExplainPropertyLong("Local server startup cost", 10, es); else ExplainPropertyLong("Remote server startup cost", 25, es); ExplainPropertyText("Remote query", festate->query, es); } } /* * mysqlEndForeignScan: Finish scanning foreign table and dispose * objects used for this scan */ static void mysqlEndForeignScan(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; if (festate->table) { if (festate->table->_mysql_res) { _mysql_free_result(festate->table->_mysql_res); festate->table->_mysql_res = NULL; } } if (festate->stmt) { _mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* * mysqlReScanForeignScan: Rescan table, possibly with new parameters */ static void mysqlReScanForeignScan(ForeignScanState *node) { /* TODO: Need to implement rescan */ } /* * mysqlGetForeignRelSize: Create a FdwPlan for a scan on the foreign table */ static void 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; MySQLFdwRelationInfo *fpinfo; ListCell *lc; MYSQL_FIELD *field; int i; int num_fields; List *params_list = NULL; fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; table = GetForeignTable(foreigntableid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch options */ options = mysql_get_options(foreigntableid); /* Connect to the server */ conn = mysql_get_connection(server, user, options); _mysql_query(conn, "SET sql_mode='ANSI_QUOTES'"); pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &attrs_used); foreach(lc, baserel->baserestrictinfo) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); if (is_foreign_expr(root, baserel, ri->clause)) fpinfo->remote_conds = lappend(fpinfo->remote_conds, ri); else fpinfo->local_conds = lappend(fpinfo->local_conds, ri); } pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &fpinfo->attrs_used); foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); pull_varattnos((Node *) rinfo->clause, baserel->relid, &fpinfo->attrs_used); } if (options->use_remote_estimate) { initStringInfo(&sql); appendStringInfo(&sql, "EXPLAIN "); mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs); if (fpinfo->remote_conds) 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; 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); 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. */ row = _mysql_fetch_row(result); num_fields = _mysql_num_fields(result); if (row) { 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]); } else if (strcmp(field->name, "filtered") == 0) { if (row[i]) filtered = atof(row[i]); } } } _mysql_free_result(result); } } if (rows > 0) rows = ((rows + 1) * filtered) / 100; else rows = DEFAULTE_NUM_ROWS; baserel->rows = rows; baserel->tuples = rows; } 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; table = GetForeignTable(foreigntableid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch the options */ options = mysql_get_options(foreigntableid); /* Connect to the server */ conn = mysql_get_connection(server, user, options); /* 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))); } } } result = _mysql_store_result(conn); if (result) { int num_fields = _mysql_num_fields(result); row = _mysql_fetch_row(result); if (row && num_fields > 3) { if ((strcmp(row[3], "PRI") == 0) || (strcmp(row[3], "UNI")) == 0) { _mysql_free_result(result); return true; } } _mysql_free_result(result); } return false; } /* * mysqlEstimateCosts: Estimate the remote query cost */ static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid) { mysql_opt *options; /* Fetch options */ options = mysql_get_options(foreigntableid); /* Local databases are probably faster */ if (strcmp(options->svr_address, "127.0.0.1") == 0 || strcmp(options->svr_address, "localhost") == 0) *startup_cost = 10; else *startup_cost = 25; *total_cost = baserel->rows + *startup_cost; } /* * mysqlGetForeignPaths: Get the foreign paths */ static void mysqlGetForeignPaths(PlannerInfo *root,RelOptInfo *baserel,Oid foreigntableid) { Cost startup_cost; Cost total_cost; /* Estimate costs */ mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, foreigntableid); /* Create a ForeignPath node and add it as only possible path */ add_path(baserel, (Path *) create_foreignscan_path(root, baserel, baserel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ NULL, /* no outer rel either */ #if PG_VERSION_NUM >= 90500 NULL, /* no extra plan */ #endif NULL)); /* no fdw_private data */ } /* * 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 #endif ) { MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) baserel->fdw_private; Index scan_relid = baserel->relid; List *fdw_private; List *local_exprs = NULL; List *params_list = NULL; List *remote_conds = NIL; StringInfoData sql; mysql_opt *options; List *retrieved_attrs; ListCell *lc; /* Fetch options */ options = mysql_get_options(foreigntableid); /* * Build the query string to be sent for execution, and identify * expressions to be sent as parameters. */ /* Build the query */ initStringInfo(&sql); /* * Separate the scan_clauses into those that can be executed remotely and * those that can't. baserestrictinfo clauses that were previously * determined to be safe or unsafe by classifyConditions are shown in * fpinfo->remote_conds and fpinfo->local_conds. Anything else in the * scan_clauses list will be a join clause, which we have to check for * remote-safety. * * Note: the join clauses we see here should be the exact same ones * previously examined by postgresGetForeignPaths. Possibly it'd be worth * passing forward the classification work done then, rather than * repeating it here. * * This code must match "extract_actual_clauses(scan_clauses, false)" * except for the additional decision about remote versus local execution. * Note however that we only strip the RestrictInfo nodes from the * local_exprs list, since appendWhereClause expects a list of * RestrictInfos. */ foreach(lc, scan_clauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Assert(IsA(rinfo, RestrictInfo)); /* Ignore any pseudoconstants, they're dealt with elsewhere */ if (rinfo->pseudoconstant) continue; if (list_member_ptr(fpinfo->remote_conds, rinfo)) remote_conds = lappend(remote_conds, rinfo); else if (list_member_ptr(fpinfo->local_conds, rinfo)) local_exprs = lappend(local_exprs, rinfo->clause); else if (is_foreign_expr(root, baserel, rinfo->clause)) remote_conds = lappend(remote_conds, rinfo); else local_exprs = lappend(local_exprs, rinfo->clause); } mysql_deparse_select(&sql, root, baserel, fpinfo->attrs_used, options->svr_table, &retrieved_attrs); if (remote_conds) mysql_append_where_clause(&sql, root, baserel, remote_conds, true, ¶ms_list); if (baserel->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"); } /* * Build the fdw_private list that will be available to the executor. * Items in the list must match enum FdwScanPrivateIndex, above. */ fdw_private = list_make2(makeString(sql.data), retrieved_attrs); /* * Create the ForeignScan node from target list, local filtering * expressions, remote parameter expressions, and FDW private information. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ return make_foreignscan(tlist ,local_exprs ,scan_relid ,params_list ,fdw_private #if PG_VERSION_NUM >= 90500 ,NIL ,NIL ,outer_plan #endif ); } /* * mysqlAnalyzeForeignTable: Implement stats collection */ static bool mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages) { StringInfoData sql; double table_size = 0; MYSQL *conn; MYSQL_RES *result; MYSQL_ROW row; Oid foreignTableId = RelationGetRelid(relation); mysql_opt *options; char *relname; ForeignServer *server; UserMapping *user; ForeignTable *table; table = GetForeignTable(foreignTableId); server = GetForeignServer(table->serverid); user = GetUserMapping(relation->rd_rel->relowner, server->serverid); /* Fetch options */ options = mysql_get_options(foreignTableId); /* Connect to the server */ conn = mysql_get_connection(server, user, options); /* Build the query */ initStringInfo(&sql); /* If no table name specified, use the foreign table name */ relname = options->svr_table; if ( relname == NULL) relname = RelationGetRelationName(relation); mysql_deparse_analyze(&sql, options->svr_database, relname); 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: { 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); table_size = atof(row[0]); _mysql_free_result(result); } *totalpages = table_size / MYSQL_BLKSIZ; return false; } static List * mysqlPlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index) { CmdType operation = plan->operation; RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); Relation rel; List *targetAttrs = NULL; StringInfoData sql; char *attname; Oid foreignTableId; initStringInfo(&sql); /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ rel = heap_open(rte->relid, NoLock); foreignTableId = RelationGetRelid(rel); if (!mysql_is_column_unique(foreignTableId)) elog(ERROR, "first column of remote table must be unique for INSERT/UPDATE/DELETE operation"); if (operation == CMD_INSERT) { TupleDesc tupdesc = RelationGetDescr(rel); int attnum; for (attnum = 1; attnum <= tupdesc->natts; attnum++) { Form_pg_attribute attr = tupdesc->attrs[attnum - 1]; if (!attr->attisdropped) targetAttrs = lappend_int(targetAttrs, attnum); } } 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); } /* We also want the rowid column to be available for the update */ targetAttrs = lcons_int(1, targetAttrs); } else { targetAttrs = lcons_int(1, targetAttrs); } attname = get_relid_attribute_name(foreignTableId, 1); /* * Construct the SQL command string. */ switch (operation) { case CMD_INSERT: mysql_deparse_insert(&sql, root, resultRelation, rel, targetAttrs); break; case CMD_UPDATE: mysql_deparse_update(&sql, root, resultRelation, rel, targetAttrs, attname); break; case CMD_DELETE: mysql_deparse_delete(&sql, root, resultRelation, rel, attname); break; default: elog(ERROR, "unexpected operation: %d", (int) operation); break; } if (plan->returningLists) elog(ERROR, "RETURNING is not supported by this FDW"); heap_close(rel, NoLock); return list_make2(makeString(sql.data), targetAttrs); } /* * mysqlBeginForeignModify: Begin an insert/update/delete operation * on a foreign table */ static void mysqlBeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, List *fdw_private, int subplan_index, int eflags) { MySQLFdwExecState *fmstate = 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; rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); foreignTableId = RelationGetRelid(rel); table = GetForeignTable(foreignTableId); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* * Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState * stays NULL. */ if (eflags & EXEC_FLAG_EXPLAIN_ONLY) return; /* Begin constructing MongoFdwModifyState. */ fmstate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); fmstate->rel = rel; fmstate->mysqlFdwOptions = mysql_get_options(foreignTableId); fmstate->conn = mysql_get_connection(server, user, fmstate->mysqlFdwOptions); fmstate->query = strVal(list_nth(fdw_private, 0)); fmstate->retrieved_attrs = (List *) list_nth(fdw_private, 1); n_params = list_length(fmstate->retrieved_attrs) + 1; fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params); fmstate->p_nums = 0; fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); /* Set up for remaining transmittable parameters */ foreach(lc, fmstate->retrieved_attrs) { int attnum = lfirst_int(lc); Form_pg_attribute attr = RelationGetDescr(rel)->attrs[attnum - 1]; Assert(!attr->attisdropped); getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena); fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); fmstate->p_nums++; } Assert(fmstate->p_nums <= n_params); n_params = list_length(fmstate->retrieved_attrs); /* Initialize mysql statment */ fmstate->stmt = _mysql_stmt_init(fmstate->conn); if (!fmstate->stmt) { 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) )); } /* 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; 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 */ static TupleTableSlot * mysqlExecForeignInsert(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate; MYSQL_BIND *mysql_bind_buffer = NULL; ListCell *lc; Datum value = 0; int n_params = 0; MemoryContext oldcontext; 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_query(fmstate->conn, "SET time_zone = '+00:00'"); _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 = slot->tts_tupleDescriptor->attrs[attnum]->atttypid; value = slot_getattr(slot, attnum + 1, &isnull[attnum]); mysql_bind_sql_var(type, attnum, value, mysql_bind_buffer, &isnull[attnum]); } /* Bind values */ if (_mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) { switch(_mysql_stmt_errno(fmstate->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(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; 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; } static TupleTableSlot * mysqlExecForeignUpdate(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; Relation rel = resultRelInfo->ri_RelationDesc; MYSQL_BIND *mysql_bind_buffer = 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; n_params = list_length(fmstate->retrieved_attrs); mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * n_params); isnull = palloc0(sizeof(bool) * n_params); /* Bind the values */ foreach(lc, fmstate->retrieved_attrs) { int attnum = lfirst_int(lc); Oid type; /* first attribute cannot be in target list attribute */ if (attnum == 1) continue; type = slot->tts_tupleDescriptor->attrs[attnum - 1]->atttypid; value = slot_getattr(slot, attnum, (bool*)(&isnull[i])); mysql_bind_sql_var(type, bindnum, value, mysql_bind_buffer, &isnull[i]); bindnum++; i++; } /* 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); if (_mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) { char *err = pstrdup(_mysql_error(fmstate->conn)); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to bind the MySQL query: %s", err) )); } if (_mysql_stmt_execute(fmstate->stmt) != 0) { switch(_mysql_stmt_errno(fmstate->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(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. */ static void mysqlAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation) { Var *var = NULL; const char *attrname = NULL; TargetEntry *tle = NULL; /* * What we need is the rowid which is the first column */ Form_pg_attribute attr = RelationGetDescr(target_relation)->attrs[0]; /* Make a Var representing the desired value */ var = makeVar(parsetree->resultRelation, 1, attr->atttypid, attr->atttypmod, InvalidOid, 0); /* Wrap it in a TLE with the right name ... */ attrname = NameStr(attr->attname); tle = makeTargetEntry((Expr *) var, list_length(parsetree->targetList) + 1, pstrdup(attrname), true); /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); } /* * MongoExecForeignDelete: Delete one row from a foreign table */ static TupleTableSlot * mysqlExecForeignDelete(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; Relation rel = resultRelInfo->ri_RelationDesc; MYSQL_BIND *mysql_bind_buffer = NULL; Oid foreignTableId = RelationGetRelid(rel); bool is_null = false; int bindnum = 0; Oid typeoid; Datum value = 0; mysql_bind_buffer = (MYSQL_BIND*) palloc0(sizeof(MYSQL_BIND) * 1); /* 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); if (_mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) { char *err = pstrdup(_mysql_error(fmstate->conn)); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to execute the MySQL query: %s", err) )); } if (_mysql_stmt_execute(fmstate->stmt) != 0) { switch(_mysql_stmt_errno(fmstate->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(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 * Finish an insert/update/delete operation on a foreign table */ static void mysqlEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo) { MySQLFdwExecState *festate = resultRelInfo->ri_FdwState; if (festate && festate->stmt) { _mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* * 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 = 'blob' THEN 'bytea'" " WHEN c.DATA_TYPE = 'mediumblob' 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]; 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; } #endif mysql_fdw-REL-2_1_2/mysql_fdw.control000066400000000000000000000010441265037052500177160ustar00rootroot00000000000000########################################################################## # # mysql_fdw.control # Foreign-data wrapper for remote MySQL servers # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # # Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. # # IDENTIFICATION # mysql_fdw.control # ########################################################################## comment = 'Foreign data wrapper for querying a MySQL server' default_version = '1.0' module_pathname = '$libdir/mysql_fdw' relocatable = true mysql_fdw-REL-2_1_2/mysql_fdw.h000066400000000000000000000143621265037052500164740ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw.h * *------------------------------------------------------------------------- */ #ifndef MYSQL_FDW_H #define MYSQL_FDW_H #define list_length mysql_list_length #define list_delete mysql_list_delete #define list_free mysql_list_free #include #undef list_length #undef list_delete #undef list_free #include "foreign/foreign.h" #include "lib/stringinfo.h" #include "nodes/relation.h" #include "utils/rel.h" #define MYSQL_PREFETCH_ROWS 100 #define MYSQL_BLKSIZ (1024 * 4) #define MYSQL_PORT 3306 #define MAXDATALEN 1024 * 64 #define WAIT_TIMEOUT 0 #define INTERACTIVE_TIMEOUT 0 #define CR_NO_ERROR 0 /* * 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 */ } mysql_opt; typedef struct mysql_column { Datum value; unsigned long length; bool is_null; bool error; MYSQL_BIND *_mysql_bind; } mysql_column; typedef struct mysql_table { MYSQL_RES *_mysql_res; MYSQL_FIELD *_mysql_fields; mysql_column *column; MYSQL_BIND *_mysql_bind; } mysql_table; /* * 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 */ 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 */ /* working memory context */ 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 */ } MySQLColumn; extern bool 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)); 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)); 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)); /* 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); 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); /* 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); void mysql_cleanup_connection(void); void mysql_rel_connection(MYSQL *conn); #endif /* MYSQL_FDW_H */ mysql_fdw-REL-2_1_2/mysql_init.sh000077500000000000000000000010271265037052500170370ustar00rootroot00000000000000#!/bin/sh export MYSQL_PWD="bar" mysql -h 127.0.0.1 -u foo -D testdb -e "DROP DATABASE IF EXISTS testdb" mysql -h 127.0.0.1 -u foo -e "CREATE DATABASE testdb" 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_fdw-REL-2_1_2/mysql_query.c000066400000000000000000000260211265037052500170470ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_query.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "mysql_fdw.h" #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 "optimizer/var.h" #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/datetime.h" #include "mysql_fdw.h" #include "mysql_query.h" #define DATE_MYSQL_PG(x, y) \ do { \ x->year = y.tm_year; \ x->month = y.tm_mon; \ x->day= y.tm_mday; \ x->hour = y.tm_hour; \ x->minute = y.tm_min; \ x->second = y.tm_sec; \ } while(0); static int32 mysql_from_pgtyp(Oid type); static int dec_bin(int n); static int bin_dec(int n); /* * 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]; /* get the type's output function */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(pgtyp)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for type%u", pgtyp); typeinput = ((Form_pg_type)GETSTRUCT(tuple))->typinput; typemod = ((Form_pg_type)GETSTRUCT(tuple))->typtypmod; ReleaseSysCache(tuple); switch (pgtyp) { /* * MySQL gives BIT / BIT(n) data type as decimal value. The only way to * retrieve this value is to use BIN, OCT or HEX function in MySQL, otherwise * mysql client shows the actual decimal value, which could be a non - printable character. * For exmple in MySQL * * CREATE TABLE t (b BIT(8)); * INSERT INTO t SET b = b'1001'; * SELECT BIN(b) FROM t; * +--------+ * | BIN(b) | * +--------+ * | 1001 | * +--------+ * * PostgreSQL expacts all binary data to be composed of either '0' or '1'. MySQL gives * value 9 hence PostgreSQL reports error. The solution is to convert the decimal number * into equivalent binary string. */ case BYTEAOID: 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; default: valueDatum = CStringGetDatum((char*)column->value); } value_datum = OidFunctionCall3(typeinput, valueDatum, ObjectIdGetDatum(InvalidOid), Int32GetDatum(typemod)); return value_datum; } /* * mysql_from_pgtyp: Give MySQL data type for PG type */ static int32 mysql_from_pgtyp(Oid type) { switch(type) { case INT2OID: return MYSQL_TYPE_SHORT; case INT4OID: return MYSQL_TYPE_LONG; case INT8OID: return MYSQL_TYPE_LONGLONG; case FLOAT4OID: return MYSQL_TYPE_FLOAT; case FLOAT8OID: return MYSQL_TYPE_DOUBLE; case NUMERICOID: return MYSQL_TYPE_DOUBLE; case BOOLOID: return MYSQL_TYPE_LONG; case BPCHAROID: case VARCHAROID: case TEXTOID: case JSONOID: return MYSQL_TYPE_STRING; case NAMEOID: return MYSQL_TYPE_STRING; case DATEOID: return MYSQL_TYPE_DATE; case TIMEOID: case TIMESTAMPOID: case TIMESTAMPTZOID: return MYSQL_TYPE_TIMESTAMP; case BITOID: return MYSQL_TYPE_LONG; case BYTEAOID: return MYSQL_TYPE_BLOB; default: { ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_DATA_TYPE), errmsg("cannot convert constant value to MySQL value"), errhint("Constant value data type: %u", type))); break; } } } /* * bind_sql_var: * Bind the values provided as DatumBind the values and nulls to modify the target table (INSERT/UPDATE) */ void mysql_bind_sql_var(Oid type, int attnum, Datum value, MYSQL_BIND *binds, bool *isnull) { /* Clear the bind buffer and attributes */ memset(&binds[attnum], 0x0, sizeof(MYSQL_BIND)); binds[attnum].buffer_type = mysql_from_pgtyp(type); binds[attnum].is_null = isnull; /* Avoid to bind buffer in case value is NULL */ if (*isnull) return; switch(type) { case INT2OID: { int16 dat = DatumGetInt16(value); int16 *bufptr = palloc0(sizeof(int16)); memcpy(bufptr, (char*)&dat, sizeof(int16)); binds[attnum].buffer = bufptr; break; } case INT4OID: { int32 dat = DatumGetInt32(value); int32 *bufptr = palloc0(sizeof(int32)); memcpy(bufptr, (char*)&dat, sizeof(int32)); binds[attnum].buffer = bufptr; break; } case INT8OID: { int64 dat = DatumGetInt64(value); int64 *bufptr = palloc0(sizeof(int64)); 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)); binds[attnum].buffer = bufptr; break; } case FLOAT8OID: { float8 dat = DatumGetFloat8(value); float8 *bufptr = palloc0(sizeof(float8)); memcpy(bufptr, (char*)&dat, sizeof(float8)); binds[attnum].buffer = bufptr; break; } case NUMERICOID: { Datum valueDatum = DirectFunctionCall1(numeric_float8, value); float8 dat = DatumGetFloat8(valueDatum); float8 *bufptr = palloc0(sizeof(float8)); memcpy(bufptr, (char*)&dat, sizeof(float8)); binds[attnum].buffer = bufptr; break; } case BOOLOID: { int32 dat = DatumGetInt32(value); int32 *bufptr = palloc0(sizeof(int32)); memcpy(bufptr, (char*)&dat, sizeof(int32)); binds[attnum].buffer = bufptr; break; } 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)); timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, pg_tzset("UTC")); DATE_MYSQL_PG(ts, tt); binds[attnum].buffer = ts; binds[attnum].buffer_length=sizeof(MYSQL_TIME); break; } case TIMEOID: case TIMESTAMPOID: case TIMESTAMPTZOID: { Timestamp valueTimestamp = DatumGetTimestamp(value); MYSQL_TIME* ts = palloc0(sizeof(MYSQL_TIME)); int tz; struct pg_tm tt, *tm = &tt; fsec_t fsec; const char *tzn; timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, pg_tzset("UTC")); DATE_MYSQL_PG(ts, tt); binds[attnum].buffer = ts; binds[attnum].buffer_length = sizeof(MYSQL_TIME); break; } case BITOID: { int32 dat; int32 *bufptr = palloc0(sizeof(int32)); char *outputString = NULL; Oid outputFunctionId = InvalidOid; bool typeVarLength = false; getTypeOutputInfo(type, &outputFunctionId, &typeVarLength); outputString = OidOutputFunctionCall(outputFunctionId, value); dat = bin_dec(atoi(outputString)); memcpy(bufptr, (char*)&dat, sizeof(int32)); binds[attnum].buffer = bufptr; break; } case BYTEAOID: { int len; char *dat = NULL; char *bufptr; char *result = DatumGetPointer(value); if (VARATT_IS_1B(result)) { len = VARSIZE_1B(result) - VARHDRSZ_SHORT; dat = VARDATA_1B(result); } else { len = VARSIZE_4B(result) - VARHDRSZ; dat = VARDATA_4B(result); } bufptr = 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))); break; } } } /* * mysql_bind_result: Bind the value and null pointers to get * the data from remote mysql table (SELECT) */ void mysql_bind_result(Oid pgtyp, int pgtypmod, MYSQL_FIELD *field, mysql_column *column) { MYSQL_BIND *mbind = column->_mysql_bind; mbind->is_null = &column->is_null; mbind->length = &column->length; mbind->error = &column->error; switch (pgtyp) { case BYTEAOID: mbind->buffer_type = MYSQL_TYPE_BLOB; /* leave room at front for bytea buffer length prefix */ column->value = (Datum) palloc0(MAX_BLOB_WIDTH + VARHDRSZ); mbind->buffer = VARDATA(column->value); mbind->buffer_length = MAX_BLOB_WIDTH; break; default: mbind->buffer_type = MYSQL_TYPE_VAR_STRING; column->value = (Datum) palloc0(MAXDATALEN); mbind->buffer = (char *) column->value; mbind->buffer_length = MAXDATALEN; } } static int dec_bin(int n) { int rem, i = 1; int bin = 0; while (n != 0) { rem = n % 2; n /= 2; bin += rem * i; i *= 10; } return bin; } static int bin_dec(int n) { int dec = 0; int i = 0; int rem; while (n != 0) { rem = n % 10; n /= 10; dec += rem * pow(2 , i); ++i; } return dec; } mysql_fdw-REL-2_1_2/mysql_query.h000066400000000000000000000015261265037052500170570ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_query.h * *------------------------------------------------------------------------- */ #ifndef MYSQL_QUERY_H #define MYSQL_QUERY_H #include "foreign/foreign.h" #include "lib/stringinfo.h" #include "nodes/relation.h" #include "utils/rel.h" Datum mysql_convert_to_pg(Oid pgtyp, int pgtypmod, mysql_column *column); void mysql_bind_sql_var(Oid type, int attnum, Datum value, MYSQL_BIND *binds, bool *isnull); void mysql_bind_result(Oid pgtyp, int pgtypmod, MYSQL_FIELD *field, mysql_column *column); #endif /* MYSQL_QUERY_H */ mysql_fdw-REL-2_1_2/option.c000066400000000000000000000134761265037052500157770ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * options.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * * Portions Copyright (c) 2004-2014, EnterpriseDB Corporation. * * IDENTIFICATION * options.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 "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 */ }; /* * 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 }, /* Sentinel */ { NULL, InvalidOid } }; extern Datum mysql_fdw_validator(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(mysql_fdw_validator); /* * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, * USER MAPPING or FOREIGN TABLE that uses file_fdw. * * Raise an ERROR if the option or its value is considered invalid. */ Datum mysql_fdw_validator(PG_FUNCTION_ARGS) { List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; /* * Check that only options supported by mysql_fdw, * and allowed for the current object type, are given. */ foreach(cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); if (!mysql_is_valid_option(def->defname, catalog)) { struct MySQLFdwOption *opt; StringInfoData buf; /* * Unknown option specified, complain about it. Provide a hint * with list of valid options for the object. */ initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (catalog == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : "") )); } } PG_RETURN_VOID(); } /* * Check if the provided option is one of the valid options. * context is the Oid of the catalog holding the object the option is for. */ bool mysql_is_valid_option(const char *option, Oid context) { struct MySQLFdwOption *opt; for (opt = valid_options; opt->optname; opt++) { if (context == opt->optcontext && strcmp(opt->optname, option) == 0) return true; } return false; } /* * Fetch the options for a mysql_fdw foreign table. */ mysql_opt* mysql_get_options(Oid foreignoid) { ForeignTable *f_table = NULL; ForeignServer *f_server = NULL; UserMapping *f_mapping; List *options; ListCell *lc; mysql_opt *opt; opt = (mysql_opt*) palloc(sizeof(mysql_opt)); memset(opt, 0, sizeof(mysql_opt)); /* * Extract options from FDW objects. */ PG_TRY(); { f_table = GetForeignTable(foreignoid); f_server = GetForeignServer(f_table->serverid); } PG_CATCH(); { f_table = NULL; f_server = GetForeignServer(foreignoid); } PG_END_TRY(); f_mapping = GetUserMapping(GetUserId(), f_server->serverid); 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); /* Default secure authentication is true */ opt->svr_sa = true; opt->use_remote_estimate = false; /* Loop through the options, and get the server/port */ foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "host") == 0) opt->svr_address = defGetString(def); if (strcmp(def->defname, "port") == 0) opt->svr_port = atoi(defGetString(def)); if (strcmp(def->defname, "username") == 0) opt->svr_username = defGetString(def); if (strcmp(def->defname, "password") == 0) opt->svr_password = defGetString(def); if (strcmp(def->defname, "dbname") == 0) opt->svr_database = defGetString(def); if (strcmp(def->defname, "table_name") == 0) opt->svr_table = defGetString(def); if (strcmp(def->defname, "secure_auth") == 0) opt->svr_sa = defGetBoolean(def); if (strcmp(def->defname, "init_command") == 0) opt->svr_init_command = defGetString(def); if (strcmp(def->defname, "max_blob_size") == 0) opt->max_blob_size = strtoul(defGetString(def), NULL, 0); if (strcmp(def->defname, "use_remote_estimate") == 0) opt->use_remote_estimate = defGetBoolean(def); } /* Default values, if required */ if (!opt->svr_address) opt->svr_address = "127.0.0.1"; if (!opt->svr_port) opt->svr_port = MYSQL_PORT; if (!opt->svr_table && f_table) opt->svr_table = get_rel_name(foreignoid); return opt; } mysql_fdw-REL-2_1_2/sql/000077500000000000000000000000001265037052500151075ustar00rootroot00000000000000mysql_fdw-REL-2_1_2/sql/mysql_fdw.sql000066400000000000000000000052751265037052500176460ustar00rootroot00000000000000\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'); 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')); 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; DELETE FROM employee; DELETE FROM department; DELETE FROM empdata; 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;