graphql_client_codegen/
query.rs

1//! The responsibility of this module is to bind and validate a query
2//! against a given schema.
3
4mod fragments;
5mod operations;
6mod selection;
7mod validation;
8
9pub(crate) use fragments::{fragment_is_recursive, ResolvedFragment};
10pub(crate) use operations::ResolvedOperation;
11pub(crate) use selection::*;
12
13use crate::{
14    constants::TYPENAME_FIELD,
15    normalization::Normalization,
16    schema::{
17        resolve_field_type, EnumId, InputId, ScalarId, Schema, StoredEnum, StoredFieldType,
18        StoredInputType, StoredScalar, TypeId, UnionId,
19    },
20};
21use std::{
22    collections::{BTreeMap, BTreeSet},
23    fmt::Display,
24};
25
26#[derive(Debug)]
27pub(crate) struct QueryValidationError {
28    message: String,
29}
30
31impl Display for QueryValidationError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.write_str(&self.message)
34    }
35}
36
37impl std::error::Error for QueryValidationError {}
38
39impl QueryValidationError {
40    pub(crate) fn new(message: String) -> Self {
41        QueryValidationError { message }
42    }
43}
44
45#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
46pub(crate) struct SelectionId(u32);
47#[derive(Debug, Clone, Copy, PartialEq)]
48pub(crate) struct OperationId(u32);
49
50impl OperationId {
51    pub(crate) fn new(idx: usize) -> Self {
52        OperationId(idx as u32)
53    }
54}
55
56#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
57pub(crate) struct ResolvedFragmentId(u32);
58
59#[allow(dead_code)]
60#[derive(Debug, Clone, Copy)]
61pub(crate) struct VariableId(u32);
62
63pub(crate) fn resolve<'doc, T>(
64    schema: &Schema,
65    query: &graphql_parser::query::Document<'doc, T>,
66) -> Result<Query, QueryValidationError>
67where
68    T: graphql_parser::query::Text<'doc>,
69{
70    let mut resolved_query: Query = Default::default();
71
72    create_roots(&mut resolved_query, query, schema)?;
73
74    // Then resolve the selections.
75    for definition in &query.definitions {
76        match definition {
77            graphql_parser::query::Definition::Fragment(fragment) => {
78                resolve_fragment(&mut resolved_query, schema, fragment)?
79            }
80            graphql_parser::query::Definition::Operation(operation) => {
81                resolve_operation(&mut resolved_query, schema, operation)?
82            }
83        }
84    }
85
86    // Validation: to be expanded and factored out.
87    validation::validate_typename_presence(&BoundQuery {
88        query: &resolved_query,
89        schema,
90    })?;
91
92    for (selection_id, _) in resolved_query.selections() {
93        selection::validate_type_conditions(
94            selection_id,
95            &BoundQuery {
96                query: &resolved_query,
97                schema,
98            },
99        )?
100    }
101
102    Ok(resolved_query)
103}
104
105fn create_roots<'doc, T>(
106    resolved_query: &mut Query,
107    query: &graphql_parser::query::Document<'doc, T>,
108    schema: &Schema,
109) -> Result<(), QueryValidationError>
110where
111    T: graphql_parser::query::Text<'doc>,
112{
113    // First, give ids to all fragments and operations.
114    for definition in &query.definitions {
115        match definition {
116            graphql_parser::query::Definition::Fragment(fragment) => {
117                let graphql_parser::query::TypeCondition::On(on) = &fragment.type_condition;
118                resolved_query.fragments.push(ResolvedFragment {
119                    name: fragment.name.as_ref().into(),
120                    on: schema.find_type(on.as_ref()).ok_or_else(|| {
121                        QueryValidationError::new(format!(
122                            "Could not find type {} for fragment {} in schema.",
123                            on.as_ref(),
124                            fragment.name.as_ref(),
125                        ))
126                    })?,
127                    selection_set: Vec::new(),
128                });
129            }
130            graphql_parser::query::Definition::Operation(
131                graphql_parser::query::OperationDefinition::Mutation(m),
132            ) => {
133                let on = schema.mutation_type().ok_or_else(|| {
134                    QueryValidationError::new(
135                        "Query contains a mutation operation, but the schema has no mutation type."
136                            .to_owned(),
137                    )
138                })?;
139                let resolved_operation: ResolvedOperation = ResolvedOperation {
140                    object_id: on,
141                    name: m
142                        .name
143                        .as_ref()
144                        .expect("mutation without name")
145                        .as_ref()
146                        .into(),
147                    _operation_type: operations::OperationType::Mutation,
148                    selection_set: Vec::with_capacity(m.selection_set.items.len()),
149                };
150
151                resolved_query.operations.push(resolved_operation);
152            }
153            graphql_parser::query::Definition::Operation(
154                graphql_parser::query::OperationDefinition::Query(q),
155            ) => {
156                let on = schema.query_type();
157                let resolved_operation: ResolvedOperation = ResolvedOperation {
158                    name: q.name.as_ref().expect("query without name. Instead of `query (...)`, write `query SomeName(...)` in your .graphql file").as_ref().into(),
159                    _operation_type: operations::OperationType::Query,
160                    object_id: on,
161                    selection_set: Vec::with_capacity(q.selection_set.items.len()),
162                };
163
164                resolved_query.operations.push(resolved_operation);
165            }
166            graphql_parser::query::Definition::Operation(
167                graphql_parser::query::OperationDefinition::Subscription(s),
168            ) => {
169                let on = schema.subscription_type().ok_or_else(|| {
170                    QueryValidationError::new(
171                        "Query contains a subscription operation, but the schema has no subscription type.".to_owned()
172                    )
173                })?;
174
175                if s.selection_set.items.len() != 1 {
176                    return Err(QueryValidationError::new(
177                        crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR.to_owned(),
178                    ));
179                }
180
181                let resolved_operation: ResolvedOperation = ResolvedOperation {
182                    name: s
183                        .name
184                        .as_ref()
185                        .expect("subscription without name")
186                        .as_ref()
187                        .into(),
188                    _operation_type: operations::OperationType::Subscription,
189                    object_id: on,
190                    selection_set: Vec::with_capacity(s.selection_set.items.len()),
191                };
192
193                resolved_query.operations.push(resolved_operation);
194            }
195            graphql_parser::query::Definition::Operation(
196                graphql_parser::query::OperationDefinition::SelectionSet(_),
197            ) => {
198                return Err(QueryValidationError::new(
199                    crate::constants::SELECTION_SET_AT_ROOT.to_owned(),
200                ))
201            }
202        }
203    }
204
205    Ok(())
206}
207
208fn resolve_fragment<'doc, T>(
209    query: &mut Query,
210    schema: &Schema,
211    fragment_definition: &graphql_parser::query::FragmentDefinition<'doc, T>,
212) -> Result<(), QueryValidationError>
213where
214    T: graphql_parser::query::Text<'doc>,
215{
216    let graphql_parser::query::TypeCondition::On(on) = &fragment_definition.type_condition;
217    let on = schema.find_type(on.as_ref()).ok_or_else(|| {
218        QueryValidationError::new(format!(
219            "Could not find type `{}` referenced by fragment `{}`",
220            on.as_ref(),
221            fragment_definition.name.as_ref(),
222        ))
223    })?;
224
225    let (id, _) = query
226        .find_fragment(fragment_definition.name.as_ref())
227        .ok_or_else(|| {
228            QueryValidationError::new(format!(
229                "Could not find fragment `{}`.",
230                fragment_definition.name.as_ref()
231            ))
232        })?;
233
234    resolve_selection(
235        query,
236        on,
237        &fragment_definition.selection_set,
238        SelectionParent::Fragment(id),
239        schema,
240    )?;
241
242    Ok(())
243}
244
245fn resolve_union_selection<'doc, T>(
246    query: &mut Query,
247    _union_id: UnionId,
248    selection_set: &graphql_parser::query::SelectionSet<'doc, T>,
249    parent: SelectionParent,
250    schema: &Schema,
251) -> Result<(), QueryValidationError>
252where
253    T: graphql_parser::query::Text<'doc>,
254{
255    for item in selection_set.items.iter() {
256        match item {
257            graphql_parser::query::Selection::Field(field) => {
258                if field.name.as_ref() == TYPENAME_FIELD {
259                    let id = query.push_selection(Selection::Typename, parent);
260                    parent.add_to_selection_set(query, id);
261                } else {
262                    return Err(QueryValidationError::new(format!(
263                        "Invalid field selection on union field ({:?})",
264                        parent
265                    )));
266                }
267            }
268            graphql_parser::query::Selection::InlineFragment(inline_fragment) => {
269                let selection_id = resolve_inline_fragment(query, schema, inline_fragment, parent)?;
270                parent.add_to_selection_set(query, selection_id);
271            }
272            graphql_parser::query::Selection::FragmentSpread(fragment_spread) => {
273                let (fragment_id, _fragment) = query
274                    .find_fragment(fragment_spread.fragment_name.as_ref())
275                    .ok_or_else(|| {
276                        QueryValidationError::new(format!(
277                            "Could not find fragment `{}` referenced by fragment spread.",
278                            fragment_spread.fragment_name.as_ref()
279                        ))
280                    })?;
281
282                let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent);
283
284                parent.add_to_selection_set(query, id);
285            }
286        }
287    }
288
289    Ok(())
290}
291
292fn resolve_object_selection<'a, 'doc, T>(
293    query: &mut Query,
294    object: &dyn crate::schema::ObjectLike,
295    selection_set: &graphql_parser::query::SelectionSet<'doc, T>,
296    parent: SelectionParent,
297    schema: &'a Schema,
298) -> Result<(), QueryValidationError>
299where
300    T: graphql_parser::query::Text<'doc>,
301{
302    for item in selection_set.items.iter() {
303        match item {
304            graphql_parser::query::Selection::Field(field) => {
305                if field.name.as_ref() == TYPENAME_FIELD {
306                    let id = query.push_selection(Selection::Typename, parent);
307                    parent.add_to_selection_set(query, id);
308                    continue;
309                }
310
311                let (field_id, schema_field) = object
312                    .get_field_by_name(field.name.as_ref(), schema)
313                    .ok_or_else(|| {
314                        QueryValidationError::new(format!(
315                            "No field named {} on {}",
316                            field.name.as_ref(),
317                            object.name()
318                        ))
319                    })?;
320
321                let id = query.push_selection(
322                    Selection::Field(SelectedField {
323                        alias: field.alias.as_ref().map(|alias| alias.as_ref().into()),
324                        field_id,
325                        selection_set: Vec::with_capacity(selection_set.items.len()),
326                    }),
327                    parent,
328                );
329
330                resolve_selection(
331                    query,
332                    schema_field.r#type.id,
333                    &field.selection_set,
334                    SelectionParent::Field(id),
335                    schema,
336                )?;
337
338                parent.add_to_selection_set(query, id);
339            }
340            graphql_parser::query::Selection::InlineFragment(inline) => {
341                let selection_id = resolve_inline_fragment(query, schema, inline, parent)?;
342
343                parent.add_to_selection_set(query, selection_id);
344            }
345            graphql_parser::query::Selection::FragmentSpread(fragment_spread) => {
346                let (fragment_id, _fragment) = query
347                    .find_fragment(fragment_spread.fragment_name.as_ref())
348                    .ok_or_else(|| {
349                        QueryValidationError::new(format!(
350                            "Could not find fragment `{}` referenced by fragment spread.",
351                            fragment_spread.fragment_name.as_ref()
352                        ))
353                    })?;
354
355                let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent);
356
357                parent.add_to_selection_set(query, id);
358            }
359        }
360    }
361
362    Ok(())
363}
364
365fn resolve_selection<'doc, T>(
366    ctx: &mut Query,
367    on: TypeId,
368    selection_set: &graphql_parser::query::SelectionSet<'doc, T>,
369    parent: SelectionParent,
370    schema: &Schema,
371) -> Result<(), QueryValidationError>
372where
373    T: graphql_parser::query::Text<'doc>,
374{
375    match on {
376        TypeId::Object(oid) => {
377            let object = schema.get_object(oid);
378            resolve_object_selection(ctx, object, selection_set, parent, schema)?;
379        }
380        TypeId::Interface(interface_id) => {
381            let interface = schema.get_interface(interface_id);
382            resolve_object_selection(ctx, interface, selection_set, parent, schema)?;
383        }
384        TypeId::Union(union_id) => {
385            resolve_union_selection(ctx, union_id, selection_set, parent, schema)?;
386        }
387        other => {
388            if !selection_set.items.is_empty() {
389                return Err(QueryValidationError::new(format!(
390                    "Selection set on non-object, non-interface type. ({:?})",
391                    other
392                )));
393            }
394        }
395    };
396
397    Ok(())
398}
399
400fn resolve_inline_fragment<'doc, T>(
401    query: &mut Query,
402    schema: &Schema,
403    inline_fragment: &graphql_parser::query::InlineFragment<'doc, T>,
404    parent: SelectionParent,
405) -> Result<SelectionId, QueryValidationError>
406where
407    T: graphql_parser::query::Text<'doc>,
408{
409    let graphql_parser::query::TypeCondition::On(on) = inline_fragment
410        .type_condition
411        .as_ref()
412        .expect("missing type condition on inline fragment");
413    let type_id = schema.find_type(on.as_ref()).ok_or_else(|| {
414        QueryValidationError::new(format!(
415            "Could not find type `{}` referenced by inline fragment.",
416            on.as_ref()
417        ))
418    })?;
419
420    let id = query.push_selection(
421        Selection::InlineFragment(InlineFragment {
422            type_id,
423            selection_set: Vec::with_capacity(inline_fragment.selection_set.items.len()),
424        }),
425        parent,
426    );
427
428    resolve_selection(
429        query,
430        type_id,
431        &inline_fragment.selection_set,
432        SelectionParent::InlineFragment(id),
433        schema,
434    )?;
435
436    Ok(id)
437}
438
439fn resolve_operation<'doc, T>(
440    query: &mut Query,
441    schema: &Schema,
442    operation: &graphql_parser::query::OperationDefinition<'doc, T>,
443) -> Result<(), QueryValidationError>
444where
445    T: graphql_parser::query::Text<'doc>,
446{
447    match operation {
448        graphql_parser::query::OperationDefinition::Mutation(m) => {
449            let on = schema.mutation_type().ok_or_else(|| {
450                QueryValidationError::new(
451                    "Query contains a mutation operation, but the schema has no mutation type."
452                        .to_owned(),
453                )
454            })?;
455            let on = schema.get_object(on);
456
457            let (id, _) = query
458                .find_operation(m.name.as_ref().map(|name| name.as_ref()).unwrap())
459                .unwrap();
460
461            resolve_variables(query, &m.variable_definitions, schema, id);
462            resolve_object_selection(
463                query,
464                on,
465                &m.selection_set,
466                SelectionParent::Operation(id),
467                schema,
468            )?;
469        }
470        graphql_parser::query::OperationDefinition::Query(q) => {
471            let on = schema.get_object(schema.query_type());
472            let (id, _) = query
473                .find_operation(q.name.as_ref().map(|name| name.as_ref()).unwrap())
474                .unwrap();
475
476            resolve_variables(query, &q.variable_definitions, schema, id);
477            resolve_object_selection(
478                query,
479                on,
480                &q.selection_set,
481                SelectionParent::Operation(id),
482                schema,
483            )?;
484        }
485        graphql_parser::query::OperationDefinition::Subscription(s) => {
486            let on = schema.subscription_type().ok_or_else(|| QueryValidationError::new("Query contains a subscription operation, but the schema has no subscription type.".into()))?;
487            let on = schema.get_object(on);
488            let (id, _) = query
489                .find_operation(s.name.as_ref().map(|name| name.as_ref()).unwrap())
490                .unwrap();
491
492            resolve_variables(query, &s.variable_definitions, schema, id);
493            resolve_object_selection(
494                query,
495                on,
496                &s.selection_set,
497                SelectionParent::Operation(id),
498                schema,
499            )?;
500        }
501        graphql_parser::query::OperationDefinition::SelectionSet(_) => {
502            unreachable!("unnamed queries are not supported")
503        }
504    }
505
506    Ok(())
507}
508
509#[derive(Default)]
510pub(crate) struct Query {
511    fragments: Vec<ResolvedFragment>,
512    operations: Vec<ResolvedOperation>,
513    selection_parent_idx: BTreeMap<SelectionId, SelectionParent>,
514    selections: Vec<Selection>,
515    variables: Vec<ResolvedVariable>,
516}
517
518impl Query {
519    fn push_selection(&mut self, node: Selection, parent: SelectionParent) -> SelectionId {
520        let id = SelectionId(self.selections.len() as u32);
521        self.selections.push(node);
522
523        self.selection_parent_idx.insert(id, parent);
524
525        id
526    }
527
528    pub fn operations(&self) -> impl Iterator<Item = (OperationId, &ResolvedOperation)> {
529        walk_operations(self)
530    }
531
532    pub(crate) fn get_selection(&self, id: SelectionId) -> &Selection {
533        self.selections
534            .get(id.0 as usize)
535            .expect("Query.get_selection")
536    }
537
538    pub(crate) fn get_fragment(&self, id: ResolvedFragmentId) -> &ResolvedFragment {
539        self.fragments
540            .get(id.0 as usize)
541            .expect("Query.get_fragment")
542    }
543
544    pub(crate) fn get_operation(&self, id: OperationId) -> &ResolvedOperation {
545        self.operations
546            .get(id.0 as usize)
547            .expect("Query.get_operation")
548    }
549
550    /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation.
551    pub(crate) fn select_operation<'a>(
552        &'a self,
553        name: &str,
554        normalization: Normalization,
555    ) -> Option<(OperationId, &'a ResolvedOperation)> {
556        walk_operations(self).find(|(_id, op)| normalization.operation(&op.name) == name)
557    }
558
559    fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> {
560        self.fragments
561            .iter_mut()
562            .enumerate()
563            .find(|(_, frag)| frag.name == name)
564            .map(|(id, f)| (ResolvedFragmentId(id as u32), f))
565    }
566
567    fn find_operation(&mut self, name: &str) -> Option<(OperationId, &mut ResolvedOperation)> {
568        self.operations
569            .iter_mut()
570            .enumerate()
571            .find(|(_, op)| op.name == name)
572            .map(|(id, op)| (OperationId::new(id), op))
573    }
574
575    fn selections(&self) -> impl Iterator<Item = (SelectionId, &Selection)> {
576        self.selections
577            .iter()
578            .enumerate()
579            .map(|(idx, selection)| (SelectionId(idx as u32), selection))
580    }
581
582    fn walk_selection_set<'a>(
583        &'a self,
584        selection_ids: &'a [SelectionId],
585    ) -> impl Iterator<Item = (SelectionId, &'a Selection)> + 'a {
586        selection_ids
587            .iter()
588            .map(move |id| (*id, self.get_selection(*id)))
589    }
590}
591
592#[derive(Debug)]
593pub(crate) struct ResolvedVariable {
594    pub(crate) operation_id: OperationId,
595    pub(crate) name: String,
596    pub(crate) default: Option<graphql_parser::query::Value<'static, String>>,
597    pub(crate) r#type: StoredFieldType,
598}
599
600impl ResolvedVariable {
601    pub(crate) fn type_name<'schema>(&self, schema: &'schema Schema) -> &'schema str {
602        self.r#type.id.name(schema)
603    }
604
605    fn collect_used_types(&self, used_types: &mut UsedTypes, schema: &Schema) {
606        match self.r#type.id {
607            TypeId::Input(input_id) => {
608                used_types.types.insert(TypeId::Input(input_id));
609
610                let input = schema.get_input(input_id);
611
612                input.used_input_ids_recursive(used_types, schema)
613            }
614            type_id @ TypeId::Scalar(_) | type_id @ TypeId::Enum(_) => {
615                used_types.types.insert(type_id);
616            }
617            _ => (),
618        }
619    }
620}
621
622#[derive(Debug, Default)]
623pub(crate) struct UsedTypes {
624    pub(crate) types: BTreeSet<TypeId>,
625    fragments: BTreeSet<ResolvedFragmentId>,
626}
627
628impl UsedTypes {
629    pub(crate) fn inputs<'s, 'a: 's>(
630        &'s self,
631        schema: &'a Schema,
632    ) -> impl Iterator<Item = (InputId, &'a StoredInputType)> + 's {
633        schema
634            .inputs()
635            .filter(move |(id, _input)| self.types.contains(&TypeId::Input(*id)))
636    }
637
638    pub(crate) fn scalars<'s, 'a: 's>(
639        &'s self,
640        schema: &'a Schema,
641    ) -> impl Iterator<Item = (ScalarId, &'a StoredScalar)> + 's {
642        self.types
643            .iter()
644            .filter_map(TypeId::as_scalar_id)
645            .map(move |scalar_id| (scalar_id, schema.get_scalar(scalar_id)))
646            .filter(|(_id, scalar)| !crate::schema::DEFAULT_SCALARS.contains(&scalar.name.as_str()))
647    }
648
649    pub(crate) fn enums<'a, 'schema: 'a>(
650        &'a self,
651        schema: &'schema Schema,
652    ) -> impl Iterator<Item = (EnumId, &'schema StoredEnum)> + 'a {
653        self.types
654            .iter()
655            .filter_map(TypeId::as_enum_id)
656            .map(move |enum_id| (enum_id, schema.get_enum(enum_id)))
657    }
658
659    pub(crate) fn fragment_ids(&self) -> impl Iterator<Item = ResolvedFragmentId> + '_ {
660        self.fragments.iter().copied()
661    }
662}
663
664fn resolve_variables<'doc, T>(
665    query: &mut Query,
666    variables: &[graphql_parser::query::VariableDefinition<'doc, T>],
667    schema: &Schema,
668    operation_id: OperationId,
669) where
670    T: graphql_parser::query::Text<'doc>,
671{
672    for var in variables {
673        query.variables.push(ResolvedVariable {
674            operation_id,
675            name: var.name.as_ref().into(),
676            default: var.default_value.as_ref().map(|dflt| dflt.into_static()),
677            r#type: resolve_field_type(schema, &var.var_type),
678        });
679    }
680}
681
682pub(crate) fn walk_operations(
683    query: &Query,
684) -> impl Iterator<Item = (OperationId, &ResolvedOperation)> {
685    query
686        .operations
687        .iter()
688        .enumerate()
689        .map(|(id, op)| (OperationId(id as u32), op))
690}
691
692pub(crate) fn operation_has_no_variables(operation_id: OperationId, query: &Query) -> bool {
693    walk_operation_variables(operation_id, query)
694        .next()
695        .is_none()
696}
697
698pub(crate) fn walk_operation_variables(
699    operation_id: OperationId,
700    query: &Query,
701) -> impl Iterator<Item = (VariableId, &ResolvedVariable)> {
702    query
703        .variables
704        .iter()
705        .enumerate()
706        .map(|(idx, var)| (VariableId(idx as u32), var))
707        .filter(move |(_id, var)| var.operation_id == operation_id)
708}
709
710pub(crate) fn all_used_types(operation_id: OperationId, query: &BoundQuery<'_>) -> UsedTypes {
711    let mut used_types = UsedTypes::default();
712
713    let operation = query.query.get_operation(operation_id);
714
715    for (_id, selection) in query.query.walk_selection_set(&operation.selection_set) {
716        selection.collect_used_types(&mut used_types, query);
717    }
718
719    for (_id, variable) in walk_operation_variables(operation_id, query.query) {
720        variable.collect_used_types(&mut used_types, query.schema);
721    }
722
723    used_types
724}
725
726pub(crate) fn full_path_prefix(selection_id: SelectionId, query: &BoundQuery<'_>) -> String {
727    let mut path = match query.query.get_selection(selection_id) {
728        Selection::FragmentSpread(_) | Selection::InlineFragment(_) => Vec::new(),
729        selection => vec![selection.to_path_segment(query)],
730    };
731
732    let mut item = selection_id;
733
734    while let Some(parent) = query.query.selection_parent_idx.get(&item) {
735        path.push(parent.to_path_segment(query));
736
737        match parent {
738            SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => {
739                item = *id;
740            }
741            _ => break,
742        }
743    }
744
745    path.reverse();
746    path.join("")
747}
748
749#[derive(Clone, Copy)]
750pub(crate) struct BoundQuery<'a> {
751    pub(crate) query: &'a Query,
752    pub(crate) schema: &'a Schema,
753}