Skip to main content

Oracle VECTOR Columns with Atlas (23ai+)

Oracle Trademark

Oracle is a trademark of Oracle Corporation. Atlas is not affiliated with or endorsed by Oracle.

Oracle 23ai introduced the VECTOR column type for storing high-dimensional embeddings and enabling similarity searches directly inside the database. Atlas lets you define, inspect, and migrate VECTOR columns like any other schema type, so your AI-related schema changes flow through the same code review and CI/CD pipeline as the rest of your schema.

This guide walks through the versioned workflow: each change is captured as a migration file, reviewed by your team, and applied through CI/CD.

The Oracle driver is available only to Atlas Pro users. To use it, run:

atlas login

Prerequisites

  1. Docker
  2. Atlas installed on your machine:

    To download and install the custom release of the Atlas CLI, simply run the following in your terminal:

    curl -sSf https://atlasgo.sh | ATLAS_FLAVOR="oracle" sh
  3. An Atlas Pro account (run atlas login to authenticate)

Project Setup

Start a local Oracle 23ai container. The VECTOR type requires Oracle 23ai (23.4+):

docker run --rm \
-e ORACLE_PWD=Pssw0rd0995 \
-p 1521:1521 \
--name atlas-oracle \
-d container-registry.oracle.com/database/free:latest-lite
Waiting for the database to be ready

Oracle images are larger than most and take longer to initialize. Wait until the database is ready before continuing:

docker logs -f atlas-oracle 2>&1 | grep -m1 "DATABASE IS READY TO USE"
#########################
DATABASE IS READY TO USE!
#########################

Export the connection URL:

export DATABASE_URL="oracle://PDBADMIN:Pssw0rd0995@localhost:1521/FREEPDB1"

Grant PDBADMIN the privileges Atlas needs to manage schema objects:

docker exec -i atlas-oracle sqlplus \
-S 'sys/Pssw0rd0995@localhost:1521/FREEPDB1' AS SYSDBA <<SQL
GRANT CONNECT, RESOURCE, SELECT_CATALOG_ROLE TO PDBADMIN;
GRANT CREATE USER, CREATE ANY TABLE, CREATE VIEW TO PDBADMIN;
GRANT ALTER USER, ALTER ANY TABLE TO PDBADMIN;
GRANT DROP USER, DROP ANY TABLE TO PDBADMIN;
GRANT EXECUTE ON SYS.DBMS_LOCK TO PDBADMIN;
ALTER USER PDBADMIN QUOTA UNLIMITED ON SYSAUX;
SQL
Grant succeeded.
Grant succeeded.
Grant succeeded.
Grant succeeded.
Grant succeeded.
User altered.

Create an atlas.hcl configuration file:

atlas.hcl
docker "oracle" "dev" {
image = "container-registry.oracle.com/database/free:latest-lite"
database = "FREEPDB1"
baseline = <<-SQL
ALTER USER PDBADMIN DEFAULT TABLESPACE SYSAUX;
ALTER USER PDBADMIN QUOTA UNLIMITED ON SYSAUX;
SQL
}

env "local" {
url = getenv("DATABASE_URL")
dev = docker.oracle.dev.url
schema {
src = "file://schema.ora.hcl"
}
migration {
dir = "file://migrations"
}
}
Dev database and the latest-lite image

latest-lite ships without a USERS tablespace. Oracle needs a tablespace with Automatic Segment Space Management (ASSM) to create VECTOR columns, and in this image only SYSAUX has it. The baseline in the docker block sets PDBADMIN's default tablespace to SYSAUX before Atlas computes any diff, which is what makes the dev container work correctly. Atlas manages the dev container's lifecycle on its own.

Defining the Schema

Atlas supports all VECTOR variants introduced in Oracle 23ai:

Type expressionWhat it stores
VECTOR(1536, FLOAT32)Fixed-dimension, single-precision (e.g., OpenAI ada-002)
VECTOR(1536, FLOAT64)Fixed-dimension, double-precision
VECTOR(1536, INT8)Fixed-dimension, 8-bit integer (quantized)
VECTOR(1536, BINARY)Fixed-dimension binary vector
VECTOR(*, FLOAT32)Flexible dimensions (any embedding model)
VECTORFully flexible (any format, any dimensions)
VECTOR(1000, INT8, SPARSE)Sparse vector for high-dimensional keyword models

Model this as a DOCUMENTS table that stores AI-generated embeddings alongside source text:

