///|
fn Expr::references(
self : Expr,
names : Map[String, Bool],
inside_aggregate~ : Bool = false,
) -> Unit raise QueryError {
match self {
Reference(name) => if !inside_aggregate { names[name] = true }
Unary(_, e) => e.references(names, inside_aggregate~)
Binary(_, a, b) => {
a.references(names, inside_aggregate~)
b.references(names, inside_aggregate~)
}
Call(_, args) =>
for arg in args {
arg.references(names, inside_aggregate~)
}
Aggregate(_, value, _) => {
if inside_aggregate {
raise Syntax(0, "nested aggregate")
}
if value is Some(e) {
e.references(names, inside_aggregate=true)
}
}
_ => ()
}
}
///|
fn validate_group(g : Group) -> Unit raise QueryError {
let scope : Map[String, Bool] = Map([])
for pat in g.patterns {
match pat {
Bind(e, name) => {
if scope.get(name) is Some(_) {
raise Syntax(0, "BIND variable already in scope")
}
if e.has_aggregate() {
raise Syntax(0, "aggregate in BIND")
}
}
Filter(e) =>
if e.has_aggregate() {
raise Syntax(0, "aggregate in FILTER")
}
Nested(inner) | Optional(inner) | Minus(inner) | Graph(_, inner) =>
validate_group(inner)
Union(groups) =>
for group in groups {
validate_group(group)
}
_ => ()
}
Group::{ patterns: [pat], }.variables(scope)
}
}
///|
fn validate_query(q : Query) -> Unit raise QueryError {
validate_group(q.group)
let scope = Map([])
q.group.variables(scope)
let grouped = query_grouped(q)
if q.having.length() > 0 && !grouped {
raise Syntax(0, "HAVING requires grouping")
}
let allowed : Map[String, Bool] = Map([])
for expression in q.group_by {
if expression.has_aggregate() {
raise Syntax(0, "aggregate in GROUP BY")
}
if expression is Reference(name) {
allowed[name] = true
}
}
if grouped && q.all {
raise Syntax(0, "SELECT * with grouping is outside profile")
}
for projection in q.projections {
let plain = projection.expression is Reference(name) &&
name == projection.variable
if !plain && scope.get(projection.variable) is Some(_) {
raise Syntax(0, "projection aliased already in scope")
}
let references = Map([])
projection.expression.references(references)
if grouped {
for name, _ in references {
if allowed.get(name) is None {
raise Syntax(0, "ungrouped projected variable " + name)
}
}
}
allowed[projection.variable] = true
scope[projection.variable] = true
}
}