use super::*;
use zirql::{ValueExpression as E, ValueExpressionKind as K};
pub type ProgramValue = E<zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>>;
pub type ProgramFunction = zirql::UnresolvedPatternFunctionDeclaration<
zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>,
>;
#[derive(Debug)]
pub enum ProgramNative {
Query(Query),
Ordering(zirql::NativeOrdering<zirql::Expression<Self, zirql::NativeSetOperator, ()>>),
Mapping(zirql::NativeMap<zirql::Expression<Self, zirql::NativeSetOperator, ()>>),
MapSorting,
Exists(Box<zirql::Expression<Self, zirql::NativeSetOperator, ()>>),
FixedPoint(Box<zirql::Expression<Self, zirql::NativeSetOperator, ()>>),
}
impl ProgramNative {
fn take_selectors(
&mut self,
pending: &mut Vec<zirql::Expression<Self, zirql::NativeSetOperator, ()>>,
) {
match self {
Self::Ordering(ordering) => pending.extend(ordering.selector.take()),
Self::Mapping(mapping) => {
pending.push(std::mem::replace(
&mut mapping.key,
Located::new(ExpressionKind::Input, 0..0),
));
pending.push(std::mem::replace(
&mut mapping.value,
Located::new(ExpressionKind::Input, 0..0),
));
}
Self::Exists(body) | Self::FixedPoint(body) => pending.push(*std::mem::replace(
body,
Box::new(Located::new(ExpressionKind::Input, 0..0)),
)),
Self::Query(_) | Self::MapSorting => {}
}
}
}
impl Drop for ProgramNative {
fn drop(&mut self) {
let mut pending = Vec::new();
self.take_selectors(&mut pending);
while let Some(expression) = pending.pop() {
match expression.value {
ExpressionKind::Input | ExpressionKind::Binding(_) => {}
ExpressionKind::Group(value) | ExpressionKind::Field { value, .. } => {
pending.push(*value)
}
ExpressionKind::Record(fields) => {
pending.extend(fields.into_iter().map(|(_, value)| value))
}
ExpressionKind::Set { left, right, .. } => {
pending.push(*left);
pending.push(*right);
}
ExpressionKind::Sequence { input, steps } => {
pending.push(*input);
for mut step in steps {
if let ExpressionStep::Stage(stage) = &mut step.value {
stage.take_selectors(&mut pending);
}
}
}
}
}
}
}
struct Inputs {
functions: Vec<ProgramFunction>,
bindings: Vec<(zirql::Pattern, ProgramValue)>,
report: Option<ProgramValue>,
}
impl Drop for Inputs {
fn drop(&mut self) {
for mut function in std::mem::take(&mut self.functions) {
for parameter in &mut function.parameters {
dispose_signature(std::mem::replace(
&mut parameter.value.declaration.value,
zirql::SignatureType::Integer,
));
}
dispose_signature(std::mem::replace(
&mut function.result.value,
zirql::SignatureType::Integer,
));
zirql::dispose_unresolved_pattern_functions(vec![function]);
}
for (pattern, value) in std::mem::take(&mut self.bindings) {
zirql::dispose_pattern(pattern);
zirql::dispose_native_value(value);
}
if let Some(value) = self.report.take() {
zirql::dispose_native_value(value);
}
}
}
impl Program {
pub fn from_values(
functions: Vec<ProgramFunction>,
bindings: Vec<(zirql::Pattern, ProgramValue)>,
final_report: ProgramValue,
limits: ProgramLimits,
) -> Result<Self, QueryError> {
let mut inputs = Inputs {
functions,
bindings,
report: Some(final_report),
};
let mut budget = Budget::for_program(limits.core());
let range = inputs.report.as_ref().unwrap().range.clone();
let mut names = std::collections::BTreeSet::new();
for function in &inputs.functions {
functions::check_signature(&function.result, &mut budget, limits.nesting)?;
for parameter in &function.parameters {
functions::check_signature(
¶meter.value.declaration,
&mut budget,
limits.nesting,
)?;
}
for query in functions::check_value(&function.value, &mut budget, limits.nesting)? {
check_native(query, &mut budget, limits.nesting)?;
}
}
let mut binding_names = Vec::new();
for (pattern, value) in &inputs.bindings {
let selected =
zirql::pattern_names(pattern, &mut budget).map_err(pipeline::core_error)?;
for name in &selected {
if !names.insert(name.value.clone()) {
return Err(QueryError::query(
name.range.clone(),
"binding is already defined",
));
}
}
binding_names.push(selected.into_iter().cloned().collect());
for query in functions::check_value(value, &mut budget, limits.nesting)? {
check_native(query, &mut budget, limits.nesting)?;
}
}
for query in
functions::check_value(inputs.report.as_ref().unwrap(), &mut budget, limits.nesting)?
{
check_native(query, &mut budget, limits.nesting)?;
}
let mut queries = Vec::new();
let mut lower = |query, range: Range<usize>| {
let index = queries.len();
queries.push(ProgramQuery {
query,
arguments: Rc::new(Vec::new()),
copy_units: range.len() + 1,
});
value_stage(Stage::Query(index, Rc::new(Vec::new())), range)
};
let mut functions = std::mem::take(&mut inputs.functions)
.into_iter()
.map(|function| zirql::UnresolvedPatternFunctionDeclaration {
name: function.name,
parameters: function.parameters,
result: function.result,
value: map_queries(function.value, &mut |query| map_native(query, &mut lower)),
})
.collect::<Vec<_>>();
let mut statements = Vec::new();
for ((pattern, value), names) in std::mem::take(&mut inputs.bindings)
.into_iter()
.zip(binding_names)
{
let range = value.range.clone();
statements.push(Located::new(
zirql::Statement::BindFields {
names,
expression: value_stage(
Stage::PatternBinding(
pattern,
map_queries(value, &mut |query| map_native(query, &mut lower)),
),
range.clone(),
),
},
range,
));
}
statements.push(Located::new(
zirql::Statement::Evaluate {
expression: value_stage(
Stage::FunctionValue(map_queries(
inputs.report.take().unwrap(),
&mut |query| map_native(query, &mut lower),
)),
range.clone(),
),
implicit_output: Some(Emitter::Json),
},
range.clone(),
));
for function in &mut functions {
functions::lower_primitives(
&mut function.value,
&BTreeMap::new(),
&queries,
&Default::default(),
);
}
for statement in &mut statements {
let expression = match &mut statement.value {
zirql::Statement::Bind { expression, .. }
| zirql::Statement::BindFields { expression, .. }
| zirql::Statement::Evaluate { expression, .. } => expression,
};
if let ExpressionKind::Sequence { steps, .. } = &mut expression.value {
for step in steps {
if let ExpressionStep::Stage(
Stage::FunctionValue(value) | Stage::PatternBinding(_, value),
) = &mut step.value
{
functions::lower_primitives(
value,
&BTreeMap::new(),
&queries,
&Default::default(),
);
}
}
}
}
let report_model_demand = queries
.iter()
.any(|query| query.query.requires_model_facts())
|| functions.iter().any(|function| {
functions::signature_needs_model(&function.result.value)
|| function.parameters.iter().any(|parameter| {
functions::signature_needs_model(¶meter.value.declaration.value)
})
});
Ok(Self {
parameters: Vec::new(),
program: zirql::Program::new(statements, range.clone()),
preflight_work: limits.work - budget.remaining_work(),
preflight_results: limits.results - budget.remaining_results().unwrap(),
preflight_range: Some(range),
queries,
functions,
function_limits: zirql::FunctionLimits { active_depth: 64 },
report_model_demand,
editing: false,
prepared: true,
prepared_values: false,
})
}
}
fn map_native(
query: zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>,
lower: &mut impl FnMut(Query, Range<usize>) -> Expression,
) -> Expression {
let range = query.range;
Located::new(
match query.value {
ExpressionKind::Input => ExpressionKind::Input,
ExpressionKind::Binding(name) => ExpressionKind::Binding(name),
ExpressionKind::Group(value) => {
ExpressionKind::Group(Box::new(map_native(*value, lower)))
}
ExpressionKind::Field { value, field } => ExpressionKind::Field {
value: Box::new(map_native(*value, lower)),
field,
},
ExpressionKind::Record(fields) => ExpressionKind::Record(
fields
.into_iter()
.map(|(name, value)| (name, map_native(value, lower)))
.collect(),
),
ExpressionKind::Set {
operator,
left,
right,
} => ExpressionKind::Set {
operator,
left: Box::new(map_native(*left, lower)),
right: Box::new(map_native(*right, lower)),
},
ExpressionKind::Sequence { input, steps } => {
let mut input = map_native(*input, lower);
for step in steps {
match step.value {
ExpressionStep::Stage(mut stage) => {
let mut leaf = match &mut stage {
ProgramNative::Query(query) => lower(
std::mem::replace(
query,
Query {
stages: Vec::new(),
range: 0..0,
nesting_limit: 1,
},
),
step.range.clone(),
),
ProgramNative::Mapping(mapping) => value_stage(
Stage::Mapping(native::MappingStage::new(zirql::NativeMap {
key: map_native(
std::mem::replace(
&mut mapping.key,
Located::new(ExpressionKind::Input, 0..0),
),
lower,
),
value: map_native(
std::mem::replace(
&mut mapping.value,
Located::new(ExpressionKind::Input, 0..0),
),
lower,
),
})),
step.range.clone(),
),
ProgramNative::Exists(body) | ProgramNative::FixedPoint(body) => {
let body = map_native(
*std::mem::replace(
body,
Box::new(Located::new(ExpressionKind::Input, 0..0)),
),
lower,
);
let body = native::BodyStage::new(body);
let stage = if matches!(stage, ProgramNative::Exists(_)) {
Stage::WhereExists(body)
} else {
Stage::FixedPoint(body)
};
value_stage(stage, step.range.clone())
}
ProgramNative::MapSorting => {
value_stage(Stage::MapSorting, step.range.clone())
}
ProgramNative::Ordering(ordering) => value_stage(
Stage::Ordering(native::OrderingStage::new(
zirql::NativeOrdering {
order: ordering.order,
selector: ordering
.selector
.take()
.map(|selector| map_native(selector, lower)),
},
)),
step.range.clone(),
),
};
let ExpressionKind::Sequence { steps, .. } = &mut leaf.value else {
unreachable!()
};
input = Located::new(
ExpressionKind::Sequence {
input: Box::new(input),
steps: std::mem::take(steps),
},
step.range,
);
}
ExpressionStep::Emit { .. } => {
unreachable!("typed emission rejected before lowering")
}
}
}
return input;
}
},
range,
)
}
pub(crate) fn map_queries<Q, R>(value: E<Q>, lower: &mut impl FnMut(Q) -> R) -> E<R> {
let range = value.range;
let value = match value.value {
K::CurrentInput => K::CurrentInput,
K::Literal(value) => K::Literal(value),
K::Name(name) => K::Name(name),
K::NativeQuery(query) => K::NativeQuery(Box::new(lower(*query))),
K::NativeValue(query) => K::NativeValue(Box::new(lower(*query))),
K::Call {
function,
arguments,
} => K::Call {
function,
arguments: arguments
.into_iter()
.map(|value| map_queries(value, lower))
.collect(),
},
K::Array(values) => K::Array(values.into_iter().map(|v| map_queries(v, lower)).collect()),
K::Tuple(values) => K::Tuple(values.into_iter().map(|v| map_queries(v, lower)).collect()),
K::Set(values) => K::Set(values.into_iter().map(|v| map_queries(v, lower)).collect()),
K::Dictionary(entries) => K::Dictionary(
entries
.into_iter()
.map(|(key, value)| (map_queries(key, lower), map_queries(value, lower)))
.collect(),
),
K::DictionaryComprehension { clauses, key, head } => K::DictionaryComprehension {
clauses: map_clauses(clauses, lower),
key: Box::new(map_queries(*key, lower)),
head: Box::new(map_queries(*head, lower)),
},
K::Record(fields) => K::Record(
fields
.into_iter()
.map(|(n, v)| (n, map_queries(v, lower)))
.collect(),
),
K::Block { bindings, value } => K::Block {
bindings: bindings
.into_iter()
.map(|(p, v)| (p, map_queries(v, lower)))
.collect(),
value: Box::new(map_queries(*value, lower)),
},
K::ValueMatch { subject, arms } => K::ValueMatch {
subject: Box::new(map_queries(*subject, lower)),
arms: arms
.into_iter()
.map(|arm| zirql::ValueMatchArm {
pattern: arm.pattern,
guard: arm.guard.map(|v| map_queries(v, lower)),
value: map_queries(arm.value, lower),
})
.collect(),
},
K::Conditional {
branches,
otherwise,
} => K::Conditional {
branches: branches
.into_iter()
.map(|(a, b)| (map_queries(a, lower), map_queries(b, lower)))
.collect(),
otherwise: Box::new(map_queries(*otherwise, lower)),
},
K::KeyedArray {
axes,
declared_axis_sources,
entries,
} => K::KeyedArray {
axes,
declared_axis_sources: declared_axis_sources
.map(|values| values.into_iter().map(|v| map_queries(v, lower)).collect()),
entries: entries
.into_iter()
.map(|(a, b)| (map_queries(a, lower), map_queries(b, lower)))
.collect(),
},
K::Comprehension {
constructor,
clauses,
head,
} => K::Comprehension {
constructor,
clauses: map_clauses(clauses, lower),
head: Box::new(map_queries(*head, lower)),
},
K::KeyedComprehension {
clauses,
key,
head,
declared_axis_sources,
empty_axis_sources,
} => K::KeyedComprehension {
declared_axis_sources: declared_axis_sources
.map(|values| values.into_iter().map(|v| map_queries(v, lower)).collect()),
clauses: map_clauses(clauses, lower),
key: Box::new(map_queries(*key, lower)),
head: Box::new(map_queries(*head, lower)),
empty_axis_sources: empty_axis_sources
.map(|values| values.into_iter().map(|v| map_queries(v, lower)).collect()),
},
K::Unique(value) => K::Unique(Box::new(map_queries(*value, lower))),
K::Tally(value) => K::Tally(Box::new(map_queries(*value, lower))),
K::Occurs(value) => K::Occurs(Box::new(map_queries(*value, lower))),
K::Absent(value) => K::Absent(Box::new(map_queries(*value, lower))),
K::One(value) => K::One(Box::new(map_queries(*value, lower))),
K::IndexSet(value) => K::IndexSet(Box::new(map_queries(*value, lower))),
K::Entries(value) => K::Entries(Box::new(map_queries(*value, lower))),
K::Unary { operator, operand } => K::Unary {
operator,
operand: Box::new(map_queries(*operand, lower)),
},
K::Binary {
operator,
left,
right,
} => K::Binary {
operator,
left: Box::new(map_queries(*left, lower)),
right: Box::new(map_queries(*right, lower)),
},
K::Field { value, field } => K::Field {
value: Box::new(map_queries(*value, lower)),
field,
},
K::TupleAccess { value, position } => K::TupleAccess {
value: Box::new(map_queries(*value, lower)),
position,
},
K::ArrayAccess { array, index } => K::ArrayAccess {
array: Box::new(map_queries(*array, lower)),
index: Box::new(map_queries(*index, lower)),
},
K::Lookup { value, key } => K::Lookup {
value: Box::new(map_queries(*value, lower)),
key: Box::new(map_queries(*key, lower)),
},
K::HasKey { value, key } => K::HasKey {
value: Box::new(map_queries(*value, lower)),
key: Box::new(map_queries(*key, lower)),
},
K::Default { value, fallback } => K::Default {
value: Box::new(map_queries(*value, lower)),
fallback: Box::new(map_queries(*fallback, lower)),
},
K::ZipExact { left, right } => K::ZipExact {
left: Box::new(map_queries(*left, lower)),
right: Box::new(map_queries(*right, lower)),
},
K::Reduce { operator, array } => K::Reduce {
operator,
array: Box::new(map_queries(*array, lower)),
},
K::PredicateSubject => K::PredicateSubject,
K::SubjectScope { subject, argument } => K::SubjectScope {
subject: Box::new(map_queries(*subject, lower)),
argument: zirql::PredicateArgument {
argument: argument.argument,
body: Box::new(map_queries(*argument.body, lower)),
},
},
K::PredicateScope { subject, predicate } => K::PredicateScope {
subject: Box::new(map_queries(*subject, lower)),
predicate: zirql::PredicateArgument {
argument: predicate.argument,
body: Box::new(map_queries(*predicate.body, lower)),
},
},
K::Filter { array, predicate } => K::Filter {
array: Box::new(map_queries(*array, lower)),
predicate: zirql::PredicateArgument {
argument: predicate.argument,
body: Box::new(map_queries(*predicate.body, lower)),
},
},
K::Sort {
array,
selector,
descending,
} => K::Sort {
array: Box::new(map_queries(*array, lower)),
selector,
descending,
},
K::Extremum {
operator,
array,
field,
} => K::Extremum {
operator,
array: Box::new(map_queries(*array, lower)),
field,
},
K::GroupBy { rows, field } => K::GroupBy {
rows: Box::new(map_queries(*rows, lower)),
field,
},
K::Require {
condition,
value,
message,
} => K::Require {
condition: Box::new(map_queries(*condition, lower)),
value: Box::new(map_queries(*value, lower)),
message,
},
K::Range {
lower: start,
upper,
} => K::Range {
lower: Box::new(map_queries(*start, lower)),
upper: Box::new(map_queries(*upper, lower)),
},
K::Queue { items, element } => K::Queue {
items: Box::new(map_queries(*items, lower)),
element,
},
K::PopFront(value) => K::PopFront(Box::new(map_queries(*value, lower))),
K::Break(value) => K::Break(Box::new(map_queries(*value, lower))),
K::Continue(values) => {
K::Continue(values.into_iter().map(|v| map_queries(v, lower)).collect())
}
K::PushBackAll { queue, items } => K::PushBackAll {
queue: Box::new(map_queries(*queue, lower)),
items: Box::new(map_queries(*items, lower)),
},
K::IdentitySet { element } => K::IdentitySet { element },
K::Insert { set, item } => K::Insert {
set: Box::new(map_queries(*set, lower)),
item: Box::new(map_queries(*item, lower)),
},
K::Fold {
items,
initial,
step,
} => K::Fold {
items: Box::new(map_queries(*items, lower)),
initial: Box::new(map_queries(*initial, lower)),
step,
},
K::Scan {
items,
initial,
step,
} => K::Scan {
items: Box::new(map_queries(*items, lower)),
initial: Box::new(map_queries(*initial, lower)),
step,
},
K::Loop { states, body } => K::Loop {
states: states
.into_iter()
.map(|(n, v)| (n, map_queries(v, lower)))
.collect(),
body: Box::new(map_queries(*body, lower)),
},
};
Located::new(value, range)
}
fn map_clauses<Q, R>(
clauses: Vec<Located<zirql::ValueGenerator<Q>>>,
lower: &mut impl FnMut(Q) -> R,
) -> Vec<Located<zirql::ValueGenerator<R>>> {
clauses
.into_iter()
.map(|clause| {
Located::new(
match clause.value {
zirql::ValueGenerator::In {
binders,
source,
filter,
} => zirql::ValueGenerator::In {
binders,
source: map_queries(source, lower),
filter: filter.map(|v| map_queries(v, lower)),
},
zirql::ValueGenerator::Bind {
binder,
value,
filter,
} => zirql::ValueGenerator::Bind {
binder,
value: map_queries(value, lower),
filter: filter.map(|v| map_queries(v, lower)),
},
},
clause.range,
)
})
.collect()
}
fn check_native(
query: &zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>,
budget: &mut Budget,
nesting: usize,
) -> Result<(), QueryError> {
let mut pending = vec![(query, 0)];
while let Some((query, depth)) = pending.pop() {
if depth >= nesting.min(64) {
return Err(QueryError::query(
query.range.clone(),
"native expression nesting limit exceeded",
));
}
budget
.charge(1, zirql::Location::Query(query.range.clone()))
.map_err(pipeline::core_error)?;
match &query.value {
ExpressionKind::Input | ExpressionKind::Binding(_) => {}
ExpressionKind::Group(value) | ExpressionKind::Field { value, .. } => {
pending.push((value, depth + 1))
}
ExpressionKind::Record(fields) => {
let location = zirql::Location::Query(query.range.clone());
budget
.charge(fields.len(), location.clone())
.map_err(pipeline::core_error)?;
budget
.charge_results(fields.len(), location)
.map_err(pipeline::core_error)?;
pending.extend(fields.iter().map(|(_, v)| (v, depth + 1)))
}
ExpressionKind::Set { left, right, .. } => {
pending.push((left, depth + 1));
pending.push((right, depth + 1));
}
ExpressionKind::Sequence { input, steps } => {
pending.push((input, depth + 1));
for step in steps {
budget
.charge_results(1, zirql::Location::Query(step.range.clone()))
.map_err(pipeline::core_error)?;
match &step.value {
ExpressionStep::Stage(ProgramNative::Query(query))
if !query.is_editing()
&& !query.stages.iter().any(|stage| {
matches!(
stage.kind,
language::StageKind::Emit
| language::StageKind::Json
| language::StageKind::EmitDocument
)
}) =>
{
budget
.charge(
query.range.len() + query.stages.len(),
zirql::Location::Query(step.range.clone()),
)
.map_err(pipeline::core_error)?;
budget
.charge_results(
query.stages.len(),
zirql::Location::Query(step.range.clone()),
)
.map_err(pipeline::core_error)?;
}
ExpressionStep::Stage(ProgramNative::Mapping(mapping)) => {
pending.push((&mapping.key, depth + 1));
pending.push((&mapping.value, depth + 1));
}
ExpressionStep::Stage(
ProgramNative::Exists(body) | ProgramNative::FixedPoint(body),
) => pending.push((body, depth + 1)),
ExpressionStep::Stage(ProgramNative::MapSorting) => {}
ExpressionStep::Stage(ProgramNative::Ordering(ordering)) => {
if let Some(selector) = &ordering.selector {
pending.push((selector, depth + 1));
}
}
ExpressionStep::Stage(_) | ExpressionStep::Emit { .. } => {
return Err(QueryError::query(
step.range.clone(),
"typed report values must be pure",
));
}
}
}
}
}
}
Ok(())
}
fn dispose_signature(signature: zirql::SignatureType) {
use zirql::SignatureType as S;
let mut pending = vec![signature];
while let Some(signature) = pending.pop() {
match signature {
S::Optional(value)
| S::Sequence(value)
| S::Set(value)
| S::Keyed(value)
| S::Queue(value)
| S::IdentitySet(value) => pending.push(value.value),
S::Dictionary(key, value) => {
pending.push(key.value);
pending.push(value.value);
}
S::Tuple(values) => pending.extend(values.into_iter().map(|value| value.value)),
S::Record(fields) => pending.extend(fields.into_iter().map(|(_, value)| value.value)),
S::Value | S::Boolean | S::Integer | S::Float | S::String | S::Named(_) => {}
}
}
}