schema.ora.hcl
table "DOCUMENTS" {
schema = schema.PDBADMIN

column "ID" {
type = NUMBER(10)
null = false
}
column "CONTENT" {
type = VARCHAR2(4000)
null = true
}
// Fixed dimensions, paired with OpenAI text-embedding-ada-002
column "EMBEDDING" {
type = VECTOR(1536, FLOAT32)
null = true
}
// Flexible dimensions, works with any embedding model
column "FLEX_EMBEDDING" {
type = VECTOR("*", FLOAT32)
null = true
}
// Untyped: store any vector without dimension or format constraints
column "RAW_VECTOR" {
type = VECTOR
null = true
}
// Sparse vector for keyword / BM25-style retrieval models
column "SPARSE_KEYWORDS" {
type = VECTOR(1000, INT8, SPARSE)
null = true
}
primary_key "PK_DOCUMENTS" {
columns = [column.ID]
}
}

schema "PDBADMIN" {}

Generating the Initial Migration

Run atlas migrate diff to generate the first migration file by comparing the schema file against the empty database:

atlas migrate diff create_documents --env local

Atlas writes two files to the migrations/ directory:

migrations/
├── 20260331120000_create_documents.sql
└── atlas.sum

Open the generated file to see what Atlas will run:

migrations/20260331120000_create_documents.sql
-- Add new table named "DOCUMENTS"
CREATE TABLE "DOCUMENTS" (
"ID" NUMBER(10) NOT NULL,
"CONTENT" VARCHAR2(4000),
"EMBEDDING" VECTOR(1536, FLOAT32),
"FLEX_EMBEDDING" VECTOR(*, FLOAT32),
"RAW_VECTOR" VECTOR,
"SPARSE_KEYWORDS" VECTOR(1000, INT8, SPARSE),
CONSTRAINT "PK_DOCUMENTS" PRIMARY KEY ("ID")
);

Atlas generates correct VECTOR DDL for every variant (fixed, flexible, untyped, and sparse) directly from the schema definition.

Applying the Migration

Apply the pending migration to the target database:

atlas migrate apply --env local
Migrating to version 20260331120000 (1 migrations in total):

-- migrating version 20260331120000
-> CREATE TABLE "DOCUMENTS" (
"ID" NUMBER(10) NOT NULL,
"CONTENT" VARCHAR2(4000),
"EMBEDDING" VECTOR(1536, FLOAT32),
"FLEX_EMBEDDING" VECTOR(*, FLOAT32),
"RAW_VECTOR" VECTOR,
"SPARSE_KEYWORDS" VECTOR(1000, INT8, SPARSE),
CONSTRAINT "PK_DOCUMENTS" PRIMARY KEY ("ID")
)
-- ok (14.159708ms)

-------------------------
-- 1.928394166s
-- 1 migration
-- 1 sql statement

Verify the deployed schema by inspecting the live database:

atlas schema inspect --env local
note

The atlas_schema_revisions table Atlas uses internally for migration tracking is omitted from this output.

table "DOCUMENTS" {
schema = schema.PDBADMIN
column "ID" {
null = false
type = NUMBER(10)
}
column "CONTENT" {
null = true
type = VARCHAR2(4000)
}
column "EMBEDDING" {
null = true
type = VECTOR(1536, FLOAT32)
}
column "FLEX_EMBEDDING" {
null = true
type = VECTOR("*", FLOAT32)
}
column "RAW_VECTOR" {
null = true
type = VECTOR
}
column "SPARSE_KEYWORDS" {
null = true
type = VECTOR(1000, INT8, SPARSE)
}
primary_key "PK_DOCUMENTS" {
columns = [column.ID]
}
}
schema "PDBADMIN" {
}

VECTOR columns round-trip cleanly: inspect, modify, and re-apply without data loss or type coercion.

With Atlas Cloud, push the migration directory and deploy from any CI/CD platform:

atlas migrate push app --env local

Making Incremental Changes

When requirements change, update the schema file and run migrate diff again. Atlas generates only what changed, leaving existing data untouched.

For example, add a high-precision FLOAT64 embedding to support a more accurate semantic model:

Add to schema.ora.hcl:

schema.ora.hcl
table "DOCUMENTS" {
schema = schema.PDBADMIN
// ... existing columns ...
// High-precision embedding column
column "EMBEDDING_F64" {
type = VECTOR(1536, FLOAT64)
null = true
}
primary_key "PK_DOCUMENTS" {
columns = [column.ID]
}
}

