Skip to content
Ocean-Atlasv0.1.0Canonical Knowledge

Database DSL Reference

The @database section defines one or more databases.

Each database contains entities that map to existing datatypes and may define:

  • database type and engine;
  • configuration;
  • entity relationships;
  • keys;
  • indexes and constraints;
  • encrypted fields;
  • queries;
  • commands.

Entity fields are defined in @datatype. The @database section adds persistence-specific information to those datatypes.

A database file starts with:

@database

A file may define multiple Database blocks.


@database
Database <DatabaseName>
type = <databaseType>
engine = <engineName>
configType = <ConfigRef>
tags = <tag1>, <tag2>, ...
encryptionKey = <KeySpec>
Entity <EntityName>
<fieldName> <linkType> <TargetEntity> [<modifiers>]
key(<field1>, <field2>, ...)
indexes: <indexDefinition>, <indexDefinition>, ...
encrypt: <field1>, <field2>, ...
query <queryName>(<parameters>) : <ReturnType>
command [T|Transactional] <commandName>(<parameters>) : <ReturnType>

Only applicable attributes and entity definitions need to be declared.


A Database block defines a logical database and its persistence configuration.

Example:

@database
Database TodoDB
engine = postgres
configType = DatabaseConfig
encryptionKey = generate
tags = primary
Entity TodoItem
key(id)
indexes: unique(title), index(title), index(dueDate)
encrypt: secret1, secret2
# Queries
query listTodoItems(offset:Int, limit:Int) : List<TodoItem>
query findById(id: String) : TodoItem
query findByTitle(title: String) : List<TodoItem>
query findByTitleAndPriority(title: String, priority: Priority) : List<TodoItem>
query findByTitleOrPriority(title: String, priority : Priority) : List<TodoItem>
# Commands
command Transactional createTodoItem(item:TodoItem) : TodoItem
command T updateTodoItem(item:TodoItem) : TodoItem
command deleteTodoItem(id:String) : _
Attribute Required Description
type No Logical database type. Defaults to transactional.
engine Yes Database engine, such as postgres, mysql, mongodb, or neo4j.
configType Yes Reference to the configuration type used by the database.
tags No Metadata tags used for classification or processing.
encryptionKey Conditional Defines the encryption key source. Required when an entity uses encrypt:.

The optional type attribute identifies the logical type of database.

Supported values are:

  • transactional
  • in-memory
  • document
  • graph
  • timeseries
  • analytical

The default is:

type = transactional

Example:

Database MainDB
type = transactional
engine = postgres
configType = DatabaseConfig

The type describes the logical storage model, while engine identifies the concrete database technology.

For type = in-memory databases, entities receive a standard generated set of CRUD operations regardless of the specific engine — see 10.4 Implicit CRUD for In-Memory Entities.


An Entity maps an existing datatype into a database.

Syntax:

Entity <EntityName>

The entity name must match an existing datatype name.

For example, given:

@datatype
TodoItem
id : String
title : String
dueDate : Date
secret1 : String
secret2 : String

the corresponding database entity may be:

Entity TodoItem
key(id)
indexes: unique(title), index(title), index(dueDate)
encrypt: secret1, secret2

Fields must not be declared again inside the entity.

The datatype defines the data structure. The entity defines its persistence behavior.


Entities may define relationships to other entities.

Each relationship is defined on one line:

<fieldName> <linkType> <TargetEntity> [<modifiers>]

Example:

buyer -m2o-> Party [index, not-null]
Type Syntax Description
One-to-One -o2o-> One-to-one relationship
One-to-Many -o2m-> One-to-many relationship
Many-to-One -m2o-> Many-to-one relationship
Many-to-Many -m2m-> Many-to-many relationship

Example:

Entity PurchaseOrder
buyer -m2o-> Party [index, not-null]
item -m2o-> Item
Modifier Description
not-null The relationship must not be null.
unique The relationship must have a unique value where applicable.
index The relationship should be indexed.

Multiple modifiers are declared inside brackets:

buyer -m2o-> Party [index, not-null]

Entity references use ID-based mapping by default. No explicit store strategy is required.


