Directives define specific behaviors that can be applied to fields or types within a GraphQL schema.
Data Connect Defined
@auth on QUERY
| MUTATION
The @auth
directive defines the authentication policy for a query or mutation.
It must be added to any operation that you wish to be accessible from a client
application. If not specified, the operation defaults to @auth(level: NO_ACCESS)
.
Refer to Data Connect Auth Guide for the best practices.
Argument | Type | Description |
---|---|---|
level |
AccessLevel |
The minimal level of access required to perform this operation. Exactly one of level and expr should be specified. |
expr |
Boolean_Expr |
A CEL expression that grants access to this operation if the expression evaluates to true . Exactly one of level and expr should be specified. |
@check on QUERY
| MUTATION
| FIELD
| FRAGMENT_DEFINITION
| FRAGMENT_SPREAD
| INLINE_FRAGMENT
Ensure this field is present and is not null or []
, or abort the request / transaction.
A CEL expression, expr
is used to test the field value. It defaults to
rejecting null and []
but a custom expression can be provided instead.
If the field occurs multiple times (i.e. directly or indirectly nested under a
list), expr
will be executed once for each occurrence and @check
succeeds if
all values succeed. @check
fails when the field is not present at all (i.e.
all ancestor paths contain null
or []
), unless optional
is true.
If a @check
fails in a mutation, the top-level field containing it will be
replaced with a partial error, whose message can be customzied via the message
argument. Each subsequent top-level fields will return an aborted error (i.e.
not executed). To rollback previous steps, see @transaction
.
Argument | Type | Description |
---|---|---|
expr |
Boolean_Expr! |
The CEL expression to test the field value (or values if nested under a list). Within the CEL expression, a special value this evaluates to the field that this directive is attached to. If this field occurs multiple times because any ancestor is a list, each occurrence is tested with this bound to each value. When the field itself is a list or object, this follows the same structure (including all decendants selected in case of objects). For any given path, if an ancestor is null or [] , the field will not be reached and the CEL evaluation will be skipped for that path. In other words, evaluation only takes place when this is null or non-null, but never undefined. (See also the optional argument.) |
message |
String! |
The error message to return to the client if the check fails. Defaults to "permission denied" if not specified. |
optional |
Boolean |
Whether the check should pass or fail (default) when the field is not present. A field will not be reached at a given path if its parent or any ancestor is [] or null . When this happens to all paths, the field will not be present anywhere in the response tree. In other words, expr is evaluated 0 times. By default, @check will automatically fail in this case. Set this argument to true to make it pass even if no tests are run (a.k.a. "vacuously true"). |
@col on FIELD_DEFINITION
Customizes a field that represents a SQL database table column.
Data Connect maps scalar Fields on @table
type to a SQL column of
corresponding data type.
- scalar
UUID
maps touuid
. - scalar
String
maps totext
. - scalar
Int
maps toint
. - scalar
Int64
maps tobigint
. - scalar
Float
maps todouble precision
. - scalar
Boolean
maps toboolean
. - scalar
Date
maps todate
. - scalar
Timestamp
maps totimestamptz
. - scalar
Any
maps tojsonb
. - scalar
Vector
maps topgvector
.
Array scalar fields are mapped to Postgres arrays.
Example: Serial Primary Key
For example, you can define auto-increment primary key.
type Post @table {
id: Int! @col(name: "post_id", dataType: "serial")
}
Data Connect converts it to the following SQL table schema.
CREATE TABLE "public"."post" (
"post_id" serial NOT NULL,
PRIMARY KEY ("id")
)
Example: Vector
type Post @table {
content: String! @col(name: "post_content")
contentEmbedding: Vector! @col(size:768)
}
Argument | Type | Description |
---|---|---|
name |
String |
The SQL database column name. Defaults to snake_case of the field name. |
dataType |
String |
Configures the custom SQL data type. Each GraphQL type can map to multiple SQL data types. Refer to Postgres supported data types. Incompatible SQL data type will lead to undefined behavior. |
size |
Int |
Required on Vector columns. It specifies the length of the Vector. textembedding-gecko@003 model generates Vector of @col(size:768) . |
@default on FIELD_DEFINITION
Specifies the default value for a column field.
For example:
type User @table(key: "uid") {
uid: String! @default(expr: "auth.uid")
number: Int! @col(dataType: "serial")
createdAt: Date! @default(expr: "request.time")
role: String! @default(value: "Member")
credit: Int! @default(value: 100)
}
The supported arguments vary based on the field type.
Argument | Type | Description |
---|---|---|
value |
Any |
A constant value validated against the field's GraphQL type during compilation. |
expr |
Any_Expr |
A CEL expression whose return value must match the field's data type. |
sql |
Any_SQL |
A raw SQL expression, whose SQL data type must match the underlying column. The value is any variable-free expression (in particular, cross-references to other columns in the current table are not allowed). Subqueries are not allowed either. See PostgreSQL defaults for more details. |
@index on FIELD_DEFINITION
| OBJECT
Defines a database index to optimize query performance.
type User @table @index(fields: ["name", "phoneNumber"], order: [ASC, DESC]) {
name: String @index
phoneNumber: Int64 @index
tags: [String] @index # GIN Index
}
Single Field Index
You can put @index
on a @col
field to create a SQL index.
@index(order)
matters little for single field indexes, as they can be scanned
in both directions.
Composite Index
You can put @index(fields: [...])
on @table
type to define composite indexes.
@index(order: [...])
can customize the index order to satisfy particular
filter and order requirement.
Argument | Type | Description |
---|---|---|
name |
String |
Configure the SQL database index id. If not overridden, Data Connect generates the index name: - {table_name}_{first_field}_{second_field}_aa_idx - {table_name}_{field_name}_idx |
fields |
[String!] |
Only allowed and required when used on a @table type. Specifies the fields to create the index on. |
order |
[IndexFieldOrder!] |
Only allowed for BTREE @index on @table type. Specifies the order for each indexed column. Defaults to all ASC . |
type |
IndexType |
Customize the index type. For most index, it defaults to BTREE . For array fields, only allowed IndexType is GIN . For Vector fields, defaults to HNSW , may configure to IVFFLAT . |
vector_method |
VectorSimilarityMethod |
Only allowed when used on vector field. Defines the vector similarity method. Defaults to INNER_PRODUCT . |
@redact on FIELD
| FRAGMENT_SPREAD
| INLINE_FRAGMENT
Redact a part of the response from the client.
Redacted fields are still evaluated for side effects (including data changes and
@check
) and the results are still available to later steps in CEL expressions
(via response.fieldName
).
@ref on FIELD_DEFINITION
Defines a foreign key reference to another table.
For example, we can define a many-to-one relation.
type ManyTable @table {
refField: OneTable!
}
type OneTable @table {
someField: String!
}
Data Connect adds implicit foreign key column and relation query field. So the above schema is equivalent to the following schema.
type ManyTable @table {
id: UUID! @default(expr: "uuidV4()")
refField: OneTable! @ref(fields: "refFieldId", references: "id")
refFieldId: UUID!
}
type OneTable @table {
id: UUID! @default(expr: "uuidV4()")
someField: UUID!
# Generated Fields:
# manyTables_on_refField: [ManyTable!]!
}
Data Connect generates the necessary foreign key constraint.
CREATE TABLE "public"."many_table" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"ref_field_id" uuid NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "many_table_ref_field_id_fkey" FOREIGN KEY ("ref_field_id") REFERENCES "public"."one_table" ("id") ON DELETE CASCADE
)
Example: Traverse the Reference Field
query ($id: UUID!) {
manyTable(id: $id) {
refField { id }
}
}
Example: Reverse Traverse the Reference field
query ($id: UUID!) {
oneTable(id: $id) {
manyTables_on_refField { id }
}
}
Optional Many-to-One Relation
An optional foreign key reference will be set to null if the referenced row is deleted.
In this example, if a User
is deleted, the assignee
and reporter
references will be set to null.
type Bug @table {
title: String!
assignee: User
reproter: User
}
type User @table { name: String! }
Required Many-to-One Relation
A required foreign key reference will cascade delete if the referenced row is deleted.
In this example, if a Post
is deleted, associated comments will also be
deleted.
type Comment @table {
post: Post!
content: String!
}
type Post @table { title: String! }
Many To Many Relation
You can define a many-to-many relation with a join table.
type Membership @table(key: ["group", "user"]) {
group: Group!
user: User!
role: String! @default(value: "member")
}
type Group @table { name: String! }
type User @table { name: String! }
When Data Connect sees a table with two reference field as its primary key, it knows this is a join table, so expands the many-to-many query field.
type Group @table {
name: String!
# Generated Fields:
# users_via_Membership: [User!]!
# memberships_on_group: [Membership!]!
}
type User @table {
name: String!
# Generated Fields:
# groups_via_Membership: [Group!]!
# memberships_on_user: [Membership!]!
}
Example: Traverse the Many-To-Many Relation
query ($id: UUID!) {
group(id: $id) {
users: users_via_Membership {
name
}
}
}
Example: Traverse to the Join Table
query ($id: UUID!) {
group(id: $id) {
memberships: memberships_on_group {
user { name }
role
}
}
}
One To One Relation
You can even define a one-to-one relation with the help of @unique
or @table(key)
.
type User @table {
name: String
}
type Account @table {
user: User! @unique
}
# Alternatively, use primary key constraint.
# type Account @table(key: "user") {
# user: User!
# }
Example: Transerse the Reference Field
query ($id: UUID!) {
account(id: $id) {
user { id }
}
}
Example: Reverse Traverse the Reference field
query ($id: UUID!) {
user(id: $id) {
account_on_user { id }
}
}
Customizations
@ref(constraintName)
can customize the SQL foreign key constraint name (table_name_ref_field_fkey
above).@ref(fields)
can customize the foreign key field names.@ref(references)
can customize the constraint to reference other columns. By default,@ref(references)
is the primary key of the@ref
table. Other fields with@unique
may also be referred in the foreign key constraint.
Argument | Type | Description |
---|---|---|
constraintName |
String |
The SQL database foreign key constraint name. Defaults to snake_case {table_name}_{field_name}_fkey . |
fields |
[String!] |
Foreign key fields. Defaults to {tableName}{PrimaryIdName} . |
references |
[String!] |
The fields that the foreign key references in the other table. Defaults to its primary key. |
@table on OBJECT
Defines a relational database table.
In this example, we defined one table with a field named myField
.
type TableName @table {
myField: String
}
Data Connect adds an implicit id
primary key column. So the above schema is equivalent to:
type TableName @table(key: "id") {
id: String @default(expr: "uuidV4()")
myField: String
}
Data Connect generates the following SQL table and CRUD operations to use it.
CREATE TABLE "public"."table_name" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"my_field" text NULL,
PRIMARY KEY ("id")
)
- You can lookup a row:
query ($id: UUID!) { tableName(id: $id) { myField } }
- You can find rows using:
query tableNames(limit: 20) { myField }
- You can insert a row:
mutation { tableName_insert(data: {myField: "foo"}) }
- You can update a row:
mutation ($id: UUID!) { tableName_update(id: $id, data: {myField: "bar"}) }
- You can delete a row:
mutation ($id: UUID!) { tableName_delete(id: $id) }
Customizations
@table(singular)
and@table(plural)
can customize the singular and plural name.@table(name)
can customize the Postgres table name.@table(key)
can customize the primary key field name and type.
For example, the User
table often has a uid
as its primary key.
type User @table(key: "uid") {
uid: String!
name: String
}
- You can securely lookup a row:
query { user(key: {uid_expr: "auth.uid"}) { name } }
- You can securely insert a row:
mutation { user_insert(data: {uid_expr: "auth.uid" name: "Fred"}) }
- You can securely update a row:
mutation { user_update(key: {uid_expr: "auth.uid"}, data: {name: "New Name"}) }
- You can securely delete a row:
mutation { user_delete(key: {uid_expr: "auth.uid"}) }
@table
type can be configured further with:
- Custom SQL data types for columns. See
@col
. - Add SQL indexes. See
@index
. - Add SQL unique constraints. See
@unique
. - Add foreign key constraints to define relations. See
@ref
.
Argument | Type | Description |
---|---|---|
name |
String |
Configures the SQL database table name. Defaults to snake_case like table_name . |
singular |
String |
Configures the singular name. Defaults to the camelCase like tableName . |
plural |
String |
Configures the plural name. Defaults to infer based on English plural pattern like tableNames . |
key |
[String!] |
Defines the primary key of the table. Defaults to a single field named id . If not present already, Data Connect adds an implicit field id: UUID! @default(expr: "uuidV4()") . |
@transaction on MUTATION
Require that this mutation always run in a DB transaction.
Mutations with @transaction
are guaranteed to either fully succeed or fully
fail. If any of the fields within the transaction fails, the entire transaction
is rolled back. From a client standpoint, any failure behaves as if the entire
request had failed with a request error and execution had not begun.
Mutations without @transaction
would execute each root field one after
another in sequence. It surfaces any errors as partial field errors,
but not impacts the subsequent executions.
The @transaction
directive cannot be added to queries for now.
Currently, queries cannot fail partially, the response data is not guaranteed
to be a consistent snapshot.
@unique on FIELD_DEFINITION
| OBJECT
Defines unique constraints on @table
.
For example,
type User @table {
phoneNumber: Int64 @unique
}
type UserProfile @table {
user: User! @unique
address: String @unique
}
@unique
on a@col
field adds a single-column unique constraint.@unique
on a@table
type adds a composite unique constraint.@unique
on a@ref
defines a one-to-one relation. It adds unique constraint on@ref(fields)
.
@unique
ensures those fields can uniquely identify a row, so other @table
type may define @ref(references)
to refer to fields that has a unique constraint.
Argument | Type | Description |
---|---|---|
indexName |
String |
Configures the SQL database unique constraint name. If not overridden, Data Connect generates the unique constraint name: - table_name_first_field_second_field_uidx - table_name_only_field_name_uidx |
fields |
[String!] |
Only allowed and required when used on OBJECT, this specifies the fields to create a unique constraint on. |
@view on OBJECT
Defines a relational database Raw SQLview.
Data Connect generates GraphQL queries with WHERE and ORDER BY clauses. However, not all SQL features has native GraphQL equivalent.
You can write an arbitrary SQL SELECT statement. Data Connect
would map Graphql fields on @view
type to columns in your SELECT statement.
- Scalar GQL fields (camelCase) should match a SQL column (snake_case) in the SQL SELECT statement.
- Reference GQL field can point to another
@table
type. Similar to foreign key defined with@ref
on a@table
type, a@view
type establishes a relation when@ref(fields)
match@ref(references)
on the target table.
In this example, you can use @view(sql)
to define an aggregation view on existing
table.
type User @table {
name: String
score: Int
}
type UserAggregation @view(sql: """
SELECT
COUNT(*) as count,
SUM(score) as sum,
AVG(score) as average,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score) AS median,
(SELECT id FROM "user" LIMIT 1) as example_id
FROM "user"
""") {
count: Int
sum: Int
average: Float
median: Float
example: User
exampleId: UUID
}
Example: Query Raw SQL View
query {
userAggregations {
count sum average median
exampleId example { id }
}
}
One-to-One View
An one-to-one companion @view
can be handy if you want to argument a @table
with additional implied content.
type Restaurant @table {
name: String!
}
type Review @table {
restaurant: Restaurant!
rating: Int!
}
type RestaurantStats @view(sql: """
SELECT
restaurant_id,
COUNT(*) AS review_count,
AVG(rating) AS average_rating
FROM review
GROUP BY restaurant_id
""") {
restaurant: Restaurant @unique
reviewCount: Int
averageRating: Float
}
In this example, @unique
convey the assumption that each Restaurant
should
have only one RestaurantStats
object.
Example: Query One-to-One View
query ListRestaurants {
restaurants {
name
stats: restaurantStats_on_restaurant {
reviewCount
averageRating
}
}
}
Example: Filter based on One-to-One View
query BestRestaurants($minAvgRating: Float, $minReviewCount: Int) {
restaurants(where: {
restaurantStats_on_restaurant: {
averageRating: {ge: $minAvgRating}
reviewCount: {ge: $minReviewCount}
}
}) { name }
}
Customizations
- One of
@view(sql)
or@view(name)
should be defined.@view(name)
can refer to a persisted SQL view in the Postgres schema. @view(singular)
and@view(plural)
can customize the singular and plural name.
@view
type can be configured further:
@unique
lets you define one-to-one relation.@col
lets you customize SQL column mapping. For example,@col(name: "column_in_select")
.
Limitations
Raw SQL view doesn't have a primary key, so it doesn't support lookup. Other
@table
or @view
cannot have @ref
to a view either.
View cannot be mutated. You can perform CRUD operations on the underlying table to alter its content.
Important: Data Connect doesn't parse and validate SQL
- If the SQL view is invalid or undefined, related requests may fail.
- If the SQL view return incompatible types. Firebase Data Connect may surface errors.
- If a field doesn't have a corresponding column in the SQL SELECT statement,
it will always be
null
. - There is no way to ensure VIEW to TABLE
@ref
constraint. - All fields must be nullable in case they aren't found in the SELECT statement or in the referenced table.
Important: You should always test @view
!
Argument | Type | Description |
---|---|---|
name |
String |
The SQL view name. If neither name nor sql are provided, defaults to the snake_case of the singular type name. name and sql cannot be specified at the same time. |
sql |
String |
SQL SELECT statement used as the basis for this type. SQL SELECT columns should use snake_case. GraphQL fields should use camelCase. name and sql cannot be specified at the same time. |
singular |
String |
Configures the singular name. Defaults to the camelCase like viewName . |
plural |
String |
Configures the plural name. Defaults to infer based on English plural pattern like viewNames . |
Built In
@deprecated on FIELD_DEFINITION
| ARGUMENT_DEFINITION
| INPUT_FIELD_DEFINITION
| ENUM_VALUE
Marks an element of a GraphQL schema as no longer supported.
Argument | Type | Description |
---|---|---|
reason |
String |
Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by CommonMark. |
@include on FIELD
| FRAGMENT_SPREAD
| INLINE_FRAGMENT
Directs the executor to include this field or fragment only when the if
argument is true.
Argument | Type | Description |
---|---|---|
if |
Boolean! |
Included when true. |
@skip on FIELD
| FRAGMENT_SPREAD
| INLINE_FRAGMENT
Directs the executor to skip this field or fragment when the if
argument is true.
Argument | Type | Description |
---|---|---|
if |
Boolean! |
Skipped when true. |
@specifiedBy on SCALAR
Exposes a URL that specifies the behavior of this scalar.
Argument | Type | Description |
---|---|---|
url |
String! |
The URL that specifies the behavior of this scalar. |