Oracle Autonomous Database Dialect
Overview#
Oracle Autonomous Database
(Autonomous Transaction Processing, Autonomous Data Warehouse, Autonomous JSON
Database) is Oracle Database run by Oracle. The SQL, types and driver are
Oracle's, so OracleAutonomousDialect extends OracleDialect and inherits
all of it. It changes how you connect, refuses what the managed database
refuses, and adds DBMS_CLOUD builders for loading from object storage.| Property | Value |
|---|
| Dialect name | oracleautonomous |
| Extends | OracleDialect |
| Driver | oracledb (Thin mode by default) |
| Default port | 1522 (mTLS and TLS; TLS also on 1521) |
| Cloud SDK | none |
| Local runtime | none — hosted service only |
Quick start#
Install#
$ npm install ts-prorm-orm oracledb
$ pnpm add ts-prorm-orm oracledb
$ yarn add ts-prorm-orm oracledb
$ bun add ts-prorm-orm oracledb
oracledb is the same driver the oracle dialect uses.Connect#
import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'oracleautonomous',
username: 'ADMIN',
password: process.env.ADB_PASSWORD,
database: 'mydb', // with service, selects the tnsnames alias mydb_tp
dialectOptions: {
service: 'tp',
configDir: '/opt/wallet_mydb',
walletLocation: '/opt/wallet_mydb',
walletPassword: process.env.WALLET_PASSWORD,
},
});
Define a model#
import { DataTypes } from 'ts-prorm-orm';
const User = db.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
name: { type: DataTypes.STRING(100) },
});
await db.sync(); // CREATE TABLE — see Generated SQL below
Query#
import { Op } from 'ts-prorm-orm';
await User.create({ email: 'ada@example.com', name: 'Ada' });
const page = await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});
Generated SQL#
Each block is the statement the model call above it hands the dialect's query(), captured with the driver stubbed out. The model layer inlines the values.Insert, update and delete#
await User.create({ email: 'ada@example.com', name: 'Ada' });
await User.update({ name: 'Grace' }, { where: { email: 'ada@example.com' } });
await User.destroy({ where: { id: 1 } });
Upsert#
await User.upsert({ id: 1, email: 'ada@example.com', name: 'Ada Lovelace' });
MERGE statement
upsert() also reads the row by primary key before and after the write, to return the instance.await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});
Transaction with a savepoint#
await db.transaction(async (t) => {
await User.create({ email: 'grace@example.com', name: 'Grace' }, { transaction: t });
// A nested transaction is a SAVEPOINT; its failure undoes only its own work.
await db
.transaction(async (sp) => {
await User.destroy({ where: { id: 1 }, transaction: sp });
throw new Error('keep the row');
}, { transaction: t })
.catch(() => {});
});
Skip locked rows (SKIP LOCKED)#
A worker that claims rows from a queue table should pass over rows another transaction holds rather than wait for them. lock with skipLocked inside a transaction:await db.transaction(async (t) => {
await User.findAll({ where: { name: 'Ada' }, lock: { level: 'UPDATE', skipLocked: true }, transaction: t });
});
Type mapping for the portable descriptors: STRING → VARCHAR2(255), auto-increment INTEGER → NUMBER(10), BOOLEAN → NUMBER(1), DATE → DATE, UUID → RAW(16), JSON → CLOB. The type comparison lines the major engines up side by side.Connection#
Every connection uses TLS. node-oracledb runs in its pure-JavaScript Thin mode
by default, so no Instant Client is needed.Mutual TLS, with a wallet#
Download the wallet zip from the console and unzip it. configDir is the directory
holding tnsnames.ora. walletLocation is the one holding ewallet.pem, usually
the same directory. walletPassword is the password you chose when you downloaded
the wallet.const db = new Prorm({
dialect: 'oracleautonomous',
username: 'ADMIN',
password: process.env.ADB_PASSWORD,
database: 'mydb', // with `service`, selects the tnsnames alias mydb_tp
dialectOptions: {
service: 'tp',
configDir: '/opt/wallet_mydb',
walletLocation: '/opt/wallet_mydb',
walletPassword: process.env.WALLET_PASSWORD,
},
});
walletContent passes the PEM contents instead of a path. You can also pass
connectString with any alias from tnsnames.ora (mydb_high), a full connect
descriptor, or an Easy Connect string, and it is used as given. mTLS runs on port
1522 only.TLS without a wallet#
First add an access control list or a private endpoint, and turn off "mTLS
required" on the database. Then give the host and the full service name, and the
dialect builds a descriptor:const db = new Prorm({
dialect: 'oracleautonomous',
username: 'ADMIN',
password: process.env.ADB_PASSWORD,
dialectOptions: {
host: 'adb.us-ashburn-1.oraclecloud.com',
port: 1521, // or 1522
serviceName: 'abc123_mydb_low.adb.oraclecloud.com',
},
});
// (DESCRIPTION=(RETRY_COUNT=20)(RETRY_DELAY=3)(ADDRESS=(PROTOCOL=TCPS)(PORT=1521)
// (HOST=adb.us-ashburn-1.oraclecloud.com))(CONNECT_DATA=(SERVICE_NAME=abc123_mydb_low.adb.oraclecloud.com))
// (SECURITY=(SSL_SERVER_DN_MATCH=YES)))
buildAutonomousConnectDescriptor() is also exported. The dialect also passes
through sslServerDNMatch, sslServerCertDN, httpsProxy/httpsProxyPort,
retryCount and retryDelay.Thick mode#
With thinMode: false, configDir goes to initOracleClient(), and the wallet
(cwallet.sso, sqlnet.ora) is found there. walletPassword and walletContent
work in Thin mode only, so they are refused in Thick mode.Services#
| Service | Use |
|---|
tpurgent | Time-critical transactions, highest priority, manual parallelism |
tp | Typical transaction processing, no parallelism |
high | Reporting and batch, parallel, low concurrency |
medium | Reporting and batch, parallel, typical concurrency |
low | Lowest priority, no parallelism |
How connect() works#
OracleDialect.connect() builds host:port/database by default, then passes
those attributes through its protected resolvePoolAttributes() hook before
calling createPool. OracleAutonomousDialect overrides that hook (see
buildPoolAttributes()) to swap in the wallet / TLS connect string and pool
sizing, and lets the inherited connect() create the pool and track
connection state.What it refuses#
Autonomous Database returns ORA-01031: insufficient privileges for these. The
dialect's query() stops them first and names the alternative:| Asked for | Instead |
|---|
privilege: SYSDBA (or any administrative privilege) | Connect as ADMIN, which is not granted SYSDBA |
CREATE / ALTER / DROP TABLESPACE | Tablespaces are managed automatically |
ALTER SYSTEM other than SET, KILL SESSION, CANCEL SQL | — |
ALTER DATABASE other than DEFAULT EDITION, SET TIME_ZONE, SET CMU_WALLET | — |
CREATE / DROP DATABASE LINK | DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK / DROP_DATABASE_LINK |
ADMINISTER KEY MANAGEMENT | Oracle-managed keys, or OCI Vault |
CREATE LIBRARY | — |
CREATE DATABASE / DROP DATABASE, createDatabaseSQL() | Provision the database in OCI |
The guard checks only the first statement of each query. Anything else is left to
the server. CREATE USER ... DEFAULT TABLESPACE is accepted by the server but has
no effect.DBMS_CLOUD: loading from object storage#
Each builder returns { sql, binds }. Every value, secrets included, is a named
bind variable and never part of the SQL text. The copyData(),
createCloudCredential() and createCloudExternalTable() methods run them.const d = db.dialect as OracleAutonomousDialect;
await d.createCloudCredential({
credentialName: 'OBJ_CRED',
username: 'me@example.com',
password: authToken,
});
// BEGIN DBMS_CLOUD.CREATE_CREDENTIAL(credential_name => :credential_name, username => :username, password => :password); END;
await d.copyData({
table: 'SALES',
credentialName: 'OBJ_CRED',
fileUris: 'https://objectstorage.us-ashburn-1.oraclecloud.com/n/ns/b/bkt/o/sales*.csv',
format: { type: 'csv', skipheaders: 1 },
});
await d.createCloudExternalTable({
table: 'EXT_SALES',
fileUris: ['https://objectstorage.example.com/n/ns/b/bkt/o/sales.parquet'],
columnList: 'id NUMBER, amount NUMBER',
format: { type: 'parquet', schema: 'first' },
});
Credentials come in two forms: username/password (OCI auth token, AWS key pair,
Azure account key) and OCI API signing key (userOcid, tenancyOcid,
privateKey, fingerprint).
fileUris must be https:// URIs. More than one URI is sent as the
comma-delimited file_uri_list.
format is sent as a JSON document. Its values must be scalars.
buildDropCredentialSql() builds DBMS_CLOUD.DROP_CREDENTIAL.
References#
What differs from Oracle#
OracleAutonomousDialect extends OracleDialect (oracle) and
overrides 14 of its methods (Object.getOwnPropertyNames(OracleAutonomousDialect.prototype),
computed by the scratch test); everything else is inherited unchanged.| Area | Overridden |
|---|
| Connection and execution | buildPoolAttributes, oracleClientInitOptions, query, resolvePoolAttributes |
| DDL and schema | buildCreateCredentialSql, buildCreateExternalTableSql, buildDropCredentialSql, createCloudCredential, createCloudExternalTable, createDatabaseSQL, dropDatabaseSQL |
| Introspection | resolveConnectString |
| Other | buildCopyDataSql, copyData |
Capabilities#
Computed from the code — capabilitiesOf() (src/transaction-support.ts),
whereSupportOf() (src/query-builders/expression-render.ts),
sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the
builders — by the scratch test described in the
capability matrix.| Capability | oracleautonomous |
|---|
| Transactions atomic | yes |
| Savepoints | yes (no explicit RELEASE) |
| Isolation | up to SERIALIZABLE (accepted: READ COMMITTED, SERIALIZABLE); native levels READ ONLY |
| Transaction modes | read-only no; deferrable no; constraint deferral no |
| RETURNING | RETURNING on INSERT |
| Upsert | MERGE INTO … WHEN MATCHED / WHEN NOT MATCHED |
| JSON column | CLOB (JSONB → BLOB) |
| Vector column | VECTOR(3, FLOAT32) |
| Array column | VARCHAR2(255) |
| WHERE builder | 34 operator keys; $and/$or/$not; $exists: no; $in subquery: no; expression operands: no |
| SQL objects | family oracle: triggers yes; procedures yes; functions multiStatement; materialized views native; sequences yes; enums check; RLS no |
| Streaming | queryStream() present; LISTEN absent; createExtension() present |
Testing#
tests/dialects/oracleautonomous.test.ts exercises the dialect with a mocked driver, so nothing touches a server.See also#
oracle — the parent dialect this one extends Modified at 2026-10-04 00:40:16