How to Query a Remote Database with REST-Enabled SQL in Oracle APEX

Set up ORDS, an OAuth client, a web credential, and a REST Enabled SQL Service, then build an interactive report on remote tables.

An Oracle APEX report does not have to read a table in its own schema. A REST Enabled SQL Service sends a SELECT statement to another Oracle database through Oracle REST Data Services (ORDS) and shows the rows in a normal report. The remote tables need no copy, database link, or REST module.

APEX stores the endpoint and the login once, in a service and a web credential, and every report that uses the service reuses them. This guide builds the whole chain: the remote tables, the ORDS endpoint and OAuth client, the trust setup in the APEX database, the service, and an interactive report.

It differs from How to Run SQL over HTTP with REST-Enabled SQL, which calls the endpoint with curl and has no APEX page. It also differs from How to Show Data from a REST API in a Report in Oracle APEX, which reads a REST module that returns fixed JSON.

Here APEX sends the SQL itself. This guide uses stock levels in warehouses as the example, and the steps work for any remote tables.

Step 1: Create the Tables in the Remote Schema

The remote database is the one that owns the data. In this example it is a schema named WHSE with two tables, warehouses and stock_levels. Here are the tables with their keys, constraints, and sample rows:

Example:

create table warehouses (
    warehouse_id   number generated by default as identity
                   constraint warehouses_pk primary key,
    warehouse_name varchar2(60) not null
                   constraint warehouses_name_uk unique,
    city           varchar2(40) not null
);

create table stock_levels (
    stock_id      number generated by default as identity
                  constraint stock_levels_pk primary key,
    warehouse_id  number not null
                  constraint stock_levels_wh_fk references warehouses (warehouse_id),
    sku           varchar2(20) not null,
    item_name     varchar2(100) not null,
    quantity      number(8) default on null 0 not null
                  constraint stock_levels_qty_ck check (quantity >= 0),
    reorder_level number(8) default on null 10 not null
                  constraint stock_levels_reorder_ck check (reorder_level >= 0),
    counted_on    date default on null sysdate,
    constraint stock_levels_wh_sku_uk unique (warehouse_id, sku)
);

create index stock_levels_sku_ix on stock_levels (sku);

insert into warehouses (warehouse_name, city) values ('North Hub', 'Delhi');
insert into warehouses (warehouse_name, city) values ('West Hub', 'Mumbai');
insert into warehouses (warehouse_name, city) values ('South Hub', 'Chennai');

insert into stock_levels (warehouse_id, sku, item_name, quantity, reorder_level)
select w.warehouse_id, s.sku, s.item_name, s.quantity, s.reorder_level
from   warehouses w
join  (select 'North Hub' wn, 'CMP-100' sku, 'Laptop 14 inch' item_name, 25 quantity, 10 reorder_level from dual union all
       select 'North Hub', 'ACC-100', 'Wireless Mouse', 150, 40 from dual union all
       select 'North Hub', 'FUR-100', 'Office Chair', 4, 8 from dual union all
       select 'West Hub',  'CMP-100', 'Laptop 14 inch', 6, 10 from dual union all
       select 'West Hub',  'ACC-110', 'Mechanical Keyboard', 60, 20 from dual union all
       select 'West Hub',  'FUR-110', 'Standing Desk', 3, 5 from dual union all
       select 'South Hub', 'CMP-300', 'Monitor 27 inch', 40, 15 from dual union all
       select 'South Hub', 'ACC-120', 'USB-C Dock', 35, 10 from dual union all
       select 'South Hub', 'FUR-120', 'Desk Lamp', 0, 12 from dual) s
       on s.wn = w.warehouse_name;

commit;

select w.warehouse_name, w.city, s.sku, s.item_name, s.quantity, s.reorder_level
from   stock_levels s
join   warehouses w on w.warehouse_id = s.warehouse_id
order  by w.warehouse_id, s.sku;

Output:

Table WAREHOUSES created.

Table STOCK_LEVELS created.

Index STOCK_LEVELS_SKU_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

9 rows inserted.

Commit complete.