The key(...) declaration defines the key of an entity.

Syntax:

key(<field1>, <field2>, ...)

A single-field key:

key(id)

A composite key:

key(code, version)

The referenced fields must exist in the datatype backing the entity.

Example:

Entity PurchaseOrder
key(code, version)

Indexes and uniqueness constraints are declared using indexes:.

All index definitions for an entity are declared on one line.

indexes: <indexDefinition>, <indexDefinition>, ...

Example:

indexes: unique(title), index(title), index(dueDate)

Supported definitions include:

Definition Description
unique(field) Defines a uniqueness constraint or unique index on a single field.
index(field) Defines an index for a single field.
unique(field1, field2, ...) Defines a composite uniqueness constraint over the listed fields.
index(field1, field2, ...) Defines a composite index over the listed fields, in the given order.

Single-field example:

Entity TodoItem
indexes: unique(title), index(title), index(dueDate)

Composite example:

Entity AuditAction
indexes: index(actorId, occurredAt, id), index(tenantId, occurredAt, id)
Entity AccessRequest
indexes: unique(subjectId, requestId), index(subjectId), index(timestamp)

Field order in a composite definition is significant and is preserved in the generated index.

All referenced fields must exist in the datatype backing the entity.


Entity fields may be stored in encrypted form.

Encryption consists of:

  1. encryptionKey at database level;
  2. encrypt: at entity level.

All encrypted fields for an entity are declared on one line.

Syntax:

encrypt: <field1>, <field2>, ...

Example:

encrypt: secret1, secret2

The fields must exist in the datatype backing the entity.


If any entity uses encrypt:, the database must define encryptionKey.

Example:

Database TodoDB
engine = postgres
configType = DatabaseConfig
encryptionKey = generate
Entity TodoItem
encrypt: secret1, secret2

The supported key specifications are:

generate
env:<ENV_VAR_NAME>
file:<PATH>
vault:<SECRET_PATH>
vault:<SECRET_PATH>#<FIELD>
literal:<BASE64_32B_LITERAL>
Key Specification Description
generate Generates an encryption key.
env:<ENV_VAR_NAME> Reads the key from an environment variable.
file:<PATH> Reads the key from a file.
vault:<SECRET_PATH> Reads the key from a vault secret.
vault:<SECRET_PATH>#<FIELD> Reads a specific field from a vault secret.
literal:<BASE64_32B_LITERAL> Uses a Base64-encoded 32-byte literal key.

Examples:

encryptionKey = generate
encryptionKey = env:DATABASE_ENCRYPTION_KEY
encryptionKey = file:/run/secrets/database-key
encryptionKey = vault:secret/database#encryptionKey
  • encryptionKey is required when any entity uses encrypt:.
  • Fields listed in encrypt: must exist in the backing datatype.
  • Encryption affects persistence and does not change the logical datatype of the field.
  • The concrete encryption implementation depends on the generated target technology.
  • Secure external key sources should be preferred over embedding literal secrets directly in DSL files.

Entities may define inline queries and commands.

Each query or command is defined on exactly one line.


Queries define read operations.

Syntax:

query <name>(<parameters>) : <ReturnType>

Examples:

query listTodoItems(offset:Int, limit:Int) : List<TodoItem>
query findById(id: String) : TodoItem
query findByTitle(title: String) : List<TodoItem>
query findByTitleAndPriority(title: String, priority: Priority) : List<TodoItem>
query findByTitleOrPriority(title: String, priority : Priority) : List<TodoItem>

Parameters and return values use Ocean datatypes. They may also reference pointer datatypes, both directly and inside collections:

query findById(id: String) : *Invoice
query findByStatus(status: InvoiceStatus) : List<*Invoice>

Commands define operations that modify persistent state.

Syntax:

command [T|Transactional] <name>(<parameters>) : <ReturnType>

Examples:

command Transactional createTodoItem(item:TodoItem) : TodoItem
command T updateTodoItem(item:TodoItem) : TodoItem
command deleteTodoItem(id:String) : _

A command may optionally include a command modifier before its name.

Examples from the current DSL include:

  • Transactional
  • T

