Permission Mongo supports computed fields that derive their values from other fields using MongoDB aggregation expressions. Computed fields can be virtual (calculated on read) or stored (persisted to database).
Overview
Computed field features:
MongoDB aggregation syntax - Use familiar operators (s u m , sum, s u m , multiply, $concat, etc.)
Virtual fields - Calculated on-the-fly when reading
Stored fields - Computed on write and persisted
Selective recomputation - Only recompute when dependencies change
Complex expressions - Support for conditionals, arrays, and nested objects
Source: /home/daytona/workspace/source/pkg/schema/computed.go:1-12
Configuration
Virtual Computed Fields
Calculated when documents are read, not stored in database:
collections :
products :
fields :
price :
type : number
quantity :
type : integer
tax_rate :
type : number
# Virtual computed fields
subtotal :
type : number
computed :
expr :
$multiply : [ "$price" , "$quantity" ]
store : false # Virtual - not stored
tax :
type : number
computed :
expr :
$multiply :
- { $multiply : [ "$price" , "$quantity" ] }
- "$tax_rate"
store : false
total :
type : number
computed :
expr :
$add :
- { $multiply : [ "$price" , "$quantity" ] }
- { $multiply : [{ $multiply : [ "$price" , "$quantity" ] }, "$tax_rate" ] }
store : false
Read request:
GET /products/507f1f77bcf86cd799439011
Response (computed fields added):
{
"_id" : "507f1f77bcf86cd799439011" ,
"price" : 29.99 ,
"quantity" : 3 ,
"tax_rate" : 0.08 ,
"subtotal" : 89.97 , // Computed: 29.99 * 3
"tax" : 7.20 , // Computed: 89.97 * 0.08
"total" : 97.17 // Computed: 89.97 + 7.20
}
Source: /home/daytona/workspace/source/pkg/schema/computed.go:65-95
Stored Computed Fields
Calculated on write and persisted to database:
collections :
orders :
fields :
items :
type : array
items :
type : object
properties :
product :
type : string
price :
type : number
quantity :
type : integer
# Stored computed fields
total_items :
type : integer
computed :
expr :
$size : "$items"
store : true
recompute_on : [ "items" ]
order_total :
type : number
computed :
expr :
$sum :
$map :
input : "$items"
as : "item"
in :
$multiply : [ "$$item.price" , "$$item.quantity" ]
store : true
recompute_on : [ "items" ]
Create request:
POST /orders
Content-Type: application/json
{
"customer" : "cust-123",
"items" : [
{ "product" : "Widget", "price": 10.00, "quantity": 2 },
{ "product" : "Gadget", "price": 15.00, "quantity": 1 }
]
}
Stored document:
{
"_id" : "507f1f77bcf86cd799439011" ,
"customer" : "cust-123" ,
"items" : [
{ "product" : "Widget" , "price" : 10.00 , "quantity" : 2 },
{ "product" : "Gadget" , "price" : 15.00 , "quantity" : 1 }
],
"total_items" : 2 , // Computed and stored
"order_total" : 35.00 , // Computed and stored
"created_at" : "2024-01-15T10:30:00Z"
}
Source: /home/daytona/workspace/source/pkg/schema/computed.go:26-63
Supported Operators
Permission Mongo supports MongoDB aggregation operators:
Arithmetic Operators
# Addition
total :
computed :
expr :
$add : [ "$price" , "$tax" , "$shipping" ]
# Subtraction
profit :
computed :
expr :
$subtract : [ "$revenue" , "$costs" ]
# Multiplication
subtotal :
computed :
expr :
$multiply : [ "$price" , "$quantity" ]
# Division
average :
computed :
expr :
$divide : [ "$total" , "$count" ]
# Rounding
rounded_price :
computed :
expr :
$round : [ "$price" , 2 ] # Round to 2 decimal places
Source: /home/daytona/workspace/source/pkg/schema/computed.go:298-723
String Operators
# Concatenation
full_name :
computed :
expr :
$concat : [ "$first_name" , " " , "$last_name" ]
# Uppercase
product_code :
computed :
expr :
$toUpper : "$sku"
# Lowercase
email_lower :
computed :
expr :
$toLower : "$email"
# String length
name_length :
computed :
expr :
$strLenCP : "$name"
Source: /home/daytona/workspace/source/pkg/schema/computed.go:729-890
Array Operators
# Array size
item_count :
computed :
expr :
$size : "$items"
# Sum array values
total_quantity :
computed :
expr :
$sum : "$quantities"
# Map array
product_names :
computed :
expr :
$map :
input : "$items"
as : "item"
in : "$$item.name"
# Filter array
active_items :
computed :
expr :
$filter :
input : "$items"
as : "item"
cond :
$eq : [ "$$item.status" , "active" ]
# Array element
first_item :
computed :
expr :
$arrayElemAt : [ "$items" , 0 ]
Source: /home/daytona/workspace/source/pkg/schema/computed.go:895-1301
Conditional Operators
# If-then-else
status_label :
computed :
expr :
$cond :
if : { $gte : [ "$stock" , 10 ] }
then : "In Stock"
else : "Low Stock"
# Switch statement
priority :
computed :
expr :
$switch :
branches :
- case : { $gte : [ "$amount" , 10000 ] }
then : "high"
- case : { $gte : [ "$amount" , 1000 ] }
then : "medium"
default : "low"
# Null coalescing
display_name :
computed :
expr :
$ifNull : [ "$nickname" , "$full_name" ]
Source: /home/daytona/workspace/source/pkg/schema/computed.go:1475-1558
Comparison Operators
# Equality
is_premium :
computed :
expr :
$eq : [ "$tier" , "premium" ]
# Greater than
is_expensive :
computed :
expr :
$gt : [ "$price" , 100 ]
# In array
is_manager :
computed :
expr :
$in : [ "manager" , "$roles" ]
Source: /home/daytona/workspace/source/pkg/schema/computed.go:1306-1421
Selective Recomputation
Stored computed fields only recompute when dependencies change:
collections :
orders :
fields :
subtotal :
type : number
computed :
expr :
$sum :
$map :
input : "$items"
as : "item"
in :
$multiply : [ "$$item.price" , "$$item.quantity" ]
store : true
recompute_on : [ "items" ] # Only recompute when items change
discount :
type : number
total :
type : number
computed :
expr :
$subtract : [ "$subtotal" , "$discount" ]
store : true
recompute_on : [ "subtotal" , "discount" ] # Recompute when either changes
Update that changes items:
PUT /orders/507f1f77bcf86cd799439011
{
"items" : [...], // Changed
"status" : "processing"
}
Result: subtotal and total are recomputed.
Update that doesn’t change items:
PUT /orders/507f1f77bcf86cd799439011
{
"status" : "shipped" // items unchanged
}
Result: subtotal and total are NOT recomputed (performance optimization).
Source: /home/daytona/workspace/source/pkg/schema/computed.go:98-133
Complex Examples
Order Totals with Tax
collections :
orders :
fields :
items :
type : array
tax_rate :
type : number
shipping :
type : number
subtotal :
type : number
computed :
expr :
$reduce :
input : "$items"
initialValue : 0
in :
$add :
- "$$value"
- $multiply : [ "$$this.price" , "$$this.quantity" ]
store : true
recompute_on : [ "items" ]
tax :
type : number
computed :
expr :
$multiply : [ "$subtotal" , "$tax_rate" ]
store : true
recompute_on : [ "subtotal" , "tax_rate" ]
total :
type : number
computed :
expr :
$add : [ "$subtotal" , "$tax" , "$shipping" ]
store : true
recompute_on : [ "subtotal" , "tax" , "shipping" ]
Source: /home/daytona/workspace/source/pkg/schema/computed.go:1108-1159
User Full Name
collections :
users :
fields :
first_name :
type : string
middle_name :
type : string
last_name :
type : string
full_name :
type : string
computed :
expr :
$trim :
input :
$concat :
- "$first_name"
- " "
- $ifNull : [ "$middle_name" , "" ]
- " "
- "$last_name"
store : false # Virtual - always fresh
Inventory Status
collections :
products :
fields :
stock :
type : integer
reserved :
type : integer
reorder_point :
type : integer
available :
type : integer
computed :
expr :
$subtract : [ "$stock" , "$reserved" ]
store : true
recompute_on : [ "stock" , "reserved" ]
status :
type : string
computed :
expr :
$switch :
branches :
- case : { $eq : [ "$available" , 0 ] }
then : "out_of_stock"
- case : { $lte : [ "$available" , "$reorder_point" ] }
then : "low_stock"
- case : { $gte : [ "$available" , 100 ] }
then : "in_stock"
default : "available"
store : true
recompute_on : [ "available" , "reorder_point" ]
When to Use Virtual Fields
Use virtual fields when:
Values change frequently
Computation is inexpensive
Data must always be current
Storage space is a concern
Example: User age from birthdate
age :
type : integer
computed :
expr :
$dateDiff :
startDate : "$birth_date"
endDate : "$$NOW"
unit : "year"
store : false # Virtual - always current
When to Use Stored Fields
Use stored fields when:
Values rarely change
Computation is expensive
Need to query/sort by computed value
Performance is critical
Example: Order total for sorting
total :
type : number
computed :
expr : { $sum : "$items.price" }
store : true # Stored - queryable, sortable
Indexing Computed Fields
Stored computed fields can be indexed:
collections :
orders :
fields :
total :
type : number
computed :
expr : { $sum : "$items.price" }
store : true
indexes :
- fields : [ "total" ] # Index for fast queries
order : [ 1 ]
Query by computed field:
GET /orders?total[gte]= 100 & sort = total:desc
Source: /home/daytona/workspace/source/pkg/schema/computed.go:135-224
Testing Computed Fields
func TestComputedFields ( t * testing . T ) {
// Create product with price and quantity
resp := createProduct ( map [ string ] interface {}{
"name" : "Widget" ,
"price" : 10.00 ,
"quantity" : 5 ,
"tax_rate" : 0.08 ,
})
assert . Equal ( t , 201 , resp . StatusCode )
productID := resp . JSON [ "_id" ].( string )
// Read product - computed fields should be present
resp = getProduct ( productID )
assert . Equal ( t , 200 , resp . StatusCode )
data := resp . JSON
assert . Equal ( t , 50.00 , data [ "subtotal" ]) // 10 * 5
assert . Equal ( t , 4.00 , data [ "tax" ]) // 50 * 0.08
assert . Equal ( t , 54.00 , data [ "total" ]) // 50 + 4
// Update quantity - stored fields should recompute
resp = updateProduct ( productID , map [ string ] interface {}{
"quantity" : 10 , // Changed
})
resp = getProduct ( productID )
assert . Equal ( t , 100.00 , resp . JSON [ "subtotal" ]) // Recomputed: 10 * 10
}
Best Practices
Use virtual fields for frequently changing values
Use stored fields for expensive computations
Define recompute_on for stored fields
Index stored fields when querying by them
Test computed fields with edge cases (nulls, zeros)
Document computed field logic for maintainability
Use $ifNull for optional dependencies
Validate computed values in tests
Common Patterns
Pattern: Derived Status
status :
computed :
expr :
$switch :
branches :
- case : { $and : [{ $ne : [ "$paid_at" , null ] }, { $ne : [ "$shipped_at" , null ] }] }
then : "completed"
- case : { $ne : [ "$paid_at" , null ] }
then : "paid"
- case : { $ne : [ "$created_at" , null ] }
then : "pending"
default : "unknown"
store : true
Pattern: Percentage Calculation
completion_percentage :
computed :
expr :
$multiply :
- $divide : [ "$completed_tasks" , "$total_tasks" ]
- 100
store : false
Pattern: Age Calculation
account_age_days :
computed :
expr :
$dateDiff :
startDate : "$created_at"
endDate : "$$NOW"
unit : "day"
store : false
Next Steps
Batch Operations Bulk create, update, and delete operations
Schema Definition Define collection schemas