MySQL
MySQL is one of the world's most popular open-source relational database management systems.
The MySQL Wrapper allows you to read and write data from MySQL within your Postgres database.
Preparation
Before you can query MySQL, you need to enable the Wrappers extension and store your credentials in Postgres.
Enable Wrappers
Make sure the wrappers extension is installed on your database:
1 | |
Enable the MySQL Wrapper
Enable the mysql_wrapper FDW:
1 2 3 | |
Store your credentials (optional)
By default, Postgres stores FDW credentials inside pg_catalog.pg_foreign_server in plain text. Anyone with access to this table will be able to view these credentials. Wrappers is designed to work with Vault, which provides an additional level of security for storing credentials. We recommend using Vault to store your credentials.
1 2 3 4 5 6 | |
Connecting to MySQL
We need to provide Postgres with the credentials to connect to MySQL, and any additional options. We can do this using the create server command:
1 2 3 4 5 | |
1 2 3 4 5 | |
The connection string follows the standard MySQL URL format:
1 | |
Some connection string examples:
mysql://root:secret@localhost:3306/mydbmysql://app_user:password@db.example.com:3306/productionmysql://user:password@127.0.0.1:3306/testdb?ssl-mode=required
Create a schema
We recommend creating a schema to hold all the foreign tables:
1 | |
Options
The following options are available when creating MySQL foreign tables:
-
table- Source table name in MySQL, requiredThis can also be a subquery enclosed in parentheses, for example,
1table '(select id, name from my_table where active = 1)' -
rowid_column- Primary key column name, optional for data scan, required for data modify
Entities
Tables
The MySQL Wrapper supports data reads and writes from MySQL tables.
Operations
| Object | Select | Insert | Update | Delete | Truncate |
|---|---|---|---|---|---|
| Tables | ✅ | ✅ | ✅ | ✅ | ❌ |
Usage
1 2 3 4 5 6 7 8 | |
Notes
- Supports
where,order by,limitand aggregate clause pushdown - Data is streamed row-by-row from MySQL, making it suitable for large result sets
- When using
rowid_column, it must be specified for data modification operations
Query Pushdown Support
This FDW supports where, order by and limit clause pushdown.
Aggregate Pushdown
The FDW pushes common aggregate queries down to MySQL so the aggregation runs remotely and only the final result rows are transferred to Postgres. This is much faster than fetching every row and aggregating locally, especially over large tables.
Supported aggregates — count(*), count(col), count(distinct col),
sum(col), avg(col), min(col), max(col).
Supported shapes — scalar aggregates, group by over plain columns, with
or without a where clause. Pushdown also works when the foreign table
option is a sub-query.
1 2 3 4 | |
Cases that are not pushed down — the query still returns the correct result, but the aggregation happens in Postgres after fetching the rows:
- The query has a
havingclause - The aggregate has a
filter (where …)clause - A
distinctmodifier is used on anything other thancount - The aggregate's argument is not a plain column (for example
sum(a + 1)) - A
group byitem is not a plain column (for examplegroup by id + 1) - The aggregate function is not in the list above (for example
stddev,group_concat)
Import Foreign Schema
This FDW supports import foreign schema to automatically create foreign table definitions by reading the MySQL table structure from information_schema.
The remote_schema maps to a MySQL database name. For example, to import all tables from the MySQL database mydb:
1 2 3 | |
You can limit which tables are imported using limit to or except:
1 2 3 4 5 6 7 8 9 10 11 | |
An additional option is available for import foreign schema:
strict- If set totrue, the import will fail if any MySQL column type cannot be mapped to a Postgres type. Defaults tofalse(unsupported column types are silently skipped).
1 2 3 4 | |
Primary key columns are automatically detected and set as the rowid_column option on the generated foreign table, enabling INSERT, UPDATE, and DELETE operations without any manual configuration.
Supported Data Types
| Postgres Type | MySQL Type |
|---|---|
| boolean | boolean, bool, tinyint(1) |
| smallint | tinyint (non-boolean), smallint, year |
| integer | mediumint, int, integer |
| bigint | bigint |
| real | float |
| double precision | double, double precision |
| numeric | decimal, numeric |
| text | char, varchar, tinytext, text, mediumtext, longtext, enum, set |
| date | date |
| timestamp | datetime, timestamp |
| time | time |
| jsonb | json |
Note
tinyint(1) is mapped to boolean as it is the conventional MySQL representation for boolean values. All other tinyint variants are mapped to smallint.
decimal and numeric columns with explicit precision and scale (e.g. decimal(12,2)) are imported as numeric(p,s) in Postgres.
Limitations
This section describes important limitations and considerations when using this FDW:
- Only a subset of MySQL data types are supported; columns with unmapped types are skipped during
import foreign schemaunlessstrictmode is enabled timestampanddatetimevalues are treated as naive timestamps without timezone conversion- MySQL
jsoncolumns are mapped to Postgresjsonb; invalid JSON will cause an error during reads - Materialized views using foreign tables may fail during logical backups
Examples
Basic example
This example shows how to query a MySQL table from Postgres.
First, create the source table in MySQL:
1 2 3 4 5 6 7 8 9 10 11 | |
Then create the foreign table in Postgres and query it:
1 2 3 4 5 6 7 8 9 10 11 | |
Data modification example
This example demonstrates INSERT, UPDATE, and DELETE on a foreign table. The rowid_column option is required for data modification:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Aggregate Query Examples
These examples assume an orders table in MySQL and a matching foreign
table on Postgres:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 | |
Each query below runs a single aggregate query against MySQL and returns just the result rows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Import foreign schema example
This example imports all tables from a MySQL database automatically:
1 2 3 4 5 6 7 8 | |