The _ return type indicates that the command does not return a meaningful result value.

Command parameters and return values may reference pointer datatypes in the same way as queries:

command T updateInvoice(item: *Invoice) : *Invoice

A command whose name follows increase<Field> or decrease<Field> atomically adjusts the corresponding numeric entity field by one.

For example, given an AccessMatrix datatype with an Int field named version:

Entity AccessMatrix
command increaseVersion(id:String) : Int
command decreaseVersion(id:String) : Int

increaseVersion resolves the Version suffix to the version field, increments its stored value by one, and returns the resulting value. decreaseVersion resolves the same field, decrements its stored value by one, and returns the resulting value.

  • The command name must contain a field suffix after increase or decrease.
  • The resolved field must exist in the datatype backing the entity.
  • The resolved field must have a supported numeric type.
  • The command must accept exactly one identifying parameter.
  • The return type must match the adjusted field type.
  • Pointer, collection, and non-numeric fields cannot be adjusted this way.

Invalid adjustment commands are rejected during model validation rather than producing invalid generated code.

The adjustment is atomic. Concurrent calls must each apply exactly one adjustment and return the value resulting from that adjustment. Implementations must not use an unsafe read-modify-write sequence that can lose concurrent updates.

An explicit T or Transactional modifier is not required when the adjustment is used alone. It may be declared when the command participates in a larger workflow whose other database changes must commit or roll back together:

command T increaseVersion(id:String) : Int

Every entity in a type = in-memory database automatically receives a standard set of generated CRUD operations. These operations do not need to be declared with query or command — they are generated per entity regardless of the concrete in-memory engine (redis or any other in-memory engine).

Generated operations:

Signature Kind Description
Put<EntityName>(entity: <EntityName>) : String Command Stores the entity, generating a new ID; returns the generated ID.
Put<EntityName>WithId(id: String, entity: <EntityName>) : _ Command Stores the entity under a caller-supplied ID, overwriting any existing value.
Put<EntityName>WithTtl(entity: <EntityName>, ttl: Int) : String Command Stores the entity with a generated ID and an expiration; returns the generated ID.
Put<EntityName>WithIdAndTtl(id: String, entity: <EntityName>, ttl: Int) : _ Command Stores the entity under a caller-supplied ID with an expiration, overwriting any existing value.
Exists<EntityName>(id: String) : Boolean Query Checks whether an entity exists for the given ID.
Get<EntityName>(id: String) : <EntityName> Query Retrieves the entity by ID without removing it. Fails if no entity exists for the ID.
Delete<EntityName>(id: String) : _ Command Removes the entity by ID. Idempotent.
Pop<EntityName>(id: String) : <EntityName> Command Atomically retrieves and removes the entity by ID. Fails if no entity exists for the ID.

<EntityName> refers to the entity’s datatype. Fields listed as id are the entity’s identifier, independent of any key(...) declared on the entity. Get<EntityName> and Pop<EntityName> fail when no entity exists for the given ID; Delete<EntityName> is idempotent and does not fail when the ID does not exist.

An entity may still declare additional custom query and command operations alongside the implicit generated set.


The following is a complete @database file (ocean-examples/0002-inv-mgt/20-inv-db.ocn):

