C3 AI Documentation Home

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.

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.

FieldFunctionKV storeRelational databaseDefault behaviorExample
datastoreDetermines 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.
domainName of the Db.Domain where data is produced and persisted.Default Db.Domain for the application@db(domain="primary") specifies the primary data domain.
partitionKeyFieldRequired 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.
partitionKeyBinDefines 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.
defaultPartitionStrategyDefines 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 appliedSee PartitionBucketStrategy for detailed configuration options.
persistenceOrderOrder 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.
persistDuplicatesDetermines whether duplicate objects are kept within a partition.false - duplicates are removed@db(persistDuplicates=false) prevents duplicate rows in the partition.
compactTypeEnables compact storage format for high-volume KV Types.false - Meta field is persisted@db(compactType=true) omits the Meta field for storage efficiency.
uniqueEnforces a uniqueness constraint on fields.No uniqueness constraints enforced@db(unique=['email', 'username']) ensures unique email and username combinations.
shortIdGenerates 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.
shortIdReservationRangeThe number of monotonically increasing short IDs a working thread reserves when writing to the databaseDefault reservation range applied@db(shortId=true, shortIdReservationRange=1000)
columnarStorageDetermines 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 persistenceOrder and partitionKeyField fields.
  • When you specify a partitionKeyField on a Type, the Type must mix Partitionable.
  • If you set unique field value, also include any non-default persistenceOrder fields in the unique field.
  • Use primitive values for the persistenceOrder field. Do not use C3 AI objects. For example, use timestamp and 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:

Type
@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:

Type
@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:

  1. Deploy your changes: Provision a new package version for any schema or server-side behavior changes to take effect.
  2. Create test data: Ensure you have records that will clearly demonstrate the effect of your change.
  3. Run the relevant function: This could be a fetch, create, or remove operation.
  4. 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:

Type
/**
* 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:

JavaScript
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:

  1. Creates a MyParent instance and 2 MyChild instances named "Alpha" and "Zulu".
  2. Fetches the parent and its children collection.
  3. Verifies that the fetch operation returns "Zulu" as the first child.

Validate a schema-changing property

This example annotation uses the unique property to prevent duplicate records:

Type
/**
* 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:

JavaScript
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:

  1. Creates one instance of MyUniqueType.
  2. Attempts to create a second instance with the same uniqueName.
  3. 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:

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:

JavaScript
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:

  1. Creates and remove()s an instance.
  2. Confirms a normal get() call fails to retrieve the object.
  3. Uses unremove() to restore the object.
  4. Confirms a subsequent get() call successfully retrieves the object.

See also

Was this page helpful?