ObjectStackObjectStack

Analytics Protocol

In traditional architectures, generating reports usually requires exporting data to a Data Warehouse or writing complex, non-portable SQL queries.

ObjectQL bridges the gap between Transactional (OLTP) and Analytical (OLAP) workloads. It provides a Declarative Analytics Protocol that allows you to request grouped, summarized, and reshaped data directly from the operational database, or transparently route these requests to an analytical replica.

1. The Aggregation AST

Just as you query records, you can query Aggregates. The Aggregation Protocol is designed to be a database-agnostic abstraction over SQL GROUP BY or MongoDB Aggregation Pipelines.

Protocol Structure

An aggregation request consists of three core components: Dimensions (Group By), Measures (Values), and Filters (Scope).

// @objectql/types
interface AggregateRequest {
  object: string;           // Target Object (e.g., 'invoice')
  filters?: FilterNode;     // Scope (e.g., 'year = 2024')
  groupBy: string[];        // Dimensions (e.g., ['sales_rep', 'status'])
  measures: Record<string, AggregateDef>; // Calculations
}

interface AggregateDef {
  field: string;
  method: 'sum' | 'avg' | 'min' | 'max' | 'count' | 'count_distinct';
}

Example: Sales Performance

"Show me total sales and average deal size by Sales Rep for closed deals."

The Protocol (JSON):

{
  "object": "deal",
  "filters": [["stage", "=", "closed_won"]],
  "groupBy": ["owner"],
  "measures": {
    "total_revenue": { "field": "amount", "method": "sum" },
    "avg_deal_size": { "field": "amount", "method": "avg" },
    "deal_count": { "field": "_id", "method": "count" }
  }
}

The Compilation (SQL):

SELECT 
  t1.owner, 
  SUM(t1.amount) as total_revenue, 
  AVG(t1.amount) as avg_deal_size,
  COUNT(t1._id) as deal_count
FROM deals t1
WHERE t1.stage = 'closed_won'
GROUP BY t1.owner

2. Date Histogram (Time-Series Analysis)

Enterprise reports heavily rely on time-based grouping (e.g., "Sales per Month"). ObjectQL standardizes date truncation to ensure consistent behavior across databases.

Protocol Syntax

Use the special logic function date_trunc(field, interval) within the groupBy array.

{
  "object": "log_entry",
  "groupBy": ["date_trunc('created_at', 'month')"],
  "measures": {
    "error_count": { "field": "_id", "method": "count" }
  }
}
  • Postgres Driver: Compiles to DATE_TRUNC('month', created_at)
  • MySQL Driver: Compiles to DATE_FORMAT(created_at, '%Y-%m-01')
  • Mongo Driver: Compiles to $dateToString with format.

3. The Pivot Protocol (Cross-Tabulation)

A Pivot Table transforms rows into columns. While most ORMs leave this to the frontend, ObjectQL defines the Pivot Structure in the protocol to enable server-side optimizations (and compatibility with Excel-like UI components).

Protocol Structure

interface PivotRequest {
  object: string;
  rows: string[];    // The Y-Axis (e.g., Region)
  columns: string[]; // The X-Axis (e.g., Year)
  values: AggregateDef[]; // The Cell Data
}

Example: Sales by Region vs. Year

{
  "type": "pivot",
  "object": "sales_order",
  "rows": ["region"],
  "columns": ["fiscal_year"],
  "values": [{ "field": "amount", "method": "sum" }]
}

Response Format

The engine returns a normalized structure ready for rendering grid components:

{
  "metadata": { "columns": ["2023", "2024", "2025"] },
  "data": [
    { "region": "North", "2023": 1000, "2024": 1500, "2025": 2000 },
    { "region": "South", "2023": 800,  "2024": 900,  "2025": 1100 }
  ]
}

4. BI Data Sources (Headless BI)

ObjectQL enables a "Headless BI" architecture. Instead of defining metrics in Tableau or PowerBI (where they are locked away), you define them in ObjectQL Analytics Cubes.

Defining a Cube (YAML)

A Cube is a virtual view specifically designed for analysis.

# src/analytics/sales_cube.yml
name: sales_performance
label: Sales Performance Cube
base_object: deal
dimensions:
  - name: region
    field: owner.region
  - name: month
    expression: "date_trunc('created_at', 'month')"
measures:
  - name: revenue
    field: amount
    method: sum
  - name: win_rate
    expression: "count(case when stage='won' then 1 end) / count(*)"

Usage

External BI tools can consume this Cube via the ObjectQL Analytics API.

  • API: POST /api/analytics/cube/sales_performance/query
  • Benefit: If you change the definition of "Revenue" in the YAML, every report (Dashboard, PDF, Excel Export) updates instantly.

5. Performance & Materialization

Running aggregations on millions of rows in real-time is resource-intensive. ObjectQL supports Transparent Materialization.

The materialized Flag

# src/analytics/sales_cube.yml
name: sales_performance
materialized: true
refresh_interval: "1h"
  • Behavior: The ObjectOS Kernel automatically creates a Materialized View (in Postgres) or a separate Summary Collection (in Mongo).
  • Routing: When a client queries this Cube, the ObjectQL Compiler rewrites the query to hit the pre-calculated view instead of the raw table.
  • Latency: The query becomes instant (O(1)), at the cost of data freshness (e.g., 1-hour lag).

:::tip Architect's Note For datasets exceeding 100M rows, ObjectQL Drivers can be configured to offload analytical queries to a dedicated OLAP engine (like ClickHouse or Snowflake) while keeping the Protocol identical. This allows you to scale infrastructure without rewriting application logic. :::

On this page