Generate the incremental migration:

atlas migrate diff add_f64_embedding --env local

Atlas generates only what changed (the new column):

migrations/20260331130000_add_f64_embedding.sql
-- Add columns to table "DOCUMENTS"
ALTER TABLE "DOCUMENTS" ADD "EMBEDDING_F64" VECTOR(1536, FLOAT64);

The migration directory now holds both files. The sum of all migrations represents the current state of the schema:

migrations/
├── 20260331120000_create_documents.sql
├── 20260331130000_add_f64_embedding.sql
└── atlas.sum

VECTOR in Procedures and Functions

VECTOR is a valid argument and return type in procedure and function blocks. This lets Atlas manage PL/SQL objects that work with vector data as part of the same schema definition and migration workflow.

note

In Oracle PL/SQL, procedure and function argument types must use the base VECTOR type without dimension or format parameters. Fixed types like VECTOR(1536, FLOAT32) are only valid in table column definitions.

Stored Procedure

Define a procedure that uses VECTOR_DISTANCE() to find the top-K nearest neighbours:

schema.ora.hcl
procedure "FIND_SIMILAR" {
schema = schema.PDBADMIN
arg "P_QUERY_VEC" {
type = VECTOR
}
arg "P_TOP_K" {
type = NUMBER
}
as = <<-SQL
BEGIN
FOR r IN (
SELECT ID, CONTENT,
VECTOR_DISTANCE(EMBEDDING, P_QUERY_VEC, COSINE) AS dist
FROM PDBADMIN.DOCUMENTS
ORDER BY dist
FETCH FIRST P_TOP_K ROWS ONLY
) LOOP
DBMS_OUTPUT.PUT_LINE(r.ID || ': ' || r.CONTENT);
END LOOP;
END;
SQL
}

Function with VECTOR Return Type

Define a function that returns a vector value:

schema.ora.hcl
function "NORMALIZE_VEC" {
schema = schema.PDBADMIN
return = VECTOR
arg "P_INPUT" {
type = VECTOR
}
as = <<-SQL
BEGIN
RETURN P_INPUT;
END;
SQL
}

After adding either object to the schema file, generate the migration:

atlas migrate diff add_vector_procedure --env local
migrations/20260331184653_add_vector_procedure.sql
-- Create "NORMALIZE_VEC" function
CREATE FUNCTION "NORMALIZE_VEC" (
"P_INPUT" VECTOR
)
RETURN VECTOR AS
BEGIN
RETURN P_INPUT;
END;;
-- Create "FIND_SIMILAR" procedure
CREATE PROCEDURE "FIND_SIMILAR" (
"P_QUERY_VEC" VECTOR,
"P_TOP_K" NUMBER
) AS
BEGIN
FOR r IN (
SELECT ID, CONTENT,
VECTOR_DISTANCE(EMBEDDING, P_QUERY_VEC, COSINE) AS dist
FROM PDBADMIN.DOCUMENTS
ORDER BY dist
FETCH FIRST P_TOP_K ROWS ONLY
) LOOP
DBMS_OUTPUT.PUT_LINE(r.ID || ': ' || r.CONTENT);
END LOOP;
END;;

Apply the migration:

atlas migrate apply --env local
Migrating to version 20260331184653 from 20260331181344 (1 migrations in total):

-- migrating version 20260331184653
-> CREATE FUNCTION "NORMALIZE_VEC" (
"P_INPUT" VECTOR
)
RETURN VECTOR AS
BEGIN
RETURN P_INPUT;
END;
-> CREATE PROCEDURE "FIND_SIMILAR" (
"P_QUERY_VEC" VECTOR,
"P_TOP_K" NUMBER
) AS
BEGIN
FOR r IN (
SELECT ID, CONTENT,
VECTOR_DISTANCE(EMBEDDING, P_QUERY_VEC, COSINE) AS dist
FROM PDBADMIN.DOCUMENTS
ORDER BY dist
FETCH FIRST P_TOP_K ROWS ONLY
) LOOP
DBMS_OUTPUT.PUT_LINE(r.ID || ': ' || r.CONTENT);
END LOOP;
END;
-- ok (52.571041ms)

-------------------------
-- 168.383542ms
-- 1 migration
-- 2 sql statements

Next Steps

Have questions? Find our team on Discord or schedule a demo.