Skip to main content

zincite_query/program/
builder.rs

1//! Owned typed values lower directly to the same prepared report program.
2use super::*;
3use zirql::ValueExpression as E;
4
5/// A typed value whose native leaves are shared expression wrappers around Query.
6/// The wrapper permits flat native pipelines to be released iteratively.
7pub type ProgramValue = E<zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>>;
8/// A typed pure function with located pattern parameters and unresolved host types.
9pub type ProgramFunction = zirql::UnresolvedPatternFunctionDeclaration<
10    zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>,
11>;
12
13/// A native MiniZinc query leaf. Common composition uses Zirql expression steps.
14#[derive(Debug)]
15pub enum ProgramNative {
16    Query(Query),
17}
18
19struct Inputs {
20    functions: Vec<ProgramFunction>,
21    bindings: Vec<(zirql::Pattern, ProgramValue)>,
22    report: Option<ProgramValue>,
23}
24impl Drop for Inputs {
25    fn drop(&mut self) {
26        for mut function in std::mem::take(&mut self.functions) {
27            for parameter in &mut function.parameters {
28                dispose_signature(std::mem::replace(
29                    &mut parameter.value.declaration.value,
30                    zirql::SignatureType::Integer,
31                ));
32            }
33            dispose_signature(std::mem::replace(
34                &mut function.result.value,
35                zirql::SignatureType::Integer,
36            ));
37            zirql::dispose_unresolved_pattern_functions(vec![function]);
38        }
39        for (pattern, value) in std::mem::take(&mut self.bindings) {
40            zirql::dispose_pattern(pattern);
41            zirql::dispose_native_value(value);
42        }
43        if let Some(value) = self.report.take() {
44            zirql::dispose_native_value(value);
45        }
46    }
47}
48impl Program {
49    /// Construct a pure report without parsing generated source. Native leaves
50    /// use shared expressions with `ProgramNative` stages and native set
51    /// operators. Query leaves own domain syntax; ordering selectors retain
52    /// their singleton input and the caller's query range.
53    ///
54    /// All input trees transfer to this call, including on failure. Preparation
55    /// against the current input/model happens during ordinary evaluation.
56    /// Shared syntax inside a native operand must have one owner:
57    /// lowering consumes its opaque native payloads. Sharing runtime values does
58    /// not impose this syntax-ownership requirement.
59    ///
60    /// # Errors
61    /// Unsupported constructs, duplicate names and exhausted construction bounds
62    /// retain caller-supplied ranges. Shared syntax with multiple owners, direct
63    /// filter, match and path payloads and emission are refused before lowering.
64    /// Evaluation also checks scope, purity,
65    /// pattern compatibility and native types before executing statements.
66    pub fn from_values(
67        functions: Vec<ProgramFunction>,
68        bindings: Vec<(zirql::Pattern, ProgramValue)>,
69        final_report: ProgramValue,
70        limits: ProgramLimits,
71    ) -> Result<Self, QueryError> {
72        let mut inputs = Inputs {
73            functions,
74            bindings,
75            report: Some(final_report),
76        };
77        let mut budget = Budget::for_program(limits.core());
78        let range = inputs.report.as_ref().unwrap().range.clone();
79        let mut names = std::collections::BTreeSet::new();
80        for function in &inputs.functions {
81            functions::check_signature(&function.result, &mut budget, limits.nesting)?;
82            for parameter in &function.parameters {
83                functions::check_signature(
84                    &parameter.value.declaration,
85                    &mut budget,
86                    limits.nesting,
87                )?;
88            }
89            for query in functions::check_value(&function.value, &mut budget, limits.nesting)? {
90                check_native(query, &mut budget, limits.nesting)?;
91            }
92        }
93        for (pattern, value) in &inputs.bindings {
94            let selected =
95                zirql::pattern_names(pattern, &mut budget).map_err(pipeline::core_error)?;
96            for name in &selected {
97                if !names.insert(name.value.clone()) {
98                    return Err(QueryError::query(
99                        name.range.clone(),
100                        "binding is already defined",
101                    ));
102                }
103            }
104            for query in functions::check_value(value, &mut budget, limits.nesting)? {
105                check_native(query, &mut budget, limits.nesting)?;
106            }
107        }
108        for query in
109            functions::check_value(inputs.report.as_ref().unwrap(), &mut budget, limits.nesting)?
110        {
111            check_native(query, &mut budget, limits.nesting)?;
112        }
113        let mut queries = Vec::new();
114        let mut lower = |query, range: Range<usize>| {
115            let index = queries.len();
116            queries.push(ProgramQuery {
117                query,
118                arguments: Rc::new(Vec::new()),
119                copy_units: range.len() + 1,
120            });
121            Stage::Query(index, Rc::new(Vec::new()))
122        };
123        let mut functions = std::mem::take(&mut inputs.functions)
124            .into_iter()
125            .map(|function| zirql::UnresolvedPatternFunctionDeclaration {
126                name: function.name,
127                parameters: function.parameters,
128                result: function.result,
129                value: zirql::map_value_payloads(function.value, &mut |query| {
130                    map_native(query, &mut lower)
131                }),
132            })
133            .collect::<Vec<_>>();
134        let mut statements = Vec::new();
135        for (pattern, value) in std::mem::take(&mut inputs.bindings) {
136            let range = value.range.clone();
137            statements.push(Located::new(
138                zirql::Statement::BindPattern {
139                    pattern: Arc::new(pattern),
140                    value: zirql::map_value_payloads(value, &mut |query| {
141                        map_native(query, &mut lower)
142                    })
143                    .into(),
144                },
145                range,
146            ));
147        }
148        statements.push(Located::new(
149            zirql::Statement::Evaluate {
150                expression: core_value(
151                    zirql::map_value_payloads(inputs.report.take().unwrap(), &mut |query| {
152                        map_native(query, &mut lower)
153                    }),
154                    range.clone(),
155                ),
156                implicit_output: Some(Emitter::Json),
157            },
158            range.clone(),
159        ));
160        for function in &mut functions {
161            functions::normalize_value(&mut function.value, &BTreeMap::new(), &queries);
162        }
163        for statement in &mut statements {
164            let expression = match &mut statement.value {
165                zirql::Statement::Bind { expression, .. }
166                | zirql::Statement::BindFields { expression, .. }
167                | zirql::Statement::Evaluate { expression, .. } => expression,
168                zirql::Statement::BindPattern { value, .. } => {
169                    functions::normalize_value(
170                        Arc::get_mut(value).expect("builder-owned value"),
171                        &BTreeMap::new(),
172                        &queries,
173                    );
174                    continue;
175                }
176                zirql::Statement::Parameter { .. } => continue,
177            };
178            if let ExpressionKind::Sequence { steps, .. } = &mut expression.value {
179                for step in steps {
180                    let value = match &mut step.value {
181                        ExpressionStep::Value(value) => {
182                            Arc::get_mut(value).expect("builder-owned value")
183                        }
184                        _ => continue,
185                    };
186                    functions::normalize_value(value, &BTreeMap::new(), &queries);
187                }
188            }
189        }
190        let report_model_demand = queries
191            .iter()
192            .any(|query| query.query.requires_model_facts())
193            || functions.iter().any(|function| {
194                functions::signature_needs_model(&function.result.value)
195                    || function.parameters.iter().any(|parameter| {
196                        functions::signature_needs_model(&parameter.value.declaration.value)
197                    })
198            });
199        Ok(Self {
200            parameters: Vec::new(),
201            program: zirql::Program::new(statements, range.clone()),
202            preflight_work: limits.work - budget.remaining_work(),
203            preflight_results: limits.results - budget.remaining_results().unwrap(),
204            preflight_range: Some(range),
205            queries,
206            functions,
207            function_limits: zirql::FunctionLimits { active_depth: 64 },
208            report_model_demand,
209            editing: false,
210            prepared: true,
211            prepared_values: false,
212        })
213    }
214}
215
216type TypedExpression = zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>;
217
218fn map_native(
219    query: TypedExpression,
220    lower: &mut impl FnMut(Query, Range<usize>) -> Stage,
221) -> Expression {
222    let range = query.range;
223    Located::new(
224        match query.value {
225            ExpressionKind::Input => ExpressionKind::Input,
226            ExpressionKind::Binding(name) => ExpressionKind::Binding(name),
227            ExpressionKind::Group(value) => {
228                ExpressionKind::Group(Box::new(map_native(*value, lower)))
229            }
230            ExpressionKind::Field { value, field } => ExpressionKind::Field {
231                value: Box::new(map_native(*value, lower)),
232                field,
233            },
234            ExpressionKind::Record(fields) => ExpressionKind::Record(
235                fields
236                    .into_iter()
237                    .map(|(name, value)| (name, map_native(value, lower)))
238                    .collect(),
239            ),
240            ExpressionKind::Set {
241                operator,
242                left,
243                right,
244            } => ExpressionKind::Set {
245                operator,
246                left: Box::new(map_native(*left, lower)),
247                right: Box::new(map_native(*right, lower)),
248            },
249            ExpressionKind::Sequence { input, steps } => ExpressionKind::Sequence {
250                input: Box::new(map_native(*input, lower)),
251                steps: steps
252                    .into_iter()
253                    .map(|step| {
254                        let range = step.range;
255                        Located::new(map_step(step.value, lower, &range), range)
256                    })
257                    .collect(),
258            },
259        },
260        range,
261    )
262}
263
264fn map_body(
265    body: zirql::NativeBody<TypedExpression>,
266    lower: &mut impl FnMut(Query, Range<usize>) -> Stage,
267) -> zirql::NativeBody<Expression> {
268    let value = body.into_value();
269    let zirql::ValueExpressionKind::NativeValue(query) = value.value else {
270        unreachable!("native body")
271    };
272    zirql::NativeBody::new(map_native(*query, lower), value.range)
273}
274
275fn map_step(
276    step: zirql::ExpressionStep<ProgramNative, (), zirql::NativeSetOperator>,
277    lower: &mut impl FnMut(Query, Range<usize>) -> Stage,
278    range: &Range<usize>,
279) -> ExpressionStep<Stage, Emitter, zirql::NativeSetOperator> {
280    let mut map_value =
281        |value| zirql::map_value_payloads(value, &mut |query| map_native(query, lower));
282    match step {
283        ExpressionStep::Value(value) => ExpressionStep::Value(
284            map_value(Arc::try_unwrap(value).expect("builder-owned value")).into(),
285        ),
286        ExpressionStep::Head(value) => ExpressionStep::Head(
287            map_value(Arc::try_unwrap(value).expect("builder-owned bound")).into(),
288        ),
289        ExpressionStep::Tail(value) => ExpressionStep::Tail(
290            map_value(Arc::try_unwrap(value).expect("builder-owned bound")).into(),
291        ),
292        ExpressionStep::Check { operand, message } => ExpressionStep::Check {
293            operand: operand.map(|value| {
294                map_value(Arc::try_unwrap(value).expect("builder-owned check operand")).into()
295            }),
296            message: message.map(|value| {
297                map_value(Arc::try_unwrap(value).expect("builder-owned check message")).into()
298            }),
299        },
300        ExpressionStep::Projection(projection) => {
301            let projection = Arc::try_unwrap(projection).expect("builder-owned projection");
302            ExpressionStep::Projection(
303                zirql::Projection {
304                    subject: projection.subject,
305                    fields: projection
306                        .fields
307                        .into_iter()
308                        .map(|(label, value)| (label, map_value(value)))
309                        .collect(),
310                }
311                .into(),
312            )
313        }
314        ExpressionStep::Ordering(ordering) => {
315            let ordering = Arc::try_unwrap(ordering).expect("builder-owned ordering");
316            ExpressionStep::Ordering(
317                zirql::NativeOrdering {
318                    order: ordering.order,
319                    selector: ordering.selector.map(|body| map_body(body, lower)),
320                }
321                .into(),
322            )
323        }
324        ExpressionStep::Mapping(mapping) => {
325            let mapping = Arc::try_unwrap(mapping).expect("builder-owned mapping");
326            ExpressionStep::Mapping(
327                zirql::NativeMap {
328                    key: map_body(mapping.key, lower),
329                    value: map_body(mapping.value, lower),
330                }
331                .into(),
332            )
333        }
334        ExpressionStep::Group(body) => ExpressionStep::Group(
335            map_body(Arc::try_unwrap(body).expect("builder-owned group"), lower).into(),
336        ),
337        ExpressionStep::WhereExists(body) => ExpressionStep::WhereExists(
338            map_body(
339                Arc::try_unwrap(body).expect("builder-owned relation"),
340                lower,
341            )
342            .into(),
343        ),
344        ExpressionStep::FixedPoint(body) => ExpressionStep::FixedPoint(
345            map_body(Arc::try_unwrap(body).expect("builder-owned body"), lower).into(),
346        ),
347        ExpressionStep::MapSorting => ExpressionStep::MapSorting,
348        ExpressionStep::Collection(operation) => ExpressionStep::Collection(operation),
349        ExpressionStep::Stage(ProgramNative::Query(query)) => {
350            ExpressionStep::Stage(lower(query, range.clone()))
351        }
352        ExpressionStep::Filter(_)
353        | ExpressionStep::Match(_)
354        | ExpressionStep::Paths(_)
355        | ExpressionStep::Emit { .. } => {
356            unreachable!("unsupported typed payload rejected before lowering")
357        }
358    }
359}
360
361fn check_native(
362    query: &zirql::Expression<ProgramNative, zirql::NativeSetOperator, ()>,
363    budget: &mut Budget,
364    nesting: usize,
365) -> Result<(), QueryError> {
366    zirql::walk_expression(query, budget, nesting, |_, _| Ok(())).map_err(pipeline::core_error)?;
367    let mut pending = vec![(query, 0)];
368    while let Some((query, depth)) = pending.pop() {
369        if depth >= nesting.min(64) {
370            return Err(QueryError::query(
371                query.range.clone(),
372                "native expression nesting limit exceeded",
373            ));
374        }
375        budget
376            .charge(1, zirql::Location::Query(query.range.clone()))
377            .map_err(pipeline::core_error)?;
378        match &query.value {
379            ExpressionKind::Input | ExpressionKind::Binding(_) => {}
380            ExpressionKind::Group(value) | ExpressionKind::Field { value, .. } => {
381                pending.push((value, depth + 1))
382            }
383            ExpressionKind::Record(fields) => {
384                let location = zirql::Location::Query(query.range.clone());
385                budget
386                    .charge(fields.len(), location.clone())
387                    .map_err(pipeline::core_error)?;
388                budget
389                    .charge_results(fields.len(), location)
390                    .map_err(pipeline::core_error)?;
391                pending.extend(fields.iter().map(|(_, v)| (v, depth + 1)))
392            }
393            ExpressionKind::Set { left, right, .. } => {
394                pending.push((left, depth + 1));
395                pending.push((right, depth + 1));
396            }
397            ExpressionKind::Sequence { input, steps } => {
398                pending.push((input, depth + 1));
399                for step in steps {
400                    budget
401                        .charge_results(1, zirql::Location::Query(step.range.clone()))
402                        .map_err(pipeline::core_error)?;
403                    let owners = match &step.value {
404                        ExpressionStep::Value(value)
405                        | ExpressionStep::Head(value)
406                        | ExpressionStep::Tail(value) => Arc::strong_count(value),
407                        ExpressionStep::Check { operand, message } => operand
408                            .iter()
409                            .chain(message)
410                            .map(Arc::strong_count)
411                            .max()
412                            .unwrap_or(1),
413                        ExpressionStep::Projection(value) => Arc::strong_count(value),
414                        ExpressionStep::Ordering(value) => Arc::strong_count(value),
415                        ExpressionStep::Mapping(value) => Arc::strong_count(value),
416                        ExpressionStep::Group(value)
417                        | ExpressionStep::WhereExists(value)
418                        | ExpressionStep::FixedPoint(value) => Arc::strong_count(value),
419                        ExpressionStep::Filter(_)
420                        | ExpressionStep::Match(_)
421                        | ExpressionStep::Paths(_) => {
422                            return Err(QueryError::query(
423                                step.range.clone(),
424                                "typed native filter, match and path payloads are unsupported",
425                            ));
426                        }
427                        ExpressionStep::Emit { .. } => {
428                            return Err(QueryError::query(
429                                step.range.clone(),
430                                "typed report values must be pure",
431                            ));
432                        }
433                        _ => 1,
434                    };
435                    if owners != 1 {
436                        return Err(QueryError::query(
437                            step.range.clone(),
438                            "typed native syntax must have one owner before lowering",
439                        ));
440                    }
441                    let mut checked = Ok(());
442                    step.value.for_each_value(|value| {
443                        if checked.is_ok() {
444                            checked = functions::check_value(
445                                value,
446                                budget,
447                                nesting.saturating_sub(depth + 1),
448                            )
449                            .map(|queries| {
450                                pending.extend(queries.into_iter().map(|query| (query, depth + 1)))
451                            });
452                        }
453                    });
454                    checked?;
455                    let mut checked = Ok(());
456                    step.value.for_each_payload(|ProgramNative::Query(query)| {
457                        if checked.is_err() {
458                            return;
459                        }
460                        checked = check_query(query, budget, &step.range);
461                    });
462                    checked?;
463                }
464            }
465        }
466    }
467    Ok(())
468}
469
470fn check_query(query: &Query, budget: &mut Budget, range: &Range<usize>) -> Result<(), QueryError> {
471    if query.is_editing()
472        || query.stages.iter().any(|stage| {
473            matches!(
474                stage.kind,
475                language::StageKind::Emit
476                    | language::StageKind::Json
477                    | language::StageKind::EmitDocument
478            )
479        })
480    {
481        return Err(QueryError::query(
482            range.clone(),
483            "typed report values must be pure",
484        ));
485    }
486    let location = zirql::Location::Query(range.clone());
487    budget
488        .charge(query.range.len() + query.stages.len(), location.clone())
489        .map_err(pipeline::core_error)?;
490    budget
491        .charge_results(query.stages.len(), location)
492        .map_err(pipeline::core_error)
493}
494
495fn dispose_signature(signature: zirql::SignatureType) {
496    use zirql::SignatureType as S;
497    let mut pending = vec![signature];
498    while let Some(signature) = pending.pop() {
499        match signature {
500            S::Optional(value)
501            | S::Sequence(value)
502            | S::Set(value)
503            | S::Keyed(value)
504            | S::Queue(value)
505            | S::IdentitySet(value) => pending.push(value.value),
506            S::Dictionary(key, value) => {
507                pending.push(key.value);
508                pending.push(value.value);
509            }
510            S::Tuple(values) => pending.extend(values.into_iter().map(|value| value.value)),
511            S::Record(fields) => pending.extend(fields.into_iter().map(|(_, value)| value.value)),
512            S::Value | S::Boolean | S::Integer | S::Float | S::String | S::Named(_) => {}
513        }
514    }
515}