Collections
Collections represent groups of documents with similar structures, like tables for SQL databases. The world is chaos, but at least there's collections. Every document created in DefraDB belongs to a collection.
A collection has a name (ex. Book) and a number of typed fields (ex. title: String).
type Book {
title: String
plot: String
rating: Float
}
Field types
Int: Signed 32‐bit integer.Float(aliasFloat64): Signed double-precision floating-point value.Float32: Signed single-precision floating-point value.String: UTF‐8 character sequence.Boolean:trueorfalse.ID: Unique identifier.DateTime: ISO-8601 time (ex.2017-07-23T03:46:56.647Z).JSON: JSON data (ex.{ privacy: { is: "sexy" } }). Query filters extend to JSON inner properties if the field is indexed.Blob: Hex string (ex.00FF).- List: Array of another type (ex.
[String]). Lists can not be nested.
Non-null fields
An exclamation mark ! after a type (ex. Int!) specifies that it should be non-null. Also supported with lists:
Int!– Non-nullInt.[Int!]– List of non-nullInt.[Int]!– Non-null list of (possibly null)Int.[Int!]!– Non-null list of non-nullInt.
Default values
Specify a default value for a field with the @default directive. The default value and the field must be of the same data type. UTC_NOW is a special value for the current timestamp.
active: Boolean @default(bool: true)
age: Int @default(int: 0)
status: String @default(string: "draft")
creation: DateTime @default(dateTime: UTC_NOW)
expiry: DateTime @default(dateTime: "2026-06-19T00:00:00Z")
meta: JSON @default(json: "{}")
thumb: Blob @default(blob: "ff0099")
Create collections
- CLI
- HTTP API
Create a collection with the CLI command defradb client collection add.
defradb client collection add '
type Book {
title: String!
plot: String
rating: Float
}
'
[
{
"Name": "Book",
"VersionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionSet": null,
"Query": null,
"PreviousVersion": null,
"Fields": [
{
"FieldID": "bafyreihqzhiz3iwro4jozp6kphq4sosg6ccoqcbiaf7rg5dmvea7aux55a",
"Name": "_docID",
"Kind": 1,
"Typ": 0,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibxx5wzp4iagt3jifid2r7hfzvbtzp2fuq26vku6t6ptk3ppwgxl4",
"Name": "plot",
"Kind": 11,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibbxpehr5radbbkkmsau5uuscoif4dxu6j3ef4by6f445fyx7pl3y",
"Name": "rating",
"Kind": 6,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreifhl4p32tbcum4353gigaz7cqrribrgxlbzbps7ec24i5ydxoxewm",
"Name": "title",
"Kind": 26,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
}
],
"Indexes": [],
"EncryptedIndexes": [],
"Policy": null,
"IsActive": true,
"IsMaterialized": true,
"IsBranchable": false,
"IsEmbeddedOnly": false,
"IsPlaceholder": false,
"VectorEmbeddings": []
}
]
Create a collection by submitting a POST request to the HTTP endpoint /collections.
POST http://localhost:9181/api/v1/collections HTTP/2
accept: application/json
content-type: text/plain
type Book {
title: String!
plot: String
rating: Float
}
[
{
"Name": "Book",
"VersionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionSet": null,
"Query": null,
"PreviousVersion": null,
"Fields": [
{
"FieldID": "bafyreihqzhiz3iwro4jozp6kphq4sosg6ccoqcbiaf7rg5dmvea7aux55a",
"Name": "_docID",
"Kind": 1,
"Typ": 0,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibxx5wzp4iagt3jifid2r7hfzvbtzp2fuq26vku6t6ptk3ppwgxl4",
"Name": "plot",
"Kind": 11,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibbxpehr5radbbkkmsau5uuscoif4dxu6j3ef4by6f445fyx7pl3y",
"Name": "rating",
"Kind": 6,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreifhl4p32tbcum4353gigaz7cqrribrgxlbzbps7ec24i5ydxoxewm",
"Name": "title",
"Kind": 26,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
}
],
"Indexes": [],
"EncryptedIndexes": [],
"Policy": null,
"IsActive": true,
"IsMaterialized": true,
"IsBranchable": false,
"IsEmbeddedOnly": false,
"IsPlaceholder": false,
"VectorEmbeddings": []
}
]
Relationships
To create a relationship between two types, define a field having the other side of the relationship as type. The way in which you define relationships depends on their kind:
- One-to-one – Each document of type
Ais linked to one document of typeB, and viceversa. - One-to-many – Each document of type
Ais linked to one document of typeB. Each document of typeBis linked to one or more documents of typeA. - Many-to-many – Each document of type
Ais linked to one or more documents of typeB, and viceversa.
This section shows how to create relationships. For information on how to populate and query them, see Create documents with relationships and Query the database.
As all other fields, relationship fields can be null. For example, defining a one-to-one relationship doesn't guarantee that each document of type A will be linked to a document of type B: a document can leave the relationship undefined.
One-to-one
One-to-one relationships are such that each document of type A is linked to one and only one document of type B.
In practice, type A defines a field of type B, and type B defines a field of type A.
For example, each Husband is married to one Wife and viceversa. At least disregarding avant-garde polyamorous relationships.
type Husband {
name: String
wife: Wife
}
type Wife {
name: String
husband: Husband @primary
}
The type holding the @primary directive stores a direct pointer to the other end of the relationship, resulting in faster queries. @primary fields also get automatically indexed. In the example above, Wife contains a (implicit) field _husbandID, so retrieving a wife's husband is quick. On the other hand, documents of type Husband do not contain any pointer to the relative Wife, so a collection scan is needed to retrieve a husband's wife. Wifes are just harder to find. Which side should have the @primary directive depends on your query patterns.
One-to-one relationships are enforced via unique indexes under the hood. The index must not be dropped, or the 1:1 nature of the relationship will not be fulfilled anymore.
There's no validation on the type when creating relationships across documents. It is the client's responsibility to validate that the Wife.husbandID is populated with the docID of a Husband document. It's up to you to marry humans.
One-to-many
One-to-many relationships link one document of type A with one document of type B, but allows documents of type B to be linked to multiple documents of type A. In practice, type A defines a field of type B, whereas type B defines a field of type [A] (list of A).
For example: each book has one author, whereas one person can author multiple books:
type Book {
title: String!
author: Person
}
type Person {
name: String!
authoredBooks: [Book]
}
There's no validation on the type when creating relationships across documents. It is the client's responsibility to validate that the Book.authorID is populated with the docID of a Person document.
Many-to-many
Many-to-many relationships link multiple documents of one type to multiple documents of another type, allowing the same on the other side. To create many-to-many relationships, use two one-to-many relationships and a join type.
For example: a student can enroll in many courses, and a course can have many students enrolled:
type Student {
name: String!
age: Int
enrollment: [Enrollment]
}
type Course {
title: String!
code: String!
enrollment: [Enrollment]
}
type Enrollment { # the join type
student: Student!
course: Course!
}
Multiple relationships of same type
A type defining multiple relationships to the same type requires extra directives to disambiguate their targets. For example, in the scenario in which a book has both an author and a reviewer, the following definitions would be ambiguous:
type Book {
title: String
author: Person
reviewer: Person
}
type Person {
name: String
authoredBooks: [Book]
reviewedBooks: [Book]
}
At query time, the database cannot infer whether Person.authoredBooks is linked to Book.author or Book.reviewer. To clarify which fields should get paired, use the @relation(name: String) directive, coupling each relationship together with the same name:
type Book {
title: String
author: Person @relation(name: "author")
reviewer: Person @relation(name: "reviewer")
}
type Person {
name: String
authoredBooks: [Book] @relation(name: "author")
reviewedBooks: [Book] @relation(name: "reviewer")
}
Show collections
- CLI
- HTTP API
To see all collections available on an instance, use the CLI command defradb client collection describe.
defradb client collection describe
Use the --name parameter to restrict the output to a specific collection.
To see all collections available on an instance, submit a GET request to the HTTP endpoint /collections.
GET http://localhost:9181/api/v1/collections HTTP/2
accept: application/json
Use the name parameter to restrict the output to a specific collection.
[
{
"Name": "Book",
"VersionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionID": "bafyreiasg2fk5b4jmqzoajyjqhmd2m2jmnzlvqnar6llqs5hu34uyh2tdy",
"CollectionSet": null,
"Query": null,
"PreviousVersion": null,
"Fields": [
{
"FieldID": "bafyreihqzhiz3iwro4jozp6kphq4sosg6ccoqcbiaf7rg5dmvea7aux55a",
"Name": "_docID",
"Kind": 1,
"Typ": 0,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibxx5wzp4iagt3jifid2r7hfzvbtzp2fuq26vku6t6ptk3ppwgxl4",
"Name": "plot",
"Kind": 11,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreibbxpehr5radbbkkmsau5uuscoif4dxu6j3ef4by6f445fyx7pl3y",
"Name": "rating",
"Kind": 6,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
},
{
"FieldID": "bafyreifhl4p32tbcum4353gigaz7cqrribrgxlbzbps7ec24i5ydxoxewm",
"Name": "title",
"Kind": 26,
"Typ": 1,
"RelationName": null,
"IsPrimary": false,
"DefaultValue": null,
"Size": 0
}
],
"Indexes": [],
"EncryptedIndexes": [],
"Policy": null,
"IsActive": true,
"IsMaterialized": true,
"IsBranchable": false,
"IsEmbeddedOnly": false,
"IsPlaceholder": false,
"VectorEmbeddings": []
}
]
Truncate collections
Truncating a collection means deleting all documents belonging to it, including their histories. It's an irreversible operation that clears the collection's contents entirely.
- CLI
- HTTP API
Truncate a collection with the CLI command defradb client collection truncate.
defradb client collection truncate --name Book
Truncate a collection by submitting a DELETE request to the HTTP endpoint /collections/<name>, where <name> is a collection name.
DELETE http://localhost:9181/api/v1/collections/Book HTTP/2
accept: application/json
content-type: application/json
Delete collections
The delete command takes one (or more) collection name and erases their schema from the database. Collections linked together through relationships must be deleted together.
- CLI
- HTTP API
Delete a collection with the CLI command defradb client collection delete, providing a comma-separated list of collection names.
defradb client collection delete Book,Person
Delete a collection by submitting a DELETE request to the HTTP endpoint /collections/, providing a comma-separated list of collection names in the name parameter.
DELETE http://localhost:9181/api/v1/collections?name=Book,Person HTTP/2
accept: application/json
You can only delete empty collections. If a collection has data, or has had data (and thus has history), you need to truncate it first. Deleting documents is not equivalent to truncating the collection.