Tables
Tables are the primary data objects in Hugr, corresponding to physical tables in the underlying database. They support full CRUD operations through the generated GraphQL API.
Defining Tables
Tables are defined using the @table
directive on a GraphQL type:
type customers @table(name: "customers") {
id: Int! @pk
name: String!
email: String!
created_at: Timestamp
deleted_at: Timestamp
}
Table Directive Parameters
name
- Physical table name in the databaseis_m2m
- Marks table as many-to-many relationship (default: false)soft_delete
- Enable soft delete functionality (default: false)soft_delete_cond
- SQL condition to check if record is deletedsoft_delete_set
- SQL statement to mark record as deleted
Primary Keys
Use the @pk
directive to define primary key fields:
type orders @table(name: "orders") {
id: Int! @pk
company_id: Int! @pk # Composite primary key
customer_id: Int!
total: Float!
}
Primary keys enable:
- Generation of
<table>_by_pk
queries - Return of inserted records in mutations
- Optimized single-record queries
Unique Constraints
Define unique constraints using the @unique
directive:
type customers @table(name: "customers")
@unique(fields: ["email"])
@unique(fields: ["tax_id", "country"], query_suffix: "by_tax") {
id: Int! @pk
email: String!
tax_id: String!
country: String!
}
This generates additional queries:
customers_by_email
customers_by_tax
Field Mapping
Map GraphQL fields to database columns using @field_source
:
type customers @table(name: "customers") {
id: String! @pk @field_source(field: "customer_id")
companyName: String! @field_source(field: "company_name")
}
Calculated Fields
Add calculated fields using the @sql
directive:
type order_details @table(name: "order_details") {
unit_price: Float! @field_source(field: "price")
quantity: Int!
discount: Float!
total: Float! @sql(exp: "round((objects.price * [quantity] * (1 - [discount]))*100)/100")
}
Note:
- To reference data object fields within SQL expressions, use the field names enclosed in square brackets
[field_name]
. This applies to both calculated fields and fields that have been renamed using the@field_source
directive. - If you need to access the original source field directly, use the data object alias
objects
with dot notation, for example:objects.<field_name>
.
Default Values and Sequences
Define default values or sequences for auto-generated IDs:
type customers @table(name: "customers") {
id: Int! @pk @default(sequence: "customers_id_seq")
status: String! @default(value: "active")
# preprocessed value - process_func, SQL func, should accept int and JSON (JSONB in Postgres) and return JSON (JSONB in Postgres)
data: JSON @default(
insert_exp: "process_func([$id], [$data])"
update_exp: "process_func(objects.id, [$data])"
)
created_at: Timestamp @default(insert_exp: "NOW()", update_exp: "objects.created_at")
created_by: String @default(insert_exp: "[$auth.user_id]", update_exp: "objects.created_by")
updated_at: Timestamp @default(update_exp: "NOW()", insert_exp: "NULL")
updated_by: String @default(update_exp: "[$auth.user_id]", insert_exp: "NULL")
}
You can define insert and update SQL expressions for the field value, in them you can reference other input values ([$<input_field>]
), auth vars or fields using the objects
alias (in a update_exp
).
Soft Delete
Enable soft delete to mark records as deleted without physical removal:
type customers @table(
name: "customers"
soft_delete: true
soft_delete_cond: "deleted_at IS NULL"
soft_delete_set: "deleted_at = NOW()"
) {
id: Int! @pk
name: String!
deleted_at: Timestamp
}
Many-to-Many Tables
Define junction tables for many-to-many relationships:
type customer_types @table(name: "customer_customer_demo", is_m2m: true) {
customer_id: String! @pk @field_references(
references_name: "customers"
field: "id"
query: "customer"
references_query: "types"
)
type_id: String! @pk @field_references(
references_name: "customer_types"
field: "id"
query: "type"
references_query: "customers"
)
}