WAREHOUSE_NAME    CITY       SKU        ITEM_NAME                 QUANTITY    REORDER_LEVEL
_________________ __________ __________ ______________________ ___________ ________________
North Hub         Delhi      ACC-100    Wireless Mouse                 150               40
North Hub         Delhi      CMP-100    Laptop 14 inch                  25               10
North Hub         Delhi      FUR-100    Office Chair                     4                8
West Hub          Mumbai     ACC-110    Mechanical Keyboard             60               20
West Hub          Mumbai     CMP-100    Laptop 14 inch                   6               10
West Hub          Mumbai     FUR-110    Standing Desk                    3                5
South Hub         Chennai    ACC-120    USB-C Dock                      35               10
South Hub         Chennai    CMP-300    Monitor 27 inch                 40               15
South Hub         Chennai    FUR-120    Desk Lamp                        0               12

9 rows selected.

The APEX schema has its own table named WAREHOUSES with four hubs, including East Hub. That is deliberate, to prove later that the report reads the remote table and not the local one:

Example:

select warehouse_name from warehouses order by 1;

Output:

WAREHOUSE_NAME
_________________
East Hub
North Hub
South Hub
West Hub

Step 2: Enable ORDS and Create an OAuth Client

REST Enabled SQL is a feature of ORDS, and it must be switched on in the ORDS configuration with restEnabledSql.active set to true. It was already on for this installation. The remote schema also has to be REST-enabled, which gives it a URL alias; here the alias is warehouse.

Instead of the schema password, APEX will log in with an OAuth client that holds the SQL Developer role, the role REST Enabled SQL accepts. Run this as the remote schema. ORDS shows the client secret only once, so copy it when you run the script yourself:

Example:

set serveroutput on

begin
    ords.enable_schema(
        p_enabled             => true,
        p_schema              => 'WHSE',
        p_url_mapping_type    => 'BASE_PATH',
        p_url_mapping_pattern => 'warehouse',
        p_auto_rest_auth      => true);
    commit;
end;
/

declare
    v_client ords_types.t_client_credentials;
begin
    v_client := ords_security.register_client(
                    p_name          => 'apex_sql_client',
                    p_grant_type    => 'client_credentials',
                    p_support_email => 'dba@warehouse.example',
                    p_client_secret => ords_types.oauth_client_secret());
    ords_security.grant_client_role(
        p_client_name => 'apex_sql_client',
        p_role_name   => 'SQL Developer');
    commit;
    dbms_output.put_line('client_id:     ' || v_client.client_key.client_id);
    dbms_output.put_line('client_secret: ' || '(shown once, copy it now)');
end;
/

Output:

PL/SQL procedure successfully completed.

client_id:     CdeIzWuWss1hechozICLzg..
client_secret: (shown once, copy it now)

PL/SQL procedure successfully completed.

The endpoint is now https://localhost:8443/ords/warehouse/_/sql. Replace the host and port with your ORDS address.

Step 3: Let the APEX Database Reach and Trust ORDS

The call to ORDS is made by the database that runs APEX, and it uses HTTPS. Two things are needed there. The APEX schema owner needs a network access rule for the ORDS host and port, and the database needs a wallet that trusts the ORDS certificate.

First create the wallet. The first command saves the ORDS certificate, and the other two put it in an auto-login wallet. Replace WALLET_PASSWORD with a password of your own:

Example:

echo | openssl s_client -connect localhost:8443 2>/dev/null | openssl x509 > /home/oracle/ords_cert.pem

orapki wallet create -wallet /home/oracle/ords_wallet -pwd WALLET_PASSWORD -auto_login

orapki wallet add -wallet /home/oracle/ords_wallet -trusted_cert -cert /home/oracle/ords_cert.pem -pwd WALLET_PASSWORD

Output:

Operation is successfully completed.

Then, connected as an administrator, allow APEX to call the host and tell the APEX instance where the wallet is:

Example:

begin
    dbms_network_acl_admin.append_host_ace(
        host       => 'localhost',
        lower_port => 8443,
        upper_port => 8443,
        ace        => xs$ace_type(
                          privilege_list => xs$name_list('http'),
                          principal_name => 'APEX_260100',
                          principal_type => xs_acl.ptype_db));
    commit;
end;
/