@database
Database InvDb
engine = postgres
configType = InvMgtDatabaseConfig
tags = primary
# ---------------------------------------------------
# InvItem Entity
# ---------------------------------------------------
Entity InvItem
key(id)
indexes: index(status), index(locationId), index(batchId), index(expiryDate)
# Relations
location -m2o-> Location
# Queries
query listInvItems(offset:Int, limit:Int) : List<InvItem>
query findById(id: String) : InvItem
query findByStatus(status: ItemStatus) : List<InvItem>
query findByLocationId(locationId: String) : List<InvItem>
query findByMaterialType(materialType: MaterialType) : List<InvItem>
query findByBatchId(batchId: String) : List<InvItem>
query findByExpiryDateBefore(expiryDate: DateTime) : List<InvItem>
query findByLocationAndName(locationId: String, name: String) : List<InvItem>
query findByLocationAndBatchId(locationId: String, batchId: String) : List<InvItem>
# Commands
command T createInvItem(item: InvItem) : InvItem
command T updateInvItem(item: InvItem) : InvItem
command deleteInvItem(id: String) : _
# ---------------------------------------------------
# Location Entity
# ---------------------------------------------------
Entity Location
key(id)
indexes: unique(name), index(site), index(warehouse)
# Queries
query listLocations(offset:Int, limit:Int) : List<Location>
query findById(id: String) : Location
query findByName(name: String): List<Location>
query findBySite(site: String): List<Location>
query findByWarehouse(warehouse: String): List<Location>
# Commands
command T createLocation(item: Location) : Location
command T updateLocation(item: Location) : Location
command deleteLocation(id: String) : _
# ---------------------------------------------------
# ItemHistoryRecord Entity
# ---------------------------------------------------
Entity ItemHistoryRecord
key(id)
indexes: index(itemId), index(eventType), index(timestamp)
# Queries
query findByItemId(itemId: String) : List<ItemHistoryRecord>
query findByItemIdAndEventType(itemId: String, eventType: ItemEventType) : List<ItemHistoryRecord>
query listAll(offset:Int, limit:Int) : List<ItemHistoryRecord>
# Commands
command T createItemHistoryRecord(record: ItemHistoryRecord) : ItemHistoryRecord

This example demonstrates:

  • a database definition;
  • a concrete database engine (postgres);
  • configuration through configType;
  • database tags;
  • multiple entity mappings in one database;
  • single-field keys;
  • inline single-field indexes and a uniqueness constraint;
  • a many-to-one entity relationship;
  • inline queries, including multi-parameter finders;
  • inline commands with the T (transactional) modifier;
  • _ as an empty result type.

For encryption (encryptionKey plus entity-level encrypt:), see Section 9; for composite indexes and type = in-memory implicit CRUD, see Sections 8 and 10.4.


The @database section does not directly import entities from Ocean.

All entities must be backed by datatypes that are already available to the DSL context.

Datatypes may be:

  • defined locally in @datatype;
  • included through the supported include mechanism;
  • imported through the @datatype import mechanism.

For example:

@datatype
@import datatype O.domain.payment.CardInfo@1.2.0 as CardInfo
Payment
card : CardInfo

The database may then define an entity for an available datatype where required.

This keeps datatype definition and reuse within @datatype, while @database remains responsible for persistence concerns.

12.1 Including a Published Database Module

Section titled “12.1 Including a Published Database Module”

A @database file may also consist solely of an @include that pulls a complete, published database definition from the Ocean Repository:

@database
@include O.rbac.database@1.0.0

The included module is resolved as if its Database blocks were declared inline.


The following rules apply:

  • A database file starts with @database.
  • A file may contain multiple Database blocks.
  • engine is required.
  • configType is required.
  • type is optional and defaults to transactional.
  • tags is optional.
  • encryptionKey is required when encrypted entity fields are declared.
  • Each entity must map to an existing datatype.
  • Fields must be declared in the corresponding datatype and must not be redeclared in the entity.
  • Fields referenced by key(...), indexes:, encrypt:, or relationships must exist in the corresponding datatype.
  • Entity references use ID-based mapping by default.
  • indexes: definitions are declared on one line.
  • encrypt: definitions are declared on one line.
  • Each relationship is declared on one line.
  • Each query is declared on one line.
  • Each command is declared on one line.
  • increase<Field> and decrease<Field> commands must satisfy the atomic numeric field-adjustment rules in Section 10.3.
  • Entities in a type = in-memory database receive a standard generated CRUD operation set per Section 10.4, in addition to any explicitly declared queries and commands.
  • Entities cannot be imported directly through @database.

The @database DSL is related to:

  • dsl.datatype — defines the datatypes used as database entities.
  • dsl.config — defines configuration referenced through configType.
  • dsl.vault — may provide encryption keys using the vault: key specification.
  • dsl.import — defines the mechanism for importing selected definitions from the Ocean Repository.
  • dsl.include — defines the mechanism for including reusable definitions from the Ocean Repository.

These semantic relationships are declared in the document metadata.