Cube SQL documentation when pushdown is enabled omits key functionality details
- Dominant language
- Rust
- Stars
- 20.8k
- Forks
- 2.1k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 181
Description
I used a llm to clean this issue up and expand the examples so apologies for the tone.
The [SQL API reference](https://docs.cube.dev/reference/core-data-apis/sql-api/reference) presents a single, fixed list of supported SQL functions and operators. In practice, when query pushdown is enabled, the *actually-available* set is:
1. **Larger than the documented list** (several working functions aren't on the page), and
2. **Different per database driver** (the same function works on one source and is rejected on another).
Neither the per-driver variation nor the additional functions are documented. On top of that, there's an **undocumented structural limitation**: an aggregate over a "dimension-only" expression only accepts a **single dimension reference** in that expression.
This issue is about getting these rules documented (and ideally fixing the limitation in #2 below).
## Environment
- Cube version: `v1.6.50`
- Query pushdown: enabled (default since 1.0)
- Source database: PostgreSQL (Northwind dataset)
---
## Issue 1 — The supported-function set is undocumented per-driver and incomplete
The docs imply the function list is fixed and source-agnostic. It isn't. The allowlist is assembled in code as:
- **Base list:** `packages/cubejs-schema-compiler/src/adapter/BaseQuery.js` → `sqlTemplates()` → `functions: { ... }` (~line 4478).
- **Per-driver overrides:** each dialect's `*Query.ts` mutates that map. Examples:
- `PostgresQuery.ts` **adds** `DATE_PART`, `CURRENT_DATE`, `LEAST`, `GREATEST`, … and keeps `PERCENTILE_CONT`.
- `BigqueryQuery.ts:351` **deletes** `PERCENTILECONT` (and `TO_CHAR`).
- `ClickHouseQuery.ts:270`, `MssqlQuery.ts:276` **delete** `PERCENTILECONT`.
A function only resolves under pushdown if a `functions/` template exists for that data source (enforced in `rust/cubesql/.../compile/rewrite/rules/wrapper/mod.rs:212`, `can_rewrite_template`). There is no catch-all passthrough.
**Concrete example — works on Postgres but is NOT on the docs page:**
```sql
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY OrderDetails.order_id) AS p50_num
FROM OrderDetails;
```
`PERCENTILE_CONT` does not appear in the SQL API reference, yet it pushes down successfully on Postgres. The **same query fails** on BigQuery / ClickHouse / MSSQL because those drivers remove the template.
Other functions that are templated (at least for Postgres) but absent from the reference page include: `STRING_AGG`, `LAG`, `LEAD`, `DATE_PART`, `CURRENT_DATE`.
**Request:** Document (a) that the supported-function set varies by data source, and (b) the actual per-driver set — or at minimum state that the reference list is a *baseline* and link to the driver-specific additions/removals.
---
## Issue 2 — Aggregates over a "dimension-only" expression only accept a single dimension (undocumented + appears to be a bug)
The query above works because the `ORDER BY` references **one** dimension. Add a second dimension reference and it fails:
```sql
-- FAILS
SELECT PERCENTILE_CONT(0.5)
WITHIN GROUP (ORDER BY OrderDetails.discount + OrderDetails.quantity) AS p50_num
FROM OrderDetails;
```
```
SQLCompilationError: Internal: Expected single cube for dimension-only measure
expr:OrderDetails.percentilecont_f, got ["OrderDetails", "OrderDetails"]
```
### Root cause (Tesseract planner)
The expression's children are two dimensions, so it's treated as a "dimension-only measure expression." The planner collects the cube name of **every child without deduplicating**:
`rust/cube/cubesqlplanner/.../planner/symbols/member_expression_symbol.rs:168`
```rust
let cube_names = childs
.into_iter()
.map(|child| child.cube_name()) // one entry per child
.collect_vec(); // no .unique()
```
Then `rust/cube/cubesqlplanner/.../planner/collectors/multiplied_measures_collector.rs:127` only accepts 0 or 1 cube names and errors otherwise:
```rust
} else if cube_names.len() == 1 {
...
} else {
return Err(CubeError::user(format!(
"Expected single cube for dimension-only measure {}, got {:?}", ...)));
}
```
So `discount + quantity` yields `["OrderDetails", "OrderDetails"]` and trips the `len() == 1` guard — **even though both references are the same cube**. This looks like a missing dedup: deduplicating `cube_names` (or comparing distinct cubes) before the check would let same-cube multi-dimension expressions through.
### Impact
You cannot run an ad-hoc aggregate over an arithmetic combination of two member columns; the combined expression must be pre-defined as a single dimension in the model, e.g.:
```yaml
dimensions:
- name: num
sql: "{CUBE}.discount + {CUBE}.quantity"
type: number
```
```sql
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY OrderDetails.num) AS p50_num
FROM OrderDetails; -- works
```
**Request:** Either (a) fix the dedup so same-cube multi-dimension aggregate expressions are allowed, and/or (b) document that aggregate expressions over dimensions must reference a single dimension and that combined expressions must be modeled as a dimension.
---
## Expected vs. actual
| | Expected (from docs) | Actual |
|---|---|---|
| Supported function set | Fixed list on the reference page | Larger, and varies per database driver |
| `PERCENTILE_CONT` | Not listed → assume unsupported | Works on Postgres/Snowflake; rejected on BigQuery/ClickHouse/MSSQL |
| Aggregate over `dim_a + dim_b` | No guidance | Fails with an internal "Expected single cube" error |
## Suggested documentation additions
1. State that the SQL API function list is a **baseline**, and that pushdown exposes additional functions **subject to the data source driver**.
2. Provide (or link to) the **per-driver** added/removed functions.
3. Document the **single-dimension constraint** for aggregate-over-dimension expressions, with the recommended workaround (model the expression as a dimension).
Contributor guide
Assessment
This issue has not been assessed yet.