begin
    apex_instance_admin.set_parameter(
        p_parameter => 'WALLET_PATH',
        p_value     => 'file:/home/oracle/ords_wallet');
    commit;
end;
/

select host, lower_port, upper_port, principal, privilege
from   dba_host_aces
where  principal = 'APEX_260100'
and    host = 'localhost';

select apex_instance_admin.get_parameter('WALLET_PATH') as wallet_path
from   dual;

Output:

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

HOST	     LOWER_PORT UPPER_PORT PRINCIPAL	  PRIVILEGE
------------ ---------- ---------- -------------- ----------
localhost	   8443       8443 APEX_260100	  HTTP

1 row selected.

WALLET_PATH
----------------------------------------
file:/home/oracle/ords_wallet

1 row selected.

The host name in the endpoint must be the name in the certificate. The test certificate here is issued to localhost, so the database reaches ORDS as localhost.

Step 4: Create a Web Credential

In App Builder, open Shared Components and click Credentials (or open Workspace Utilities, Web Credentials). Click Create. Set Name to Warehouse ORDS Client and Static ID to warehouse_ords_client. Set Authentication Type to OAuth2 Client Credentials.

Create Web Credential page with Name Warehouse ORDS Client, Static ID warehouse_ords_client and Authentication Type OAuth2 Client Credentials marked with red boxes
Name, Static ID, and authentication type of the credential.

Enter the client ID and the client secret from Step 2, and repeat the secret in Verify Client Secret or Password. In Valid for URLs enter https://localhost:8443/ords/warehouse/, so that APEX sends the credential only to this address. Click Create.

Web Credential page with Client ID or Username, the masked Client Secret or Password, Verify Client Secret or Password and Valid for URLs marked with red boxes
The client ID, the masked secret, and the URLs the credential is valid for.

APEX stores the secret encrypted. It cannot be read back, so keep your own copy in a safe place.

Step 5: Create the REST Enabled SQL Service

Go to Shared Components and click REST Enabled SQL Databases (Workspace Utilities, REST Enabled SQL). Click Create. On the first screen, set Name to Warehouse Database, Static ID to warehouse_db, and Endpoint URL to the address from Step 2. Click Next.

Create REST Enabled SQL Service dialog with Name Warehouse Database, Static ID warehouse_db and Endpoint URL https://localhost:8443/ords/warehouse/_/sql marked with red boxes
The general screen of the service wizard.

On the Authentication screen, open the Credentials list and choose Warehouse ORDS Client, the web credential from Step 4. The screen also lets you type a user name and password for Basic Authentication instead; this guide uses the OAuth client.

Create REST Enabled SQL Service Authentication screen with the Credentials list set to Warehouse ORDS Client marked with a red box
Choosing the web credential.

Click Create. APEX tests the service right away by sending a small query. A success message shows that the remote database answered and that the session is connected as WHSE:

Test REST Enabled SQL Service dialog showing the success message and the line You are connected as WHSE
The connection test after creating the service.

Step 6: Create a Report on the Remote Database

Open the application in Page Designer, click Create, and choose Page. Under Component, choose Interactive Report. Click Next.

Create a Page dialog with the Interactive Report tile marked with a red box
Choosing Interactive Report.

Set Page Number to 1300 and Name to Warehouse Stock. In Data Source, set Data Source to REST Enabled SQL and REST Enabled SQL Service to Warehouse Database. Set Source Type to SQL Query.

Create Interactive Report dialog with Page Number 1300, Name Warehouse Stock, Data Source REST Enabled SQL, service Warehouse Database and Source Type SQL Query marked with red boxes
Page definition and data source.

Enter the query below. It joins both remote tables and adds a stock status column. Table works too: choose a table or view of the remote schema and APEX builds the query for you.

Example:

select w.warehouse_name, w.city, s.sku, s.item_name, s.quantity, s.reorder_level,
       case when s.quantity <= s.reorder_level then 'Reorder' else 'OK' end as stock_status
from   stock_levels s
join   warehouses w on w.warehouse_id = s.warehouse_id
order  by w.warehouse_name, s.sku

In the Navigation group, turn off Use Breadcrumb, keep Use Navigation on, and set Icon to fa-database. Click Create Page.

