-
-
Notifications
You must be signed in to change notification settings - Fork 119
Aggregation API
Tested ✓ 2026-04-16
SQL-like aggregation queries via REST API.
Status:
- ✅ Basic aggregations (count, sum, avg, min, max) - Working
- ✅ GROUP BY - Working
- ✅ Filters (eq, not, lt, lte, gt, gte, in) - Working
- ✅ ORDER BY - Working
- ✅ POST method - Fixed in commit 9e3b5650 (Issue #174)
- ✅ HAVING clause - Fixed in commit e1c3017c (Issue #173)
- ✅ Security: column/group expressions validated against schema (CWE-89 fix, v0.11.4)
GET /aggregate/{entity} ✅ Working
POST /aggregate/{entity} ✅ Working (fixed in commit 9e3b5650)
Both GET and POST methods work identically. Use POST for complex queries that would exceed URL length limits.
curl "http://localhost:6336/aggregate/product?column=count" \
-H "Authorization: Bearer $TOKEN"Response:
{
"data": [
{
"type": "aggregate_product",
"id": "019bfa88-8fc8-7242-bafc-5ca3da2a34fe",
"attributes": {
"__type": "aggregate_product",
"count": 5
}
}
]
}curl "http://localhost:6336/aggregate/product?\
column=count,sum(price),avg(price),min(price),max(price)" \
-H "Authorization: Bearer $TOKEN"Response:
{
"data": [
{
"type": "aggregate_product",
"attributes": {
"count": 5,
"sum(price)": 1559.95,
"avg(price)": 311.99,
"min(price)": 29.99,
"max(price)": 999.99
}
}
]
}curl "http://localhost:6336/aggregate/product?\
group=category&\
column=category,count,avg(price)" \
-H "Authorization: Bearer $TOKEN"Response:
{
"data": [
{
"type": "aggregate_product",
"attributes": {
"category": "Electronics",
"count": 3,
"avg(price)": 369.99
}
},
{
"type": "aggregate_product",
"attributes": {
"category": "Furniture",
"count": 2,
"avg(price)": 224.99
}
}
]
}The column and group parameters accept a strict, validated set of expression forms. Arbitrary SQL expressions are rejected — column names are validated against the entity schema and function names are checked against an explicit allowlist.
| Form | Example | Notes |
|---|---|---|
count |
count |
Shorthand for COUNT(*) |
| Column name | status |
Must exist in entity schema |
| Aggregate function | sum(price) |
Column schema-validated |
| Aggregate with alias | sum(price) as total |
Alias: letters/digits/underscore only |
| Scalar function | date(created_at) |
Column schema-validated |
| Scalar with alias | date(created_at) as day |
|
| Scalar with literal | strftime('%Y-%m', created_at) as month |
String literals must be single-quoted |
| Joined column | customer.name |
Table must be in join= list |
Same forms as column, except aggregate functions (sum, avg, etc.) are not permitted in GROUP BY.
| Function | Syntax | Description |
|---|---|---|
| Count | count |
Count all rows (shorthand) |
| Count | count(column) |
Count non-null values |
| Sum | sum(column) |
Sum of values |
| Average | avg(column) |
Average value |
| Minimum | min(column) |
Minimum value |
| Maximum | max(column) |
Maximum value |
| First | first(column) |
First value |
| Last | last(column) |
Last value |
These safe data-transformation functions are permitted. System functions, I/O functions, and anything not in this list is rejected.
| Category | Functions |
|---|---|
| Date/time |
date, time, datetime, strftime, julianday, month, year, day
|
| String |
upper, lower, length, substr, trim, ltrim, rtrim, replace, hex
|
| Math |
abs, round
|
| Null handling |
coalesce, ifnull, nullif
|
Scalar functions require at least one schema column argument. Zero-argument calls (e.g. random(), sqlite_version()) are rejected.
# Count all
curl "http://localhost:6336/aggregate/product?column=count"
# Sum prices
curl "http://localhost:6336/aggregate/order?column=sum(total)"
# Multiple aggregates
curl "http://localhost:6336/aggregate/product?column=count,avg(price),min(price),max(price)"# Group by single column
curl "http://localhost:6336/aggregate/order?group=status&column=status,count,sum(total)"
# Group by multiple columns
curl "http://localhost:6336/aggregate/order?group=status,payment_method&column=status,payment_method,count"Response:
{
"data": [
{"type": "aggregate_order", "attributes": {"status": "pending", "count": 25, "sum_total": 5000}},
{"type": "aggregate_order", "attributes": {"status": "completed", "count": 100, "sum_total": 25000}},
{"type": "aggregate_order", "attributes": {"status": "cancelled", "count": 10, "sum_total": 2000}}
]
}| Function | Description |
|---|---|
eq(col,val) |
Equals |
not(col,val) |
Not equals |
lt(col,val) |
Less than |
lte(col,val) |
Less than or equal |
gt(col,val) |
Greater than |
gte(col,val) |
Greater than or equal |
in(col,v1,v2) |
In list |
notin(col,v1,v2) |
Not in list |
is(col,null) |
Is null |
not(col,null) |
Is not null |
# Equals - products in Electronics category
curl "http://localhost:6336/aggregate/product?filter=eq(category,Electronics)&column=count,sum(price)"
# Result: count=3, sum=1109.97
# Greater than - products with price > 100
curl "http://localhost:6336/aggregate/product?filter=gt(price,100)&column=count,avg(price)"
# Result: count=3, avg=483.32
# Greater than or equal
curl "http://localhost:6336/aggregate/product?filter=gte(price,50)&column=count"
# Result: count=4
# Less than
curl "http://localhost:6336/aggregate/product?filter=lt(price,100)&column=count"
# Result: count=2
# Less than or equal
curl "http://localhost:6336/aggregate/product?filter=lte(price,100)&column=count,avg(price)"
# Result: count=2, avg=54.99
# Not equals
curl "http://localhost:6336/aggregate/product?filter=not(category,Electronics)&column=count"
# Result: count=2 (Furniture products)
# Multiple filters (AND) - Electronics AND price >= 50
curl "http://localhost:6336/aggregate/product?filter=eq(category,Electronics)&filter=gte(price,50)&column=count"
# Result: count=2
# In list - orders with status in (completed, pending)
curl "http://localhost:6336/aggregate/sales_order?filter=in(status,completed,pending)&column=count,sum(total)"
# Result: count=4, sum=1929.94✅ Working (Fixed in commit e1c3017c): HAVING clause now properly filters aggregated results.
Filter on aggregated values:
# Filter groups with more than 5 orders
curl "http://localhost:6336/aggregate/order?group=customer_id&column=customer_id,count,sum(total)&having=gt(count,5)"
# Multiple HAVING conditions
curl "http://localhost:6336/aggregate/order?group=status&column=status,count,sum(total)&having=gte(count,10)&having=gt(sum(total),1000)"TOKEN=$(cat /tmp/daptin-token.txt)
# Groups with more than 1 user
curl "http://localhost:6336/aggregate/user_account?group=permission&column=permission,count&having=gt(count,1)" \
-H "Authorization: Bearer $TOKEN"
# Categories with total price over $500
curl "http://localhost:6336/aggregate/product?group=category&column=category,count,sum(price)&having=gt(sum(price),500)" \
-H "Authorization: Bearer $TOKEN"
# Average order value greater than $100
curl "http://localhost:6336/aggregate/order?group=customer_id&column=customer_id,count,avg(total)&having=gt(avg(total),100)" \
-H "Authorization: Bearer $TOKEN"✅ Working (Fixed in commit 9e3b5650): POST method now properly handles JSON request bodies for complex aggregation queries.
Use POST for:
- Complex queries with many parameters
- GROUP BY with multiple columns
- Large filter conditions that exceed URL length limits
- Better readability of complex queries
# Simple count via POST
curl -X POST http://localhost:6336/aggregate/order \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"column": ["count"]
}'# Complex query via POST
curl -X POST http://localhost:6336/aggregate/order \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"group": ["status", "payment_method"],
"column": ["status", "payment_method", "count", "sum(total)", "avg(total)"],
"filter": ["eq(status,completed)"]
}'Response (same as GET method):
{
"data": [
{
"type": "aggregate_order",
"attributes": {
"status": "completed",
"payment_method": "credit_card",
"count": 50,
"sum(total)": 15000.00,
"avg(total)": 300.00
}
}
]
}| Aspect | POST | GET |
|---|---|---|
| Readability | Better for complex queries | Simple for basic queries |
| URL length limit | No limit | Limited by URL length |
| Request body | JSON object | Query parameters |
| Response | Identical | Identical |
| Use case | Complex multi-parameter queries | Simple single-condition queries |
# Daily totals
curl "http://localhost:6336/aggregate/order?group=date(created_at)&column=date(created_at),count,sum(total)"
# Monthly totals
curl "http://localhost:6336/aggregate/order?group=month(created_at),year(created_at)&column=month(created_at),year(created_at),count"# Total sales by status
curl "http://localhost:6336/aggregate/order?\
group=status&\
column=status,count,sum(total),avg(total)&\
filter=gte(created_at,2024-01-01)"
# Top customers by order count
curl "http://localhost:6336/aggregate/order?\
group=customer_id&\
column=customer_id,count,sum(total)&\
having=gte(count,5)&\
sort=-count&\
limit=10"# Products by category with stats
curl "http://localhost:6336/aggregate/product?\
group=category_id&\
column=category_id,count,avg(price),min(price),max(price)"
# Low stock products
curl "http://localhost:6336/aggregate/product?\
filter=lt(stock,10)&\
column=count"# Users by registration month
curl "http://localhost:6336/aggregate/user_account?\
group=month(created_at),year(created_at)&\
column=month(created_at),year(created_at),count"{
"links": {
"current_page": 1,
"total": 5
},
"data": [
{
"type": "aggregate_{entity}",
"attributes": {
"group_column": "value",
"count": 100,
"sum_total": 5000.00,
"avg_total": 50.00
}
}
]
}Aggregation queries support LEFT JOIN operations for cross-table analysis.
join=table@eq(local_column,remote_table.remote_column)
# Join orders with customers
curl "http://localhost:6336/aggregate/order?\
join=customer@eq(customer_id,customer.id)&\
column=count,sum(total),customer.name&\
group=customer.name"
# Multiple join conditions (AND)
curl "http://localhost:6336/aggregate/order?\
join=customer@eq(customer_id,customer.id)&eq(region,customer.region)&\
column=count,customer.name"Reference entity values in join conditions using entity@uuid format:
curl "http://localhost:6336/aggregate/order?\
join=customer@eq(customer_id,customer@abc-123-uuid)&\
column=count"# Filter by time range
curl "http://localhost:6336/aggregate/order?\
filter=gte(created_at,2024-01-01)&\
filter=lt(created_at,2024-02-01)&\
column=count,sum(total)"- Maximum 1000 result rows
- Use GraphQL for more complex nested queries
- Home
- Installation
- First-Admin-Setup ⭐ NEW
- Common-Errors 🔧 NEW
- Getting-Started-Guide
- Configuration
- Database-Setup
- Walkthrough-Product-Catalog
- Walkthrough-WebSocket-Real-Time ✨ NEW
- Walkthrough-YJS-Collaborative-Editing ✨ NEW
- Actions-Overview
- Action-Permission-Schema-Sync-Technical-KT
- User-Actions
- Admin-Actions
- Data-Actions
- Cloud-Actions
- Email-Actions
- Certificate-Actions
- Custom-Actions
- GraphQL-API ✓ NEW
- State-Machines
- Task-Scheduling ✓ NEW
- Template-Rendering ✓ NEW
- Data-Exchange
- Integrations