Back to changelog
New
6 minute read

Oracle: Sequences, Global Temporary Tables, Database Links, Synonyms, CHECK Constraints, and More

Atlas now fully inspects, diffs, and migrates Oracle SEQUENCE objects, GLOBAL TEMPORARY TABLE with ON COMMIT semantics, DATABASE LINK (private and public), SYNONYM (private and public), VARCHAR2/CHAR length semantics, primary keys backed by pre-created indexes, and value CHECK constraints.

This release delivers a broad set of additions to the Oracle driver. Atlas now handles the full lifecycle for seven previously unsupported or incorrectly handled Oracle constructs.

Sequence Objects

Standalone SEQUENCE objects are now inspected, diffed, and managed as first-class schema objects. A sequence "…" { … } block is emitted in HCL with all standard options: start, increment, min_value, max_value, cache, cycle, and order. Previously, inspecting a schema containing sequences returned an empty block.

schema.hcl
sequence "my_seq" {  schema    = schema.app  start     = 1000  increment = 1  cache     = 20  cycle     = false}
table "orders" {  schema = schema.app  column "id" {    null    = true    type    = NUMBER    default = sql("\"my_seq\".NEXTVAL")  }}

The migration planner generates CREATE SEQUENCE, ALTER SEQUENCE, and DROP SEQUENCE statements. NEXTVAL defaults on columns that reference a managed sequence are preserved and normalized across schema-qualified and unqualified forms.

Global Temporary Tables

GLOBAL TEMPORARY TABLE is now correctly round-tripped. Previously, such a table was inspected as an ordinary permanent table and re-applying the HCL would silently recreate it as permanent. Atlas now captures the temporary nature and the ON COMMIT behavior in a temporary { } sub-block:

schema.hcl
table "session_data" {  schema = schema.app  column "id" {    null = true    type = NUMBER  }  temporary {    on_commit = PRESERVE_ROWS  }}
table "work_items" {  schema = schema.app  column "id" {    null = true    type = NUMBER  }  temporary {    on_commit = DELETE_ROWS  }}

The corresponding DDL is generated correctly:

atlas migrate diff
-- Add new table named "session_data"CREATE GLOBAL TEMPORARY TABLE "session_data" (  "id" NUMBER) ON COMMIT PRESERVE ROWS;
-- Add new table named "work_items"CREATE GLOBAL TEMPORARY TABLE "work_items" (  "id" NUMBER) ON COMMIT DELETE ROWS;

Database Links

DATABASE LINK objects, both private (schema-scoped) and PUBLIC (realm-level), are now inspected and managed. All four authentication modes are supported:

  • Connectionless (no CONNECT TO clause)
  • CONNECT TO CURRENT_USER
  • Fixed-user: CONNECT TO user IDENTIFIED BY password
  • Shared: AUTHENTICATED BY …

Passwords are write-only inputs: they are used to generate the CREATE DATABASE LINK statement (which is marked atlas:sensitive) but are not surfaced during inspection. Since Oracle has no CREATE OR REPLACE DATABASE LINK, a modification is implemented as DROP + CREATE.

schema.hcl
# Connectionless linkdatabase_link "L1" {  schema = schema.app  host   = "remote_db"}
# Current-user linkdatabase_link "L2" {  schema = schema.app  host   = "remote_db"  connect {    current_user = true  }}
# Fixed-user link (password is write-only: not surfaced on inspect)database_link "L3" {  schema = schema.app  host   = "remote_db"  connect {    user     = "HR"    password = "secret"  }}
# SHARED link with AUTHENTICATED BYdatabase_link "L4" {  schema = schema.app  host   = "remote_db"  connect {    user     = "HR"    password = "secret"  }  authenticated_by {    user     = "AUTH_USER"    password = "auth_secret"  }}
atlas migrate diff
-- Create database link "L1"CREATE DATABASE LINK "L1" USING 'remote_db';-- Create database link "L2"CREATE DATABASE LINK "L2" CONNECT TO CURRENT_USER USING 'remote_db';-- Create database link "L3"-- atlas:sensitiveCREATE DATABASE LINK "L3" CONNECT TO "HR" IDENTIFIED BY "secret" USING 'remote_db';-- Create database link "L4"-- atlas:sensitiveCREATE SHARED DATABASE LINK "L4" CONNECT TO "HR" IDENTIFIED BY "secret"  AUTHENTICATED BY "AUTH_USER" IDENTIFIED BY "auth_secret" USING 'remote_db';

Synonyms

SYNONYM objects (private and public) are now inspected and managed. Local targets (a managed table or sequence) are expressed as typed HCL references: object = table.T. Remote targets use a string object plus a db_link reference. Dependency ordering ensures the backing database link is always created before its synonyms.

schema.hcl
# Synonym pointing to a local tablesynonym "orders_syn" {  schema = schema.app  object = table.orders}
# Synonym pointing to a local sequencesynonym "seq_syn" {  schema = schema.app  object = sequence.my_seq}
# Synonym pointing to a remote object via a database linksynonym "remote_syn" {  schema  = schema.app  object  = "\"REMOTE_TABLE\""  db_link = database_link.L1}
# Public synonym pointing to a remote objectsynonym "pub_syn" {  object  = "\"REMOTE_TABLE\""  db_link = database_link.pub_link}

