About the Database Annotation
Use the @db annotation to define how the C3 Agentic AI Platform stores, indexes, and manages persisted data in a database. The annotation has configurable properties defined in the Ann.Db Type.
If you add the @db annotation to a Type that contains mixin Types, you do not need to add the annotation to the mixed Types. The mixed Types inherit the annotation and its fields from the parent Type.
Database annotation fields
The following list provides information about configurable @db annotation fields. Set these fields to determine how the platform persists data. The table indicates the field name, its function, whether it applies to KV store or relational database, default behavior of not specified, and an example setting.
| Field | Function | KV store | Relational database | Default behavior | Example |
|---|---|---|---|---|---|
datastore | Determines where the platform persists data for an entity Type. This field can enable the key-value (KV) store data storage. and external Types can use SQL or KV store. If you omit this field, the platform defaults to a relational database, PostgreSQL. | ✓ | ✓ | Defaults to PostgreSQL relational database | @db(datastore="kv") stores entity Type data to a KV store. |
domain | Name of the Db.Domain where data is produced and persisted. | ✓ | ✓ | Default Db.Domain for the application | @db(domain="primary") specifies the primary data domain. |
partitionKeyField | Required for KV store. Name of the partition (row or set of rows in a KV store) where the platform persists data. If you do not specify this field, the KV store uses ID-based partitioning and disables packing. If you specify a partitionKeyField on a Type, the Type must mix Partitionable. | ✓ | ID-based partitioning with packing disabled | @db(datastore="kv", partitionKeyField="sensorId") partitions data by sensor ID. | |
partitionKeyBin | Defines an expression that increases granularity within a partition that stores data cross multiple rows. This field enables multi-row partitions and Cassandra Composite Keys (CCK). To define this field, you must define a partitionKeyField. | ✓ | No partition key binning | @db(partitionKeyBin="concat(year(timestamp), '-', month(timestamp))") bins data by year-month. | |
defaultPartitionStrategy | Defines default behavior for the C3 AI packing process, including data time range in hot and cold storage, the number of objects per bucket and more. | ✓ | Default packing strategy applied | See PartitionBucketStrategy for detailed configuration options. | |
persistenceOrder | Order specification for persisting objects within a partition key. See the following section for information on how to use this field. | ✓ | Required field. Usually inherited through mixin. | @db(persistenceOrder='start, end') sorts Observation rows by time. | |
persistDuplicates | Determines whether duplicate objects are kept within a partition. | ✓ | ✓ | false - duplicates are removed | @db(persistDuplicates=false) prevents duplicate rows in the partition. |
compactType | Enables compact storage format for high-volume KV Types. | ✓ | ✓ | false - Meta field is persisted | @db(compactType=true) omits the Meta field for storage efficiency. |
unique | Enforces a uniqueness constraint on fields. | ✓ | ✓ | No uniqueness constraints enforced | @db(unique=['email', 'username']) ensures unique email and username combinations. |
shortId | Generates readable, monotonically increasing IDs instead of UUIDs. | ✓ | ✓ | false - UUIDs are generated | @db(shortId=true, shortIdPrefix="WO-", shortIdReservationRange=1000) generates IDs like WO-1, WO-2. |
shortIdReservationRange | The number of monotonically increasing short IDs a working thread reserves when writing to the database | ✓ | ✓ | Default reservation range applied | @db(shortId=true, shortIdReservationRange=1000) |
columnarStorage | Determines whether data is stored in a compressed columnar format. Typically for time series data. | ✓ | false - standard row-based storage | @db(columnarStorage=true) enables columnar compression for analytics workloads. |
To view all configurable properties in the @db annotation, see the Ann.Db Type.
To learn about relational database and KV store options, see Specify a KV Store.
persistenceOrder field guidelines
Follow these guidelines when you use the persistenceOrder field in the @db annotation:
- Specify a value for the
persistenceOrderandpartitionKeyFieldfields. - When you specify a
partitionKeyFieldon a Type, the Type must mixPartitionable. - If you set
uniquefield value, also include any non-defaultpersistenceOrderfields in theuniquefield. - Use primitive values for the
persistenceOrderfield. Do not use C3 AI objects. For example, usetimestampand do not use a C3 AI Type. - The platform does not support Meta fields as persistence order on compact Types. Meta fields in persistence order causes duplicates.
Database annotation examples
The following examples demonstrate how to use the @db annotation for different purposes.
High-volume, time-series data
The following annotation serves high-volume, time-series data like sensor readings, log entries, or measurement data points:
@db(datastore='cassandra',
partitionKeyField='parent',
persistenceOrder='start',
persistDuplicates=false,
compactType=true,
shortId=true,
shortIdReservationRange=100000)
entity type WindTurbineMeasurement {
// ...
}This annotation defines the following for the WindTurbineMeasurement Type:
datastore='cassandra': Specifies the Cassandra datastore, which supports large data loads and low latency reads and writes.partitionKeyField='parent': Stores all measurements for a single wind turbine on the same row, which optimizes queries for that turbine.persistenceOrder='start': Sorts measurements within a partition by time, and allows for fast time-range queries.compactType=true&shortId=true: Minimizes the overall storage footprint.persistDuplicates=false: Prevents the same measurement from being ingested twice to ensure data quality.
Search-optimized data
For Types like User, PurchaseOrder, or a custom Project Type, you might prioritize data integrity, searchability, and relational capabilities more than write speed. For example:
@db(unique=['purchaseOrderNumber'], index+=['customer', 'orderDate'])
entity type PurchaseOrder {
purchaseOrderNumber: !string,
customer: !Customer,
orderDate: !date
// ...
}This annotation omits the datastore field, so the Type uses the default relational database PostgreSQL.
This annotation defines the following for the PurchaseOrder Type:
unique=['purchaseOrderNumber']: Enforces the rule that every purchase order must have a unique number.index+=['customer', 'orderDate']: Creates indexes to optimize queries for customer or order date data.
Test and validate the annotation
After you apply a @db annotation, verify that it works as expected. The following examples demonstrate how to test various fields on the Ann.Db Type.
Prerequisites
To test your changes, you need a C3 AI development environment. For more information, see Set up a single node environment.
Complete the following workflow to test the annotation:
- Deploy your changes: Provision a new package version for any schema or server-side behavior changes to take effect.
- Create test data: Ensure you have records that will clearly demonstrate the effect of your change.
- Run the relevant function: This could be a
fetch,create, orremoveoperation. - Assert the outcome: Check that the result of the function call matches your expectation.
The following sections provide example @db annotations with different properties, and steps to validate each annotation.
Validate a query-time property
This example annotation uses the order property to sort a collection name in descending order:
/**
* A parent entity that contains a collection of MyChild objects,
* ordered by the child's name in descending order at the database level.
*/
entity type MyParent {
/**
* A unique identifier for the parent.
*/
id: !string,
/**
* A collection of child objects. The @db(order=...) annotation
* instructs the database to always return this collection sorted
* by the 'name' field of the MyChild objects, in descending order.
* The '(parent)' part defines the foreign key relationship.
*/
@db(order='descending(name)')
children: [MyChild](parent)
}Run the following code in C3 AI Console to validate the annotation:
var parent = MyParent.make({ id: 'parent-1' }).create();
MyChild.make({ id: 'child-a', parent: parent, name: 'Alpha' }).create();
MyChild.make({ id: 'child-z', parent: parent, name: 'Zulu' }).create();
var fetchedParent = MyParent.get('parent-1', 'children');
var firstChildName = fetchedParent.children.first().name;
console.log('First child name:', firstChildName);
// Expected output: "Zulu"This code does the following:
- Creates a
MyParentinstance and 2MyChildinstances named "Alpha" and "Zulu". - Fetches the parent and its
childrencollection. - Verifies that the
fetchoperation returns "Zulu" as the first child.
Validate a schema-changing property
This example annotation uses the unique property to prevent duplicate records:
/**
* An entity that demonstrates a unique constraint. The @db(unique=...)
* annotation ensures that no two instances of this Type can have the
* same value in the 'uniqueName' field.
*/
@db(unique=['uniqueName'])
entity type MyUniqueType {
/**
* The system-generated primary identifier for the object.
*/
id: !string,
/**
* This field must have a unique value across all instances of
* MyUniqueType. The database will enforce this constraint.
*/
uniqueName: !string
}Run the following code in C3 AI Console to validate the annotation:
try {
MyUniqueType.make({ id: 'A', uniqueName: 'test-name' }).create();
console.log('First object created successfully.');
MyUniqueType.make({ id: 'B', uniqueName: 'test-name' }).create(); // This line should fail
console.error('Test Failed: Unique constraint was not enforced.');
} catch (e) {
// The expected outcome is to catch an exception.
// Inspect 'e.message' to confirm it is a unique constraint violation.
console.log('Test Passed: Caught expected exception: ' + e.message);
}This code does the following:
- Creates one instance of
MyUniqueType. - Attempts to create a second instance with the same
uniqueName. - Confirms the second
create()call fails and throws an exception.
Validate a write-time property
This example annotation uses the archive property to enable soft-deletes for a Type:
/**
* An entity that demonstrates the archive-on-remove behavior.
* When @db(archive=true) is set, instances of this Type are not
* physically deleted when the remove() function is called. Instead,
* they are serialized and moved to a dedicated archive table, from
* which they can be recovered later using the unremove() function.
*/
@db(archive=true)
entity type MyArchivableType {
/**
* A unique identifier for the object.
*/
id: !string,
/**
* A simple data field to demonstrate that the entire object's
* state is preserved during archiving.
*/
name: string
}Run the following code in C3 AI Console to validate the annotation:
var obj = MyArchivableType.make({id: 'archive-test'}).create();
obj.remove();
var afterRemove = MyArchivableType.get('archive-test');
console.assert(!afterRemove, 'Object should be gone after removal.');
MyArchivableType.make({id: 'archive-test'}).unremove();
var afterUnremove = MyArchivableType.get('archive-test');
console.assert(afterUnremove, 'Object should be restored after unremove.');
console.log('Archive and unremove test passed.');This code does the following:
- Creates and
remove()s an instance. - Confirms a normal
get()call fails to retrieve the object. - Uses
unremove()to restore the object. - Confirms a subsequent
get()call successfully retrieves the object.