///|
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)
}
}
Exists(g, _) => validate_group(g)
_ => ()
}
}
///|
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")
}
e.references(Map([]))
}
Filter(e) => {
if e.has_aggregate() {
raise Syntax(0, "aggregate in FILTER")
}
e.references(Map([]))
}
Subselect(q) => validate_query(q)
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, group_name) in q.group_by {
if expression.has_aggregate() {
raise Syntax(0, "aggregate in GROUP BY")
}
expression.references(Map([]))
if group_name is Some(name) {
if scope.contains(name) || allowed.contains(name) {
raise Syntax(0, "GROUP BY alias already in scope")
}
allowed[name] = true
scope[name] = true
} else if expression is Reference(name) {
allowed[name] = true
}
}
if grouped && q.form == Select && q.all {
raise Syntax(0, "SELECT * with grouping is outside profile")
}
for expression in q.having {
check_group_references(expression, allowed)
}
q.trailing.variables(scope)
q.trailing.variables(allowed)
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")
}
if grouped {
check_group_references(projection.expression, allowed)
} else {
projection.expression.references(Map([]))
}
allowed[projection.variable] = true
scope[projection.variable] = true
}
for (expression, _) in q.order {
if grouped {
check_group_references(expression, allowed)
} else {
expression.references(Map([]))
}
}
}
///|
fn check_group_references(
e : Expr,
allowed : Map[String, Bool],
) -> Unit raise QueryError {
let references = Map([])
e.references(references)
for name, _ in references {
if !allowed.contains(name) {
raise Syntax(0, "ungrouped variable " + name)
}
}
}