Schema as Code: HCL Syntax
Atlas enables you to define your database schema as code, whether in SQL, through ORMs, custom programs, or the Atlas HCL language. The HCL-based language allows developers to describe database schemas in a declarative manner, and it supports all SQL features supported by Atlas. The main advantages of using HCL are that it enables developers to manage their database schemas like regular code, facilitates sharing and reusing files between projects, allows variable injection, and provides the ability to attach annotations to objects, such as PII or sensitive data.
Editor Support
The Atlas HCL language provides plugins for popular editors like VSCode and JetBrains to enhance your day-to-day editing, navigation, formatting, and testing experience.
File Naming for Editor Support
To enable your editor to recognize Atlas HCL files and provide syntax support, use the following file extensions:
| Database | File Suffix |
|---|---|
| MySQL | .my.hcl |
| MariaDB | .ma.hcl |
| PostgreSQL | .pg.hcl |
| SQLite | .lt.hcl |
| ClickHouse | .ch.hcl |
| SQL Server | .ms.hcl |
| Redshift | .rs.hcl |
| Oracle | .oc.hcl |
| Spanner | .sp.hcl |
| Snowflake | .sf.hcl |
| Databricks | .dbx.hcl |
Name your files accordingly, for example: warehouse_schema.pg.hcl for PostgreSQL, test_users.test.hcl for a test file.
Schema
The schema object describes a database schema. A DATABASE in MySQL and SQLite, or a SCHEMA in PostgreSQL.
An HCL file can contain 1 or more schema objects.
- MySQL
- PostgreSQL
- SQLite
- SQL Server
- ClickHouse
- Redshift
In MySQL and MariaDB, the schema resource can contain the charset and collate attributes. Read more about them
in MySQL or
MariaDB websites.
# Schema with attributes.
schema "market" {
charset = "utf8mb4"
collate = "utf8mb4_0900_ai_ci"
comment = "A schema comment"
}
# Schema without attributes.
schema "orders" {}
schema "public" {
comment = "A schema comment"
}
schema "private" {}
Atlas does not support attached databases, and support only the default
database (i.e. main).
schema "main" {}
schema "dbo" {
comment = "A schema comment"
}
schema "private" {}
In Clickhouse, the schema resource can contain the engine attribute. If not specified, the default engine depends on ClickHouse settings.
Use sql() to specify the engine in advanced cases.
Read more about database engines in ClickHouse documentation.
schema "default" {
engine = sql("Replicated('/clickhouse/databases/default', '{shard}', '{replica}')")
}
schema "atomic" {
engine = Atomic
}
schema "public" {
comment = "A schema comment"
}
schema "private" {}
Table
A table describes a table in a SQL database. A table hold its columns, indexes, constraints, and additional attributes
that are supported by the different drivers.
table "users" {
schema = schema.public
column "id" {
type = int
}
column "name" {
type = varchar(255)
}
column "manager_id" {
type = int
}
primary_key {
columns = [
column.id
]
}
index "idx_name" {
columns = [
column.name
]
unique = true
}
foreign_key "manager_fk" {
columns = [column.manager_id]
ref_columns = [column.id]
on_delete = CASCADE
on_update = NO_ACTION
}
}
Check
A check is a child resource of a table that describes a CHECK constraint.
table "products" {
column "price" {
type = float
}
check "positive price" {
expr = "price > 0"
}
}
Partitions
The partition option allows defining partitioned tables. Table partitioning refers to splitting large logical tables
into smaller physical ones, but the HCL shape differs by driver.
Partitions are available only to Atlas Pro users. To use this feature, run:
atlas login
- PostgreSQL
- MySQL
- SQL Server
table "logs" {
schema = schema.public
column "date" {
type = date
}
column "text" {
type = integer
}
partition {
type = RANGE
columns = [column.date]
}
}
table "metrics" {
schema = schema.public
column "x" {
type = integer
}
column "y" {
type = integer
}
partition {
type = RANGE
by {
column = column.x
}
by {
expr = "floor(y)"
}
}
}
Defining Partitions
In PostgreSQL, the top-level partition block allows defining child partitions. The of attribute is used to specify
the parent table that the partition belongs to, and the range, list and hash attributes are used to define the
partition boundaries.
- LIST
- HASH
- RANGE
table "traffic" {
schema = schema.public
column "id" {
null = false
type = serial
}
column "region" {
null = false
type = text
}
column "ts" {
null = false
type = timestamp
}
primary_key {
columns = [column.region]
}
partition {
type = LIST
columns = [column.region]
}
}
partition "traffic_default" {
schema = schema.public
of = table.traffic
}
partition "traffic_eu" {
schema = schema.public
of = table.traffic
list {
in = ["'de'", "'fr'", "'it'"]
}
}
partition "traffic_us" {
schema = schema.public
of = table.traffic
list {
in = ["'us-east'", "'us-west'"]
}
}
// ...
table "metrics" {
schema = schema.public
column "device_id" {
null = false
type = integer
}
column "ts" {
null = false
type = timestamp
}
column "value" {
null = true
type = double_precision
}
primary_key {
columns = [column.device_id]
}
partition {
type = HASH
columns = [column.device_id]
}
}
partition "metrics_0" {
schema = schema.public
of = table.metrics
hash {
modulus = 4
remainder = 0
}
}
partition "metrics_1" {
schema = schema.public
of = table.metrics
hash {
modulus = 4
remainder = 1
}
}
// ...
table "invoices" {
schema = schema.public
column "id" {
null = false
type = serial
}
column "issued_at" {
null = false
type = date
default = sql("CURRENT_DATE")
}
column "customer_id" {
null = false
type = integer
}
column "amount" {
null = false
type = numeric
default = 0
}
primary_key {
columns = [column.id, column.issued_at]
}
partition {
type = RANGE
columns = [column.issued_at]
}
}
partition "invoices_2025_06" {
schema = schema.public
of = table.invoices
range {
from = ["'2025-06-01'"]
to = ["'2025-07-01'"]
}
}
partition "invoices_2025_08" {
schema = schema.public
of = table.invoices
range {
from = ["'2025-08-01'"]
to = ["'2025-09-01'"]
}
}
Computed Partitions
The HCL language allows defining computed partitions using the for_each meta-argument. This is useful for creating
partitions based on dynamic values, such as dates or other computed values. For example, given the logs table below,
the following HCL code maintains partitions for the last 7 days, creating a new partition each day and deleting old ones.
table "logs" {
schema = schema.public
column "log_time" {
null = false
type = timestamptz
}
// ...
partition {
type = RANGE
columns = [column.log_time]
}
}
locals {
now = timestamp()
days_ahead = [0, 1, 2, 3, 4, 5, 6, 7]
partition_dates = [for d in local.days_ahead : formatdate("YYYY-MM-DD", timeadd(local.now, "${d * 24}h"))]
}
partition {
for_each = [
for i in range(0, length(local.partition_dates) - 1) : {
name = format("logs_%s", replace(local.partition_dates[i], "-", ""))
from = local.partition_dates[i]
to = local.partition_dates[i + 1]
}
]
name = each.value.name // logs_20250720, ..., logs_20250726.
schema = schema.public
of = table.logs
range {
from = ["'${each.value.from}'"]
to = ["'${each.value.to}'"]
}
}
In MySQL, partition definitions stay inside the table-level partition block. For RANGE and LIST partitioning,
each named partition is defined with a nested partition "name" block.
- RANGE
- LIST
- HASH
- KEY
table "range_orders" {
schema = schema.public
column "id" {
null = false
type = int
}
partition {
type = RANGE
by {
expr = "(`id` + 1)"
}
partition "p0" {
values_less_than = ["101"]
comment = "first range"
}
partition "pmax" {
values_less_than = ["MAXVALUE"]
comment = "catch all range"
}
}
}
table "range_columns_scores" {
schema = schema.public
column "tenant_id" {
null = false
type = int
}
column "score" {
null = false
type = int
}
partition {
type = RANGE_COLUMNS
columns = [column.tenant_id, column.score]
partition "p0" {
values_less_than = ["10", "100"]
comment = "small scores"
}
partition "pmax" {
values_less_than = ["MAXVALUE", "MAXVALUE"]
comment = "catch all scores"
}
}
}
table "list_numbers" {
schema = schema.public
column "id" {
null = false
type = int
}
partition {
type = LIST
by {
expr = "(`id` + 1)"
}
partition "p_low" {
values_in = ["2", "3"]
comment = "low ids"
}
partition "p_high" {
values_in = ["4", "5"]
comment = "high ids"
}
}
}
table "list_columns_regions" {
schema = schema.public
column "region_id" {
null = false
type = int
}
partition {
type = LIST_COLUMNS
columns = [column.region_id]
partition "p_local" {
values_in = ["1", "2"]
comment = "local regions"
}
partition "p_remote" {
values_in = ["3", "4"]
comment = "remote regions"
}
}
}
table "hash_events" {
schema = schema.public
column "id" {
null = false
type = int
}
partition {
type = HASH
by {
expr = "(`id` + 1)"
}
partition "p0" {
comment = "hash 0"
}
partition "p1" {
comment = "hash 1"
}
}
}
table "linear_hash_events" {
schema = schema.public
column "id" {
null = false
type = int
}
partition {
type = LINEAR_HASH
by {
expr = "(`id` + 1)"
}
partition "p0" {
comment = "linear hash 0"
}
partition "p1" {
comment = "linear hash 1"
}
}
}
table "key_logs" {
schema = schema.public
column "id" {
null = false
type = int
}
column "tenant_id" {
null = false
type = int
}
partition {
type = KEY
columns = [column.id, column.tenant_id]
partition "p0" {
comment = "key 0"
}
partition "p1" {
comment = "key 1"
}
}
}
table "linear_key_members" {
schema = schema.public
column "id" {
null = false
type = int
}
partition {
type = LINEAR_KEY
columns = [column.id]
partition "p0" {
comment = "linear key 0"
}
partition "p1" {
comment = "linear key 1"
}
}
}
In SQL Server, partitioning is composed of two database-scoped objects - a partition_function and a
partition_scheme - plus a table-level partition block that binds a table to a scheme on a specific column.
partition_function "PF1" {
input = int
values = ["100", "200", "300"]
}
partition_scheme "PS1" {
partition = partition_function.PF1
filegroups = ["PRIMARY", "PRIMARY", "PRIMARY", "PRIMARY"]
}
table "Orders" {
schema = schema.dbo
column "OrderID" {
null = false
type = int
}
column "OrderDate" {
null = false
type = int
}
primary_key {
columns = [column.OrderID, column.OrderDate]
}
partition {
scheme = partition_scheme.PS1
column = column.OrderDate
}
}
The schema above corresponds to the following SQL:
CREATE PARTITION FUNCTION [PF1] (int) AS RANGE LEFT FOR VALUES (100, 200, 300);
CREATE PARTITION SCHEME [PS1] AS PARTITION [PF1] ALL TO ([PRIMARY]);
CREATE TABLE [dbo].[Orders] (
[OrderID] int NOT NULL,
[OrderDate] int NOT NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY ([OrderID] ASC, [OrderDate] ASC)
) ON [PS1]([OrderDate]);
Defining Partitions
The partition_function block accepts the following attributes:
input- the parameter type. SQL Server allows exactly one.range-LEFT(the default) orRIGHT. It is omitted in HCL whenLEFT, and emitted explicitly whenRIGHT.values- the ordered boundary values, as string literals.
partition_function "PF2" {
input = int
range = RIGHT
values = ["100", "200", "300"]
}
The partition_scheme block accepts the following attributes:
partition- a reference to apartition_function.filegroups- a list of filegroup names. Its length must belen(values) + 1. When all entries are identical, Atlas emits the compactALL TO ([...])form.
The table-level partition block accepts the following attributes:
scheme- a reference to apartition_scheme.column- the partitioning column.
Foreign Tables
The foreign_table option is a PostgreSQL-specific block that allows defining a foreign table.
A foreign table is a table that is not local to the current database but is defined in another database.
foreign_table "table_1" {
schema = schema.public
column "id" {
type = integer
}
// If the server is defined in the
// same schema, we can reference it.
server = server.test_server
options = {
schema_name = "public"
table_name = "tablex_1"
}
}
foreign_table "table_2" {
schema = schema.public
column "id" {
type = integer
}
column "name" {
type = integer
options = {
column_name = "other_name"
}
}
// If the server is not defined in the same
// schema, we can define its name as a string.
server = "external_server"
}
server "test_server" {
fdw = extension.postgres_fdw
comment = "test server"
options = {
dbname = "postgres"
host = "localhost"
port = "5429"
}
}
Row Level Security
The row_security option is a PostgreSQL-specific option that allows enabling row-level security policies for a table.
table "users" {
schema = schema.public
column "id" {
type = int
}
row_security {
enabled = true // ENABLE ROW LEVEL SECURITY
enforced = true // FORCE ROW LEVEL SECURITY
}
}
To define row-level security policies for a table, refer to the policy example.
Full-Text Index
- SQL Server
- MySQL
The fulltext option is a SQL Server-specific option that allows enabling full-text index for a table.
table "t1" {
schema = schema.dbo
column "c1" {
null = false
type = bigint
identity {
seed = 701
increment = 1000
}
}
column "c2" {
null = false
type = varbinary(-1)
}
column "c3" {
null = false
type = char(3)
}
index "uq_t1" {
unique = true
columns = [column.c1]
}
fulltext {
unique_key = index.uq_t1
filegroup = "PRIMARY"
catalog = "FT_CD"
on {
column = column.c2
type = column.c3
language = "English"
}
}
}
schema "dbo" {
}
To able to use this feature, you need to enable the Full-Text Search feature in SQL Server both on the production server and the devdb. Bellow is example of enabling the feature on the devdb, with custom dockerfile.
- atlas.hcl
- Dockerfile
- schema.sql
docker "sqlserver" "dev" {
image = "mssql:2017-fts"
build {
context = "."
platform = ["linux/amd64"] # SQL Server 2017 only supports amd64 architecture.
}
database = "awesome" # Can't use full-text search in master database.
collate = "Vietnamese_CI_AS" # Collation for the database.
# We need create the default catalog for full-text search on the database.
baseline = <<SQL
CREATE FULLTEXT CATALOG [FT_CD] ON FILEGROUP [PRIMARY] AS DEFAULT;
SQL
}
env {
name = atlas.env
dev = docker.sqlserver.dev.url
src = "file://schema.sql"
}
The content is taken from this example on the official Microsoft repository.
# mssql-agent-fts-ha-tools
# Maintainers: Microsoft Corporation (twright-msft on GitHub)
# GitRepo: https://github.com/Microsoft/mssql-docker
# Base OS layer: Latest Ubuntu LTS
FROM ubuntu:16.04
# Install prerequistes since it is needed to get repo config for SQL server
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -yq curl apt-transport-https && \
# Get official Microsoft repository configuration
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \
curl https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2017.list | tee /etc/apt/sources.list.d/mssql-server.list && \
apt-get update && \
# Install SQL Server from apt
apt-get install -y mssql-server && \
# Install optional packages
apt-get install -y mssql-server-ha && \
apt-get install -y mssql-server-fts && \
# Cleanup the Dockerfile
apt-get clean && \
rm -rf /var/lib/apt/lists
# Run SQL Server process
CMD /opt/mssql/bin/sqlservr
-- Create "t1" table
CREATE TABLE [t1] (
[c1] bigint IDENTITY (701, 1000) NOT NULL,
[c2] varbinary(MAX) NOT NULL,
[c3] char(3) NOT NULL
);
-- Create index "uq_t1" to table: "t1"
CREATE UNIQUE CLUSTERED INDEX [uq_t1] ON [t1] ([c1] ASC);
-- Add full-text index on table "t1"
CREATE FULLTEXT INDEX ON [t1] (
[c2] TYPE COLUMN [c3] LANGUAGE 'English'
) KEY INDEX [uq_t1] ON ([FT_CD], FILEGROUP [PRIMARY]);
Then we can run atlas schema inspect --env local --url "env://src" to get the schema in HCL format.
To use full-text indexes in MySQL, you need to create a full-text index on the table.
table "users" {
schema = schema.myschema
column "text" {
null = false
type = text
}
index "users_text" {
type = FULLTEXT
columns = [column.text]
}
index "ngram_text" {
type = FULLTEXT
parser = ngram
columns = [column.text]
}
}
schema "myschema" {}
Table Qualification
In some cases, an Atlas DDL document may contain multiple tables of the same name. This usually happens
when the same table name appears in two different schemas. In these cases, the table names must be
disambiguated by using resource qualifiers. The following document describes a
database that contains two schemas named a and b, and both of them contain a table named users.
schema "a" {}
schema "b" {}
table "a" "users" {
schema = schema.a
// .. columns
}
table "b" "users" {
schema = schema.b
// .. columns
}
Storage Engine
The engine attribute allows for overriding the default storage engine of the table. Supported by MySQL, MariaDB and ClickHouse.
- MySQL & MariaDB
- ClickHouse
table "users" {
schema = schema.public
engine = MyISAM
}
table "posts" {
schema = schema.public
engine = InnoDB
}
table "orders" {
schema = schema.public
engine = "MyRocks"
}
In ClickHouse, the engine can be specified using either enumerated values or string literals
table "users" {
schema = schema.public
engine = MergeTree
}
table "posts" {
schema = schema.public
engine = "ReplacingMergeTree"
}
For complex engines, use the sql() function to specify the engine in advanced cases.
table "users" {
schema = schema.public
engine = sql("Replicated('/clickhouse/tables/orders', '{shard}', '{replica}')")
}
TimeSeries
The TimeSeries engine is designed to optimize storage and query performance for time-series data.
To create a table with the TimeSeries engine, use the following syntax:
table "orders" {
schema = schema.public
engine = TimeSeries
}
Use the engine block to specify the TimeSeries engine with
External Target Tables.
table "orders" {
schema = schema.public
engine "TimeSeries" {
data = public.data_table
tags = public.tags_table
metrics = public.metrics_table
}
}
For adjusting the types of columns, specify them explicitly while defining the TimeSeries table.
table "orders" {
schema = schema.public
engine = TimeSeries
column "timestamp" {
type = DateTime64(6)
}
}
Inner UUID of the TimeSeries engine is not supported by Atlas currently.
System-Versioned Tables
System-Versioned tables are available only to Atlas Pro users. To use this feature, run:
atlas login
- SQL Server
- MariaDB
The system_versioned block allows marking a table as a system-versioned temporal table.
Supported by SQL Server. This block can be used to define the history table, retention period, and the period unit.
The period block defines the period columns for the system-versioned table. It requires two columns: start and end,
which are used to define the period of validity for each row. These columns must be of type datetime2, non-nullable, and
have the generated always as row start and row end respectively.
schema "dbo" {}
table "t1" {
schema = schema.dbo
column "c1" {
type = int
null = false
}
column "c2" {
type = money
null = false
}
column "c3" {
type = datetime2(7)
null = false
generated_always {
as = ROW_START
}
}
column "c4" {
type = datetime2(7)
null = false
generated_always {
as = ROW_END
}
}
primary_key {
on {
column = column.c1
desc = true
}
}
period "system_time" {
type = SYSTEM_TIME
start = column.c3
end = column.c4
}
system_versioned {
history_table = "dbo.t1_History"
retention = 3
retention_unit = MONTH
}
}
The system_versioned attribute allows marking a table as a system-versioned table.
Supported by MariaDB.
table "f" {
schema = schema.public
column "id" {
type = int
}
// ...
system_versioned = true
}
Distribution
The distribution block is a Redshift-specific option that allows specifying the distribution method of the table.
table "users" {
schema = schema.public
column "id" {
type = int
}
distribution {
style = KEY // EVEN | ALL | AUTO
key = column.id // only for KEY style
}
}
Sorting
The sort block is a Redshift-specific option that allows specifying the sorting method of the table.
table "users" {
schema = schema.public
column "id" {
type = int
}
sort {
style = COMPOUND // INTERLEAVED | COMPOUND | AUTO
columns = [column.id]
}
}
AUTORedshift restricts user access to certain external tables which are used to inspect the sort style.
Therefore, Atlas will ignore differences when changing the style to AUTO.
You will need to manually adjust the sort style on your target Redshift database after modifying it in the Atlas schema.
To change the sort style to AUTO, run the following SQL command:
ALTER TABLE "my_table" ALTER SORTKEY AUTO;
View
A view is a virtual table in the database, defined by a statement that queries rows from one or more existing
tables or views.
Views are available only to Atlas Pro users. To use this feature, run:
atlas login
view "clean_users" {
schema = schema.public
column "id" {
type = int
}
column "name" {
type = text
}
as = <<-SQL
SELECT u.id, u.name
FROM ${table.users.name} AS u
JOIN ${view.active_users.name} AS au USING (id)
SQL
depends_on = [table.users, view.t1]
comment = "A view to active users without sensitive data"
}
view "comedies" {
schema = schema.public
column "id" {
type = int
}
column "name" {
type = text
}
as = "SELECT id, name FROM films WHERE kind = 'Comedy'"
depends_on = [table.films]
check_option = CASCADED
security = INVOKER // DEFINER | INVOKER (MySQL/MariaDB only).
}
Atlas's testing framework allows you to write unit tests for your views. The following example demonstrates how
to write tests for the clean_users view defined above. For more detail, read the schema testing docs
or see the full example.
- Simple Test
- Table-driven Test
test "schema" "view" {
# Seeding to test view.
exec {
sql = "INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');"
}
log {
message = "Seeded the database"
}
# Expected exec to pass.
exec {
sql = <<SQL
SELECT id, name
FROM clean_users;
SQL
}
log {
message = "Tested the view"
}
# Validates data.
exec {
sql = "SELECT id, name FROM clean_users;"
format = table
output = <<TAB
id | name
----+---------
1 | Alice
2 | Bob
3 | Charlie
TAB
}
log {
message = "Table is as expected"
}
}
test "schema" "view" {
for_each = [
{id = 1, name = "Alice"},
{id = 2, name = "Bob"},
{id = 3, name = "Charlie"}
]
# Seed the `users` table.
exec {
sql = "INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');"
}
# Query the `clean_users` view.
exec {
sql = "SELECT id, name FROM clean_users WHERE id IN (1, 2, 3);"
}
# Check each ID returns the right user.
log {
message = "Testing ${each.value.id} -> ${each.value.name}"
}
}
Materialized View
A materialized view is a table-like structure that holds the results of a query. Unlike a regular view, the results of
a materialized view are stored in the database and can be refreshed periodically to reflect changes in the underlying data.
Materialized views are available only to Atlas Pro users. To use this feature, run:
atlas login
- PostgreSQL
- Clickhouse
- Redshift
materialized "mat_view" {
schema = schema.public
column "total" {
null = true
type = numeric
}
index "idx_expr" {
unique = true
on {
expr = "((total > (0)::numeric))"
}
}
index "idx_pred" {
unique = true
columns = [column.total]
where = "(total < (0)::numeric)"
}
as = <<-SQL
SELECT sum(total) AS total
FROM m1;
SQL
depends_on = [materialized.m1]
}
When creating materialized views with TO [db.]table,
the view will be created with the same structure as the table or view specified in the TO clause.
materialized "mat_view" {
schema = schema.public
to = table.dest
as = "SELECT * FROM table.src"
depends_on = [table.src]
}
The engine and primary_key attributes are required if the TO clause is not specified.
In this syntax, populate can be used for the first time to populate the materialized view.
materialized "mat_view" {
schema = schema.public
engine = MergeTree
column "id" {
type = UInt32
}
column "name" {
type = String
}
primary_key {
columns = [column.id]
}
as = "SELECT * FROM table.src"
populate = true
depends_on = [table.src]
}
materialized "mat_view" {
schema = schema.public
column "c1" {
null = true
type = smallint
}
as = "SELECT * FROM t1;"
comment = "example materialized view"
distribution {
style = EVEN
}
sort {
style = AUTO
}
depends_on = [table.t1]
}
Column
A column is a child resource of a table.
column "name" {
type = text
null = false
}
column "age" {
type = integer
default = 42
}
column "active" {
type = tinyint(1)
default = true
}
Properties
| Name | Kind | Type | Description |
|---|---|---|---|
| null | attribute | bool | Defines whether the column is nullable. |
| type | attribute | *schemahcl.Type | Defines the type of data that can be stored in the column. |
| default | attribute | *schemahcl.LiteralValue | Defines the default value of the column. |
Generated Columns
Generated columns are columns whose their values are computed using other columns or by deterministic expressions.
- MySQL
- PostgreSQL
- SQLite
- SQL Server
table "users" {
schema = schema.test
column "a" {
type = int
}
column "b" {
type = int
# In MySQL, generated columns are VIRTUAL by default.
as = "a * 2"
}
column "c" {
type = int
as {
expr = "a * b"
type = STORED
}
}
}
table "users" {
schema = schema.test
column "a" {
type = int
}
column "b" {
type = int
# In PostgreSQL, generated columns are STORED by default.
as = "a * 2"
}
column "c" {
type = int
as {
expr = "a * b"
type = STORED
}
}
}
table "users" {
schema = schema.test
column "a" {
type = int
}
column "b" {
type = int
# In SQLite, generated columns are VIRTUAL by default.
as = "a * 2"
}
column "c" {
type = int
as {
expr = "a * b"
type = STORED
}
}
}
table "users" {
schema = schema.test
column "a" {
type = int
}
column "b" {
type = int
as = "a * 2"
}
column "c" {
type = int
as {
expr = "a * b"
# In SQLServer, computed columns are non-PERSISTED by default.
type = PERSISTED
}
}
}
Note, it is recommended to use the --dev-url option when generated columns are used.
Encodings
Encodings are used to define the compression algorithm for the column data. Supported by ClickHouse and Redshift.
- Redshift
table "users" {
schema = schema.public
column "name" {
type = text
encode = LZ4 // AZ64 | RAW | LZ4 | ZSTD
}
}
Column Types
The SQL dialects supported by Atlas (Postgres, MySQL, MariaDB, and SQLite) vary in the types they support. At this point, the Atlas DDL does not attempt to abstract away the differences between various databases. This means that the schema documents are tied to a specific database engine and version. This may change in a future version of Atlas as we plan to add "Virtual Types" support. This section lists the various types that are supported in each database.
For a full list of supported column types, click here.
Primary Key
A primary_key is a child resource of a table, and it defines the table's
primary key.
Example
- Columns
- Expressions
primary_key {
columns = [column.id]
}
Properties
| Name | Kind | Type | Description |
|---|---|---|---|
| columns | attribute | reference (list) | A list of references to columns that comprise the primary key. |
Note, primary key expressions are supported by ClickHouse.
primary_key {
on {
column = column.id
}
on {
expr = "c1 + c2"
}
}
Properties
| Name | Kind | Type | Description |
|---|---|---|---|
| on | resource | schema.IndexPart (list) | The index parts that comprise the index |
Foreign Key
Foreign keys are child resources of a table, and it defines some columns in the table
as references to columns in other tables.
Example
table "users" {
schema = schema.public
column "id" {
type = integer
}
primary_key {
columns = [column.id]
}
}
table "orders" {
schema = schema.market
// ...
column "owner_id" {
type = integer
}
foreign_key "owner_id" {
columns = [column.owner_id]
ref_columns = [table.users.column.id]
on_update = NO_ACTION
on_delete = NO_ACTION
}
}
Referencing Qualified Tables
If a foreign key references a column in a qualified table, it is referenced
using table.<qualifier>.<table_name>.column.<column_name>:
table "public" "users" {
schema = schema.public
column "id" {
type = integer
}
primary_key {
columns = [column.id]
}
}
table "admin" "users" {
schema = schema.admin
// ...
column "external_id" {
type = integer
}
foreign_key "external_id" {
columns = [column.external_id]
ref_columns = [table.public.users.column.id]
on_update = NO_ACTION
on_delete = NO_ACTION
}
}
Properties
| Name | Kind | Type | Description |
|---|---|---|---|
| columns | attribute | reference (list) | The columns that reference other columns. |
| ref_columns | attribute | reference (list) | The referenced columns. |
| on_update | attribute | schema.ReferenceOption | Defines what to do on update. |
| on_delete | attribute | schema.ReferenceOption | Defines what to do on delete. |
Index
Indexes are child resources of a table, and it defines an index on the table.
Example
# Columns only.
index "idx_name" {
unique = true
columns = [column.name]
}
# Columns and order.
index "idx_name" {
unique = true
on {
column = column.rank
}
on {
column = column.score
desc = true
}
}
# Custom index type.
index "idx_name" {
type = GIN
columns = [column.data]
}
# Include non-key columns.
index "idx_include" {
columns = [column.range]
include = [column.version]
}
# Define operator class.
index "idx_operator_class" {
type = GIN
on {
column = column.j
ops = jsonb_path_ops
}
}
# Full-text index with ngram parser.
index "index_parser" {
type = FULLTEXT
columns = [column.text]
parser = ngram
}
# Postgres-specific NULLS [NOT] DISTINCT option.
index "index_nulls_not_distinct" {
unique = true
columns = [column.text]
nulls_distinct = false
}