VARCHAR2 / CHAR Length Semantics

Oracle distinguishes VARCHAR2(n CHAR) (character-length semantics) from VARCHAR2(n) / VARCHAR2(n BYTE) (byte-length semantics, the default). Previously both were inspected identically as VARCHAR2(n), which silently lost the character-length qualifier. This makes a meaningful difference in multi-byte character sets.

Atlas now detects the character-length semantics during inspection and preserves them on the column type. The DDL formatter emits the full VARCHAR2(n CHAR) form in this case:

schema.hcl
table "t_charsem" {  schema = schema.app  column "c_char" {    null = true    type = sql("VARCHAR2(100 CHAR)")   # CHAR semantics preserved  }  column "c_byte" {    null = true    type = VARCHAR2(100)               # BYTE semantics (default)  }  column "c_nchar" {    null = true    type = sql("NVARCHAR2(50)")  }}
atlas migrate diff
-- Previously: both columns produced VARCHAR2(100)-- Now: CHAR semantics are preserved in the generated DDLCREATE TABLE "t_charsem" (  "c_char"  VARCHAR2(100 CHAR),  "c_byte"  VARCHAR2(100),  "c_nchar" NVARCHAR2(50));

Primary Keys Backed by Pre-created Indexes

When a primary key is created with ADD CONSTRAINT … PRIMARY KEY … USING INDEX <name>, Oracle records the constraint as using a pre-existing index rather than auto-creating one. Previously, Atlas only recognized primary keys backed by auto-created indexes, so such a primary key was demoted to a plain unique index and the primary key semantic was lost entirely.

schema.sql
-- Pre-created unique indexCREATE UNIQUE INDEX ACTIONS_PK ON ACTIONS (APPID, GATEID, ACTIONID);
-- PK constraint reusing the pre-created indexALTER TABLE ACTIONS  ADD CONSTRAINT ACTIONS_PK  PRIMARY KEY (APPID, GATEID, ACTIONID)  USING INDEX ACTIONS_PK ENABLE;

Atlas now matches the constraint to its pre-created index, correctly identifying and naming the primary key after its constraint (which may differ from the backing index name):

atlas schema inspect
# Previously: reported as a plain unique indexindex "ACTIONS_PK" {  unique  = true  columns = [column.APPID, column.GATEID, column.ACTIONID]}
# Now: reported as the table's primary keyprimary_key "ACTIONS_PK" {  columns = [column.APPID, column.GATEID, column.ACTIONID]}

Dropping such a primary key now also drops its backing pre-created index with DROP CONSTRAINT … DROP INDEX; without this, Oracle would retain the pre-created index as an orphaned object:

atlas migrate diff
-- Dropping a PK backed by a pre-created index-- also drops its backing index (DROP INDEX), so Oracle does not retain itALTER TABLE "ACTIONS" DROP CONSTRAINT "ACTIONS_PK" DROP INDEX;

CHECK Constraints

Value CHECK constraints are now inspected, diffed, and applied. Oracle stores NOT NULL constraints internally as CHECK constraints; these continue to be modeled as null = false on the column. Only value checks, such as age > 0 or doc IS JSON, are surfaced as check { } blocks:

schema.sql
CREATE TABLE t_check_demo (  doc  CLOB,  age  NUMBER,  CONSTRAINT doc_is_json  CHECK (doc IS JSON),  CONSTRAINT age_positive CHECK (age > 0));
atlas schema inspect
table "T_CHECK_DEMO" {  schema = schema.app  column "DOC" { null = true, type = CLOB }  column "AGE" { null = true, type = NUMBER }  check "DOC_IS_JSON"  { expr = "DOC IS JSON" }  check "AGE_POSITIVE" { expr = "AGE > 0" }}

The full lifecycle is supported:

  • Add: Emits an out-of-line CONSTRAINT … CHECK (…) clause in CREATE TABLE, or ALTER TABLE … ADD CONSTRAINT for an existing table.
  • Modify: Implemented as DROP + ADD (Oracle cannot alter a check expression in place).
  • Rename: Uses ALTER TABLE … RENAME CONSTRAINT, including renaming an Oracle-generated (SYS_C…) name to a user-defined one.
  • Anonymous checks: An unnamed ADD CHECK is non-reversible (Oracle auto-names it with a non-deterministic name, so no reversible DROP can be generated).
atlas migrate diff
-- Modify a check (DROP + ADD, Oracle cannot ALTER in place)ALTER TABLE "T_CHECK_DEMO" DROP CONSTRAINT "AGE_POSITIVE";ALTER TABLE "T_CHECK_DEMO" ADD CONSTRAINT "AGE_POSITIVE" CHECK (AGE >= 18);
-- Rename a check (same expression, new name — uses RENAME CONSTRAINT)ALTER TABLE "T_CHECK_DEMO" RENAME CONSTRAINT "AGE_POSITIVE" TO "AGE_NON_NEGATIVE";

Compatibility

All of these features work on supported Oracle versions. Inspect, diff, and apply all work with both the declarative workflow (atlas schema apply) and versioned migrations (atlas migrate diff).

featureoraclesequenceglobal-temporary-tabledatabase-linksynonymchar-semanticsprimary-keycheck-constraint