Use oracle_fdw
- Overview
- Installation
- Change wrapper options
- Create a foreign server
- Create user mapping
- Create a foreign table
- Data type conversion
- WHERE and ORDER BY clauses
- Joins between foreign tables
- Modify foreign data
- Collect statistics on foreign tables
- Support for IMPORT FOREIGN SCHEMA
- Functions created by the extension
Overview
The oracle_fdw extension is a foreign data wrapper to access Oracle databases. It allows you to query Oracle tables and views directly from the PostgreSQL interface using standard SQL statements. The main capabilities of the extension include the following:
-
Read and write operations — you can read data (
SELECTcommand) and modify it usingINSERT,UPDATE, andDELETEcommands on the Oracle side. -
Connection caching — the extension keeps Oracle sessions open during long-running transactions to avoid repeated authentication overhead. All connections are closed when the ADP/PostgreSQL session ends.
-
Type conversion — the extension automatically maps Oracle data types to the corresponding PostgreSQL data types.
The most common use cases are:
-
Migration — when moving IT systems from Oracle to ADP/PostgreSQL, the extension allows you to set up two-way data exchange so that applications can work simultaneously with both databases during the transition period.
-
System integration — if a company stores some of its data in Oracle, and new services are developed for ADP/PostgreSQL,
oracle_fdwmakes it possible to build reports and combine tables from two DBMSs.
Installation
The package required for the installation of the oracle_fdw extension is shipped with ADP. To use oracle_fdw, execute the CREATE EXTENSION command:
CREATE EXTENSION oracle_fdw;
|
NOTE
If the oracle_fdw extension is created in the template1 database used as the default template, all subsequently created databases will have this extension installed.
|
ADP uses the oracle_fdw version
2.8.0
. To check it, call the oracle_diag function:
SELECT oracle_diag();
oracle_diag --------------------------------------------------------------- oracle_fdw 2.8.0, PostgreSQL 16.13, Oracle client 23.26.2.0.0
The extension automatically creates a foreign data wrapper named oracle_fdw. Typically, you only need to create a foreign server to connect to Oracle.
Change wrapper options
The oracle_fdw data wrapper has an optional nls_lang parameter that allows you to set the NLS_LANG environment variable for Oracle. If you need this functionality, you can execute the ALTER FOREIGN DATA WRAPPER command to set nls_lang:
ALTER FOREIGN DATA WRAPPER oracle_fdw
OPTIONS (ADD nls_lang 'AMERICAN_AMERICA.AL32UTF8');
Note that with this approach, all changes will be lost when creating a dump and restoring it. To avoid this, you can create a new foreign data wrapper. The oracle_fdw extension includes the handler and validator functions that are necessary to create a foreign data wrapper. You can define a new foreign data wrapper as follows:
CREATE FOREIGN DATA WRAPPER custom_oracle_fdw
HANDLER oracle_fdw_handler
VALIDATOR oracle_fdw_validator
OPTIONS (nls_lang 'AMERICAN_AMERICA.AL32UTF8');
Create a foreign server
Using oracle_fdw involves operations on foreign tables. Call the CREATE SERVER command to create a foreign server. Replace <dbserver.address.com> with the Oracle server address and specify Oracle System Identifier instead of <SID>:
CREATE SERVER ora_server FOREIGN DATA WRAPPER oracle_fdw
OPTIONS (dbserver '//<dbserver.address.com>:1521/<SID>');
In the OPTIONS field, the CREATE SERVER command also accepts additional parameters described below.
| Name | Description | Default value |
|---|---|---|
dbserver |
The Oracle database connection string for the remote database. This is a required option |
— |
isolation_level |
The transaction isolation level to use at the Oracle database. The value can be Note that the Oracle table can be queried more than once during a single PostgreSQL statement, for example, during a nested loop join. To make sure that no inconsistencies caused by concurrent transactions occur, the transaction isolation level must guarantee read stability. This is only guaranteed with the Oracle The Oracle implementation of |
serializable |
nchar |
If |
off |
set_timezone |
If If you change a time zone after the Oracle connection has been established, If Oracle does not recognize the time zone, connections will fail with the error: |
off |
Create user mapping
It is a good practice to use superuser privileges when absolutely necessary, so it is recommended to grant a standard user (pguser in the example below) permissions to use the foreign server:
CREATE USER pguser;
GRANT USAGE ON FOREIGN SERVER ora_server TO pguser;
Access to remote data requires authentication on the Oracle side. For example, you can create an Oracle user as follows:
CREATE USER <ora_user> IDENTIFIED BY <ora_password>;
GRANT CREATE SESSION TO <ora_user>;
where:
-
<ora_user>— user name; -
<ora_password>— user password.
Connect to ADP as pguser and create a user mapping object to specify a user name and password that can be used for the Oracle authentication of a current ADP/PostgreSQL role. To do this, execute the CREATE USER MAPPING command:
CREATE USER MAPPING FOR pguser SERVER ora_server
OPTIONS (user '<ora_user>', password '<ora_password>');
To use external authentication, pass an empty string as the user value (<ora_user>). In this case, the operating system user postgres should have access to the Oracle server.
Create a foreign table
For example, there is the following table on the Oracle server:
CREATE TABLE ORA_USER.ORA_BOOKS (
id NUMBER PRIMARY KEY,
author_id NUMBER,
title VARCHAR2(255 char),
genre VARCHAR2(50 char),
price NUMBER);
Execute the CREATE FOREIGN TABLE command to define a foreign table. When you define the field types of the foreign table, consider the possibility of type conversion:
CREATE FOREIGN TABLE books_oracle (
id integer OPTIONS (key 'true') NOT NULL,
author_id integer,
title VARCHAR(255) NOT NULL,
genre VARCHAR(50),
price NUMERIC(10, 2)
) SERVER ora_server OPTIONS (schema 'ORA_USER', table 'ORA_BOOKS');
where:
-
ORA_USER— schema name (usually the same as the Oracle username); -
ORA_BOOKS— Oracle table name.
Oracle table and schema names are typically written in uppercase.
Now you can use the books_oracle table as a regular ADP/PostgreSQL table.
| Name | Description | Default value |
|---|---|---|
table |
Oracle table name. This name should be written as it appears in the Oracle system catalog, usually in uppercase letters only. To define a foreign table based on an Oracle query, set this option to the query enclosed in parentheses, for example:
Do not set the The |
required |
dblink |
Oracle database link through which the table is accessed. This name should be written as it appears in the Oracle system catalog, usually in uppercase letters only |
optional |
schema |
Table schema (or owner) used to access tables that do not belong to the connecting Oracle user. This name should be written as it appears in the Oracle system catalog, usually in uppercase letters only |
optional |
max_long |
The maximal length of the If |
32767 |
readonly |
The |
false |
sample_percent |
Defines the percentage of Oracle table blocks that will be randomly selected to calculate PostgreSQL table statistics. The value should be between |
100 |
prefetch |
Sets the number of rows that will be fetched with a single round-trip between ADP and Oracle during a foreign table scan. The value should be between Higher values can speed up performance but will use more memory on the ADP server and can lead to out-of-memory errors. Note that there is no prefetching if the Oracle table contains columns of the type |
50 |
lob_prefetch |
Sets the number of bytes that are prefetched for the |
1048576 |
When you create a foreign table, the Oracle table columns are mapped to the ADP/PostgreSQL table columns in the order they are specified in the FOREIGN TABLE command. The ADP/PostgreSQL table can include more or fewer columns than the Oracle table. If it has more columns, and these columns are used, you will receive a warning, and oracle_fdw will return NULL values for the missing columns.
oracle_fdw includes only those columns in the Oracle query that are required by the PostgreSQL query.
Foreign table columns can have the following optional settings:
-
key— if set toyes/on/true, the corresponding column on the foreign Oracle table is considered a primary key column. The default value isfalse. -
strip_zeros— if set toyes/on/true, ASCII 0 characters will be removed from the string during transfer. Such characters are valid in Oracle but not in ADP/PostgreSQL. This option only makes sense forcharacter,character varyingandtextcolumns. The default value isfalse.
If you want to execute the UPDATE or DELETE commands, ensure that the key option is set on all columns that belong to the table primary key.
Populate the foreign table with data. To successfully insert rows into an Oracle table, you may need to change the isolation_level foreign server’s option (see Modify foreign data):
ALTER SERVER ora_server OPTIONS (ADD isolation_level 'read_committed');
Add rows to the books_oracle foreign table:
INSERT INTO books_oracle (id,author_id, title, genre, price) VALUES
(1, 1, 'Mrs. Dalloway', 'novel', 360),
(2, 1, 'To the Lighthouse', 'novel', 440),
(3, 2, 'To Kill a Mockingbird', 'novel', 750),
(4, 3, 'The Great Gatsby', 'novel', 900),
(5, 4, 'The Lord of the Rings', 'fantasy', 1200);
Read rows from the table:
SELECT * FROM books_oracle WHERE genre = 'fantasy';
id | author_id | title | genre | price ----+-----------+-----------------------+---------+--------- 5 | 4 | The Lord of the Rings | fantasy | 1200.00
Data type conversion
It is necessary to define the ADP/PostgreSQL columns with data types that oracle_fdw can convert. The oracle_fdw extension automatically handles the following conversions:
| Oracle type | ADP/PostgreSQL type |
|---|---|
CHAR |
char, varchar, text |
NCHAR |
char, varchar, text |
VARCHAR |
char, varchar, text |
VARCHAR2 |
char, varchar, text, json |
NVARCHAR2 |
char, varchar, text |
CLOB |
char, varchar, text, json |
NCLOB |
char, varchar, text, json |
LONG |
char, varchar, text |
RAW |
uuid, bytea |
BLOB |
bytea |
BFILE |
bytea (read-only) |
LONG RAW |
bytea |
NUMBER |
numeric, float4, float8, char, varchar, text |
NUMBER(n,m) with m <= 0 |
numeric, float4, float8, int2, int4, int8, boolean, char, varchar, text |
FLOAT |
numeric, float4, float8, char, varchar, text |
BINARY_FLOAT |
numeric, float4, float8, char, varchar, text |
BINARY_DOUBLE |
numeric, float4, float8, char, varchar, text |
DATE |
date, timestamp, timestamptz, char, varchar, text |
TIMESTAMP |
date, timestamp, timestamptz, char, varchar, text |
TIMESTAMP WITH TIME ZONE |
date, timestamp, timestamptz, char, varchar, text |
TIMESTAMP WITH LOCAL TIME ZONE |
date, timestamp, timestamptz, char, varchar, text |
INTERVAL YEAR TO MONTH |
interval, char, varchar, text |
INTERVAL DAY TO SECOND |
interval, char, varchar, text |
XMLTYPE |
xml, char, varchar, text |
MDSYS.SDO_GEOMETRY |
geometry |
If an Oracle value exceeds the size of the ADP/PostgreSQL column, you will receive a runtime error.
If NUMBER is converted to a boolean, 0 is interpreted as false, any other values — as true.
You can insert or update XMLTYPE values only if they do not exceed the maximum length of the VARCHAR2 data type (4000 bytes or 32767 bytes, depending on the Oracle MAX_STRING_SIZE parameter).
If you want to convert TIMESTAMP WITH LOCAL TIME ZONE to timestamp, consider setting the set_timezone option of the foreign server.
The data type geometry is only available when PostGIS is installed.
The oracle_fdw extension only supports the following geometry types: POINT, LINE, POLYGON, MULTIPOINT, MULTILINE, and MULTIPOLYGON in two and three dimensions. Empty PostGIS geometries are not supported because they have no equivalent in Oracle Spatial.
WHERE and ORDER BY clauses
ADP/PostgreSQL uses all applicable parts of the WHERE clause as a filter for the scan.
oracle_fdw constructs an Oracle query containing a WHERE clause corresponding to these filter criteria. Since the WHERE clause is applied on the Oracle side, it can greatly reduce the number of rows retrieved from Oracle. This feature is also known as push-down of WHERE clauses.
ORDER BY is also executed on the Oracle side whenever possible. Note that an ORDER BY condition that sorts by a character string is not pushed down, as it is impossible to guarantee that the sort order in ADP/PostgreSQL matches the sort order in Oracle.
To successfully push down ORDER BY clauses, use simple conditions on the foreign table. Choose PostgreSQL column data types that correspond to the Oracle types; otherwise the conditions cannot be converted.
The expressions now(), transaction_timestamp(), current_timestamp, current_date, and localtimestamp are translated correctly.
The output of the EXPLAIN command shows the Oracle query that is used, so you can see which conditions were translated to Oracle and how. For example:
EXPLAIN SELECT * FROM books_oracle WHERE genre = 'fantasy';
QUERY PLAN
-------------------------------------------------------------------------------------------------------
Foreign Scan on books_oracle (cost=10000.00..20000.00 rows=1000 width=658)
Oracle query: SELECT /*7c63955190fa1d22*/ r1."ID", r1."AUTHOR_ID", r1."TITLE", r1."GENRE", r1."PRICE"
FROM "ORA_USER"."ORA_BOOKS" r1 WHERE (r1."GENRE" = 'fantasy')
Joins between foreign tables
oracle_fdw can push down JOINs to the Oracle server. So, JOIN between two foreign tables leads to a single Oracle query that performs JOIN on the Oracle side. Such a JOIN operation has the following limitations:
-
Both tables must be defined on the same foreign server.
-
The
JOINoperation between three or more tables is not pushed down. -
JOINshould be in aSELECTstatement. -
oracle_fdwmust be able to push down allJOINconditions andWHEREclauses. -
CROSS JOINwithoutJOINconditions is not pushed down. -
If
JOINis pushed down,ORDER BYclauses are not pushed down.
It is recommended to use ANALYZE to collect statistics on both foreign tables to determine the optimal strategy.
Example
Create another table on the Oracle server:
CREATE TABLE ORA_USER.ORA_AUTHORS (
id NUMBER PRIMARY KEY,
author_name VARCHAR2(100 char)
);
INSERT INTO ORA_USER.ORA_AUTHORS (id, author_name) VALUES
(1, 'Virginia Woolf'),
(2, 'Harper Lee'),
(3, 'F. Scott Fitzgerald'),
(4, 'J.R.R. Tolkien');
Create the corresponding foreign table in ADP:
CREATE FOREIGN TABLE authors_oracle (
id integer OPTIONS (key 'true') NOT NULL,
name VARCHAR(100)
) SERVER ora_server OPTIONS (schema 'ORA_USER', table 'ORA_AUTHORS');
Run the ANALYZE command:
ANALYZE authors_oracle;
ANALYZE books_oracle;
Execute the JOIN query:
SELECT authors_oracle.name, books_oracle.title
FROM books_oracle INNER JOIN authors_oracle ON authors_oracle.id = books_oracle.author_id
ORDER BY name;
name | title ---------------------+----------------------- F. Scott Fitzgerald | The Great Gatsby Harper Lee | To Kill a Mockingbird J.R.R. Tolkien | The Lord of the Rings Virginia Woolf | Mrs. Dalloway Virginia Woolf | To the Lighthouse
Display the query plan:
EXPLAIN SELECT authors_oracle.name, books_oracle.title
FROM books_oracle INNER JOIN authors_oracle ON authors_oracle.id = books_oracle.author_id
ORDER BY name;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------
Sort (cost=10200.43..10200.48 rows=20 width=531)
Sort Key: authors_oracle.name
-> Foreign Scan (cost=10000.00..10200.00 rows=20 width=531)
Oracle query: SELECT /*39731fa1cb7295c3*/ r2."AUTHOR_NAME", r1."TITLE" FROM ("ORA_USER"."ORA_BOOKS" r1
INNER JOIN "ORA_USER"."ORA_AUTHORS" r2 ON (r1."AUTHOR_ID" = r2."ID"))
In the output, you can see that JOIN is performed on the Oracle side.
Modify foreign data
oracle_fdw supports the INSERT, UPDATE, and DELETE commands on foreign tables. These operations are allowed by default and can be disabled by setting the readonly table option.
If you omit a foreign table column during INSERT, that column is set to the value defined in the DEFAULT clause of the ADP/PostgreSQL foreign table (or NULL if there is no DEFAULT clause). DEFAULT clauses on the corresponding Oracle columns are not used. If the ADP/PostgreSQL foreign table does not include all columns of the Oracle table, the Oracle DEFAULT clauses will be used for the columns not included in the foreign table definition.
The RETURNING clause of the INSERT, UPDATE, and DELETE commands is supported except for columns of the LONG and LONG RAW Oracle data types.
Triggers on foreign tables are supported, but triggers defined with AFTER and FOR EACH ROW require that the foreign table has no columns of the LONG or LONG RAW Oracle data types, since these triggers use the RETURNING clause mentioned above.
Although modifying foreign data is supported, performance may be poor, especially when processing large numbers of rows. This is due to the specifics of oracle_fdw, which requires processing each row individually.
Transactions are forwarded to Oracle, so the BEGIN, COMMIT, ROLLBACK, and SAVEPOINT commands work as expected. Prepared statements involving Oracle are not supported. See Internals.
Since oracle_fdw uses serialized transactions by default, it is possible that data-modifying statements lead to a serialization failure: ORA-08177: can’t serialize access for this transaction. This can happen if concurrent transactions modify the same table, and is more likely in case of long-running transactions. Such errors can be identified by the SQLSTATE (40001) code. An application using oracle_fdw should retry transactions that fail with this error.
It is also possible to use a different transaction isolation level, see Create a foreign server.
Collect statistics on foreign tables
You can use the ANALYZE command to gather statistics on a foreign table. Without statistics, ADP cannot estimate the row count for queries on a foreign table, which can cause inappropriate execution plans to be chosen.
ADP does not automatically collect statistics for foreign tables using the autovacuum daemon as it does for regular tables, so you need to run ANALYZE on foreign tables after they are created and whenever a remote table changes significantly.
Note that analyzing an Oracle foreign table will result in a full sequential table scan. You can set the sample_percent table option to speed this up by using only randomly selected blocks of the Oracle table.
The ADP/PostgreSQL EXPLAIN command shows the query that is issued to Oracle. EXPLAIN VERBOSE displays the Oracle execution plan. Execute the EXPLAIN VERBOSE command with the JOIN query from the Joins between foreign tables section:
EXPLAIN VERBOSE SELECT authors_oracle.name, books_oracle.title
FROM books_oracle INNER JOIN authors_oracle ON authors_oracle.id = books_oracle.author_id
ORDER BY name;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Sort (cost=10200.43..10200.48 rows=20 width=531)
Output: authors_oracle.name, books_oracle.title
Sort Key: authors_oracle.name
-> Foreign Scan (cost=10000.00..10200.00 rows=20 width=531)
Output: authors_oracle.name, books_oracle.title
Oracle query: SELECT /*4bab0a18b3951d4f*/ r2."AUTHOR_NAME", r1."GENRE"
FROM ("ORA_USER"."ORA_BOOKS" r1 INNER JOIN "ORA_USER"."ORA_AUTHORS" r2 ON (r1."TITLE" = r2."ID"))
Oracle plan: SELECT STATEMENT
Oracle plan: HASH JOIN (condition "R2"."ID"=TO_NUMBER("R1"."TITLE"))
Oracle plan: NESTED LOOPS
Oracle plan: NESTED LOOPS
Oracle plan: STATISTICS COLLECTOR
Oracle plan: TABLE ACCESS FULL ORA_BOOKS
Oracle plan: INDEX UNIQUE SCAN SYS_C008644 (condition "R2"."ID"=TO_NUMBER("R1"."TITLE"))
Oracle plan: TABLE ACCESS BY INDEX ROWID ORA_AUTHORS
Oracle plan: TABLE ACCESS FULL ORA_AUTHORS
Query Identifier: -5125250772757756403
Support for IMPORT FOREIGN SCHEMA
The IMPORT FOREIGN SCHEMA command supports bulk import of all table definitions from an Oracle schema. In addition to the IMPORT FOREIGN SCHEMA command documentation, consider the following:
-
IMPORT FOREIGN SCHEMAcreates foreign tables for all objects found in theALL_TAB_COLUMNSdata dictionary view. That includes tables, views, and materialized views, but not synonyms. -
The Oracle schema name should be written exactly as it is in Oracle, usually in uppercase. Since ADP/PostgreSQL translates names to lower case before processing, you should protect the schema name with double quotes (for example
"SCHEMA1"). -
Table names in the
LIMIT TOorEXCEPTclause should be written as they will appear in ADP/PostgreSQL after applying the case-change rules defined by theIMPORT FOREIGN SCHEMAoptions.
The following options can be specified in the OPTIONS field of the IMPORT FOREIGN SCHEMA command.
| Name | Description |
|---|---|
case |
Controls case folding for table and column names during import. The possible values are:
|
collation |
A collation used for case folding for the The default value is |
dblink |
Oracle database link through which the schema is accessed. This name should be written as it appears in the Oracle system catalog, usually in uppercase letters only |
readonly |
Sets the |
skip_tables |
Specifies whether to skip tables during the import. The default value is |
skip_views |
Specifies whether to skip views during the import. The default value is |
skip_matviews |
Specifies whether to skip materialized views during the import. The default value is |
max_long |
Sets the |
sample_percent |
Sets the |
prefetch |
Sets the |
lob_prefetch |
Sets the |
nchar |
Sets the |
set_timezone |
Sets the |
The following code creates the schema_oracle schema and imports the ORA_USER schema from the Oracle server into it:
CREATE SCHEMA schema_oracle;
IMPORT FOREIGN SCHEMA "ORA_USER" from SERVER ora_server into schema_oracle;
Display the existing foreign tables to check the result:
SELECT foreign_table_schema AS schema_name,
foreign_table_name AS table_name,
foreign_server_name AS server_name
FROM information_schema.foreign_tables
WHERE foreign_table_schema = 'schema_oracle';
schema_name | table_name | server_name ---------------+-------------+------------- schema_oracle | ora_authors | ora_server schema_oracle | ora_books | ora_server
Functions created by the extension
oracle_fdw_handler and oracle_fdw_validator
The oracle_fdw_handler and oracle_fdw_validator functions are necessary to create a foreign data wrapper:
FUNCTION oracle_fdw_handler() RETURNS fdw_handler
FUNCTION oracle_fdw_validator(text[], oid) RETURNS void
You can find an example above.
oracle_close_connections
The function can be used to close all open Oracle connections in this session.
FUNCTION oracle_close_connections() RETURNS void
oracle_fdw caches Oracle connections because it is expensive to create an Oracle session for each query. All connections are closed when the ADP/PostgreSQL session ends.
The oracle_close_connections() function can be useful for long-running sessions that do not access foreign tables all the time, and you want to avoid blocking the resources required to keep an open connection to Oracle.
Example:
SELECT oracle_close_connections();
You cannot call this function inside a transaction that modifies Oracle data.
oracle_diag
This function is useful for diagnostic purposes only.
FUNCTION oracle_diag(<server_name> DEFAULT NULL) RETURNS text
where <server_name> is the name of a foreign server.
The function returns the versions of oracle_fdw, PostgreSQL server, and Oracle client. If called with no argument or NULL, it additionally returns the values of environment variables used for establishing Oracle connections:
SELECT oracle_diag(NULL);
oracle_diag -------------------------------------------------------------------------------------------- oracle_fdw 2.8.0, PostgreSQL 16.13, Oracle client 23.26.2.0.0, ORACLE_HOME=/usr/lib/oracle
If called with the name of a foreign server, it also returns the Oracle server version:
SELECT oracle_diag('ora_server');
oracle_diag ----------------------------------------------------------------------------------------- oracle_fdw 2.8.0, PostgreSQL 16.13, Oracle client 23.26.2.0.0, Oracle server 23.0.0.0.0
oracle_execute
The function allows you to execute SQL statements that do not return results (typically DDL statements) on the remote Oracle server.
FUNCTION oracle_execute(<server_name>, <query_text>) RETURNS void
where:
-
<server_name>— the name of a foreign server; -
<query_text>— query text.
For example, you can add the in_stock column to the ORA_BOOKS table on the Oracle server:
SELECT oracle_execute('ora_server', 'ALTER TABLE ORA_USER.ORA_BOOKS ADD in_stock NUMBER');
Be careful when using this function, since it may impact the oracle_fdw transaction management. Executing DDL statements in Oracle is followed by an implicit COMMIT command. It is not recommended to use this function in transactions with multiple statements.