Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/docs/queries/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,62 @@ Select specific columns with computed expressions:
; MSFT 378000
```

### Projections that build on earlier projections

A projection sees every projection defined before it in the same `select`,
by its alias — as a bare name or as a literal symbol:

```lisp
(select {from: t sym: sym notional: (* price volume) nn: (+ notional 1)})
; sym notional nn
; --- -------- ------
; AAPL 75000 75001
; GOOG 112000 112001
; MSFT 378000 378001
```

The alias is bound after its own expression, so an alias that shadows a
source column reads the source in its own definition and the new column in
every projection after it:

```lisp
(select {from: t price: (* price 2) p2: (+ price 1)})
; price p2
; ----- ---
; 300 301
; 560 561
; 840 841
```

Only projections see aliases. `where:` and `by:` are evaluated against the
source table, so `where: (> notional 100000)` raises `schema` — filter on
the expression itself, or on a nested select.

In a grouped select a later output may build on an earlier aggregate. An
alias that is one value per group — an aggregate, or an output built on one
— is read from the group result, so `nn` below is computed from the
per-group sum, and a chain of such outputs costs one column each. An alias
of a per-row expression (`vals: price`) is substituted where it is named,
so `m: (max vals)` is `(max price)`.

```lisp
(select {from: t by: sym notional: (sum (* price volume)) nn: (+ notional 1)})
```

Three rules follow from an alias being one value per group. Inside an
aggregate's argument a name that is a source column is always the source
column, so `s: (sum s) mx: (max s)` takes the maximum of the rows, not of a
sum. An aggregate cannot be aggregated again, through an alias or written
out: `s: (sum price) mx: (max s)` and `mx: (max (sum price))` both raise
`domain`. And an output may not put a source column beside a per-group
value outside an aggregate: `f: (* price n)` with `n: (count price)` raises
`domain`; write `(* (sum price) n)` or whichever aggregate is meant.

A literal symbol inside a select resolves to an earlier alias or a source
column of that name, in that order; one naming neither stays a constant
symbol. Arithmetic on a symbol — a literal or a symbol column — is a `type`
error inside a select, as it is outside.

### Whole-column projections

A projection may be a whole-column verb — `distinct`, `asc`, `desc`, or
Expand Down
7 changes: 7 additions & 0 deletions src/lang/env.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ static struct {
* threads with no bound VM, so plain __VM derefs are safe below. */

int32_t ray_env_scope_depth(void) { return __VM ? __VM->scope_depth : 0; }

bool ray_env_query_scope_above(int32_t depth) {
if (!__VM) return false;
for (int32_t d = depth < 0 ? 0 : depth; d < __VM->scope_depth; d++)
if (__VM->scope_stack[d].kind == RAY_SCOPE_QUERY) return true;
return false;
}
int32_t ray_env_global_count(void) { return g_env.count; }

/* Reverse-map a builtin function object to the symbol it is bound under in the
Expand Down
3 changes: 3 additions & 0 deletions src/lang/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ ray_err_t ray_env_push_scope(void);
ray_err_t ray_env_push_query_scope(void);
void ray_env_pop_scope(void);
int32_t ray_env_scope_depth(void);
/* True when a query scope frame sits at index >= depth: a nested query has
* bound its own columns since the frame that was at depth-1 was pushed. */
bool ray_env_query_scope_above(int32_t depth);
ray_err_t ray_env_set_local(int64_t sym_id, ray_t* val);
ray_t* ray_env_get_lexical_local(int64_t sym_id);
bool ray_env_has_lexical_local(int64_t sym_id);
Expand Down
9 changes: 4 additions & 5 deletions src/lang/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3824,11 +3824,10 @@ ray_t* ray_eval(ray_t* obj) {
* the rule fires only while a query is active (ray_active_query_table
* is NULL otherwise). A literal naming no column returns itself. */
if (obj->type == -RAY_SYM) {
ray_t* qt = ray_active_query_table();
if (qt && qt->type == RAY_TABLE) {
ray_t* col = ray_table_get_col(qt, obj->i64);
if (col) { ray_retain(col); ret = col; goto out; }
}
/* The column — or, during a per-row evaluation, its cell in the
* current row (ray_active_query_literal). */
ray_t* v = ray_active_query_literal(obj->i64);
if (v) { ret = v; goto out; }
}
ray_retain(obj);
ret = obj; goto out;
Expand Down
12 changes: 7 additions & 5 deletions src/ops/agg_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,8 @@ static bool agg_desc_init(agg_desc_t* d, ray_graph_t* g, ray_op_ext_t* ext,
for (uint32_t k = 0; k < nk; k++) d->key_data[k] = ray_data(key_cols[k]);
for (uint32_t a = 0; a < na; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
d->agg_syms[a] = ie->sym;
ray_t* vc = (ext->agg_ops[a] != OP_COUNT) ? ray_table_get_col(tbl, ie->sym) : NULL;
d->agg_syms[a] = ie ? ie->sym : 0;
ray_t* vc = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
d->val_data[a] = vc ? ray_data(vc) : NULL;
d->val_types[a] = vc ? vc->type : RAY_I64;
d->val_hasnull[a] = vc ? ray_vec_may_have_nulls(vc) : false;
Expand Down Expand Up @@ -966,7 +966,7 @@ static bool agg_vo_init(agg_vo_t* vo, ray_graph_t* g, ray_op_ext_t* ext, ray_t*
vo->block = 0;
for (uint32_t a = 0; a < na; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
ray_t* vc = (ext->agg_ops[a] != OP_COUNT) ? ray_table_get_col(tbl, ie->sym) : NULL;
ray_t* vc = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
int8_t in_type = vc ? vc->type : RAY_I64;
vo->vts[a] = agg_resolve(ext->agg_ops[a], in_type);
vo->off[a] = vo->block;
Expand Down Expand Up @@ -5285,13 +5285,15 @@ static ray_t* exec_group_v2_run_inner(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
const agg_vtable_t* vt = agg_resolve(ext->agg_ops[a], x_col->type);
col = agg_run_one_bin(vt, x_col, y_col, groups.gids, nrows, groups.ngroups, kparam);
} else {
ray_t* val_col = (ext->agg_ops[a] != OP_COUNT) ? ray_table_get_col(tbl, ie->sym) : NULL;
ray_t* val_col = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
int8_t in_type = val_col ? val_col->type : RAY_I64;
const agg_vtable_t* vt = agg_resolve(ext->agg_ops[a], in_type);
col = agg_run_one(vt, val_col, groups.gids, nrows, groups.ngroups, kparam);
}
if (!col || RAY_IS_ERR(col)) { agg_groups_free(&groups); ray_release(result); return col ? col : ray_error("oom", NULL); }
int64_t agg_name = agg_result_col_name(ie->sym, ext->agg_ops[a]);
/* A COUNT reads no input column, so its input node need not be a
* scan (`(count (* price 2))`) and has no ext to name it by. */
int64_t agg_name = agg_result_col_name(ie ? ie->sym : 0, ext->agg_ops[a]);
result = ray_table_add_col(result, agg_name, col);
ray_release(col);
}
Expand Down
2 changes: 2 additions & 0 deletions src/ops/graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ ray_graph_t* ray_graph_new(ray_t* tbl) {
void ray_graph_free(ray_graph_t* g) {
if (!g) return;

if (g->compile_err) { ray_release(g->compile_err); g->compile_err = NULL; }

/* Unconsumed slice-group hint (error paths / shapes that never
* reached exec_group). */
if (g->sg_col) { ray_release(g->sg_col); g->sg_col = NULL; }
Expand Down
26 changes: 26 additions & 0 deletions src/ops/ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,31 @@ typedef struct ray_graph {
uint32_t node_id;
} cexpr_env[32];
int cexpr_env_top;

/* Output aliases of the select being compiled (src/ops/query.c):
* the projections compiled so far, in order. A name reference or a
* literal symbol consults them after the lambda/let env and before
* the source table's columns. Borrowed views into the projection
* loop's scratch arrays: set and cleared by that loop, never freed
* here. Kept apart from cexpr_env so a wide select does not eat the
* slots lambda inlining needs. */
const int64_t* sel_alias_syms;
const uint32_t* sel_alias_ids;
int sel_alias_n;

/* Set by compile_expr_dag when it declines an expression that can
* only fail (arithmetic on a symbol column). A caller with an
* evaluation fallback ignores it — the evaluator raises the same
* error; one without reports it instead of a generic compile
* failure. Owned; released by ray_graph_free. */
ray_t* compile_err;

/* > 0 while compile_expr_dag is inside a branch of `if`/`cond`. The
* DAG evaluates both arms element-wise and the condition picks one, so
* an arm's value is observable only where it is selected; the checks
* that reject an expression outright (arithmetic on a symbol) stay
* quiet inside an arm and let the arm compile as it always did. */
int if_arm_depth;
} ray_graph_t;

/* ===== Morsel Iterator ===== */
Expand Down Expand Up @@ -918,6 +943,7 @@ ray_t* ray_lazy_append(ray_t* lazy, uint16_t opcode);
* so a literal never captures a lambda/let local and resolution fires only
* inside a query. Returns NULL when no query is active. */
ray_t* ray_active_query_table(void);
ray_t* ray_active_query_literal(int64_t sym);

#ifdef __cplusplus
}
Expand Down
Loading
Loading