Create Interactive Report dialog with the SQL query and, in Navigation, Use Breadcrumb off and Icon fa-database marked with red boxes
The query and the navigation settings.

In Page Designer, select the Warehouse Stock region. Under Source, the Location is REST Enabled SQL, the Remote Server is Warehouse Database, and the Type is SQL Query. Change these properties here if the service or query changes later.

Page Designer with the Warehouse Stock region selected and Source Location REST Enabled SQL, Remote Server Warehouse Database and Type SQL Query marked with red boxes
The region source in Page Designer.

Step 7: Run the Report

Run the page. APEX sends the query to ORDS, the remote database runs it, and the rows come back as a report. It shows three hubs and nine stock rows.

East Hub from the local WAREHOUSES table is not there, so the data really comes from the remote schema. Office Chair at North Hub, Desk Lamp at South Hub, and the Laptop and Standing Desk at West Hub show Reorder.

Interactive report Warehouse Stock showing nine stock rows from North Hub, South Hub and West Hub with a Stock Status column
The report reads the remote tables.

Step 8: Test a Change in the Remote Data

Restock the Office Chair at North Hub from 4 to 20 in the remote schema, and reload the report:

Example:

update stock_levels
set    quantity = 20
where  sku = 'FUR-100'
and    warehouse_id = (select warehouse_id from warehouses where warehouse_name = 'North Hub');

commit;

select s.sku, s.item_name, s.quantity, s.reorder_level
from   stock_levels s
join   warehouses w on w.warehouse_id = s.warehouse_id
where  w.warehouse_name = 'North Hub'
order  by s.sku;

Output:

1 row updated.

Commit complete.

SKU        ITEM_NAME            QUANTITY    REORDER_LEVEL
__________ _________________ ___________ ________________
ACC-100    Wireless Mouse            150               40
CMP-100    Laptop 14 inch             25               10
FUR-100    Office Chair               20                8

The report shows the new quantity at once, and the status of the Office Chair changes from Reorder to OK. Nothing is copied or cached, because the SQL runs on the remote database on every refresh.

Interactive report Warehouse Stock where the Office Chair at North Hub now shows quantity 20 and status OK
The report after the remote update.

Step 9: Test a Wrong Credential

To see what users get when the login is wrong, save a wrong secret in the web credential. This test uses a wrong client secret; the correct one was restored after the test:

Example:

begin
    apex_util.set_workspace('SOLUTIONS');
    apex_credential.set_persistent_credentials(
        p_credential_static_id => 'warehouse_ords_client',
        p_client_id            => 'CdeIzWuWss1hechozICLzg..',
        p_client_secret        => 'wrong-secret');
    commit;
end;
/

Output:

PL/SQL procedure successfully completed.

Reload the report. It shows an authentication error instead of rows.

Interactive report region showing the error ORA-20001: Authentication failed
The report with a wrong client secret.

Things to Know

  • The SQL and PL/SQL of the report run as the remote schema, here WHSE, with its privileges. Give the remote schema only the tables APEX needs, and treat the OAuth client like a password.
  • The ORDS version on the remote side must support REST Enabled SQL and have restEnabledSql.active set to true.
  • The database that runs APEX must trust the ORDS certificate. This test used a self-signed certificate, so the wallet holds that certificate; with a certificate from a public authority the wallet needs the authority's root certificate instead.
  • The credential's Valid for URLs list limits where APEX may send the secret. Keep it as narrow as the endpoint.
  • The region has a Remote Cache group in Page Designer. Caching was Disabled in this test, which is why Step 8 showed the change immediately.
  • A credential saved from a script, as in Step 9, replaces the one in the workspace. Save the correct secret again afterwards, or the report keeps failing.

Related Guides

Conclusion

To query a remote database from Oracle APEX, REST-enable the remote schema in ORDS, create an OAuth client with the SQL Developer role, and make sure the APEX database trusts the ORDS certificate. Then create a web credential and a REST Enabled SQL Service.

Build reports on the service with the Create Page wizard or by setting the region Location to REST Enabled SQL. The remote database runs the SQL on every refresh, so the report always shows current data.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted