Skip to main content

teaql_core/
request.rs

1//! Query builder layer types for TeaQL.
2//!
3//! This module contains the builder-side query types that were previously generated
4//! by the code generator's StringTemplate. They are the static, domain-independent
5//! parts shared by every generated TeaQL crate.
6//!
7//! Several types here intentionally shadow names from the parent crate (e.g.
8//! [`RelationAggregate`], [`ObjectGroupBy`], [`RawProjection`]). The builder
9//! versions carry a [`QuerySelection`] while the core/query versions carry a
10//! [`SelectQuery`]. The conversion happens in [`QuerySelection::into_query`] and
11//! [`apply_runtime_metadata`].
12
13use std::collections::BTreeMap;
14
15use serde_json::Value as JsonValue;
16
17use crate::{
18    BinaryOp, CompactRow, Expr, ObjectGroupBy as CoreObjectGroupBy,
19    RawSqlProjection as CoreRawSqlProjection, RelationAggregate as RuntimeRelationAggregate,
20    SelectQuery, SmartList, Value,
21};
22
23// ---------------------------------------------------------------------------
24// Constants
25// ---------------------------------------------------------------------------
26
27pub const COUNT_ALIAS: &str = "count";
28pub const TYPE_FIELD: &str = "internal_type";
29pub const TYPE_GROUP_FIELD: &str = "type_group";
30
31// ---------------------------------------------------------------------------
32// FieldOperator
33// ---------------------------------------------------------------------------
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum FieldOperator {
37    Equal,
38    NotEqual,
39    GreaterThan,
40    GreaterThanOrEqual,
41    LessThan,
42    LessThanOrEqual,
43    Between,
44    In,
45    NotIn,
46    Contain,
47    NotContain,
48    BeginWith,
49    NotBeginWith,
50    EndWith,
51    NotEndWith,
52    SoundsLike,
53    IsNull,
54    IsNotNull,
55}
56
57// ---------------------------------------------------------------------------
58// DateRange
59// ---------------------------------------------------------------------------
60
61#[derive(Clone, Debug, PartialEq)]
62pub struct DateRange<T> {
63    pub start: T,
64    pub end: T,
65}
66
67impl<T> DateRange<T> {
68    pub fn new(start: T, end: T) -> Self {
69        Self { start, end }
70    }
71}
72
73// ---------------------------------------------------------------------------
74// EntityReference
75// ---------------------------------------------------------------------------
76
77pub trait EntityReference {
78    fn entity_id_value(self) -> Value;
79}
80
81impl EntityReference for Value {
82    fn entity_id_value(self) -> Value {
83        self
84    }
85}
86
87impl EntityReference for u64 {
88    fn entity_id_value(self) -> Value {
89        Value::U64(self)
90    }
91}
92
93// ---------------------------------------------------------------------------
94// QuerySelection
95// ---------------------------------------------------------------------------
96
97#[derive(Clone, Debug, PartialEq)]
98pub struct QuerySelection {
99    pub query: SelectQuery,
100    pub relation_selections: Vec<RelationSelection>,
101    pub relation_filters: Vec<RelationFilter>,
102    pub child_enhancements: Vec<QuerySelection>,
103    pub query_options: QueryOptions,
104}
105
106impl QuerySelection {
107    pub fn new(query: impl Into<SelectQuery>) -> Self {
108        Self {
109            query: query.into(),
110            relation_selections: Vec::new(),
111            relation_filters: Vec::new(),
112            child_enhancements: Vec::new(),
113            query_options: QueryOptions::default(),
114        }
115    }
116
117    pub fn into_query(self) -> SelectQuery {
118        let query = apply_relation_selections(self.query, self.relation_selections);
119        apply_runtime_metadata(query, &self.query_options, &self.child_enhancements)
120    }
121}
122
123impl From<SelectQuery> for QuerySelection {
124    fn from(query: SelectQuery) -> Self {
125        QuerySelection::new(query)
126    }
127}
128
129// ---------------------------------------------------------------------------
130// RelationSelection
131// ---------------------------------------------------------------------------
132
133#[derive(Clone, Debug, PartialEq)]
134pub struct RelationSelection {
135    pub name: String,
136    pub query: SelectQuery,
137    pub relation_selections: Vec<RelationSelection>,
138    pub relation_filters: Vec<RelationFilter>,
139    pub child_enhancements: Vec<QuerySelection>,
140    pub query_options: QueryOptions,
141    pub operation: Option<crate::QueryOperation>,
142}
143
144impl RelationSelection {
145    pub fn new(name: impl Into<String>, selection: impl Into<QuerySelection>) -> Self {
146        let selection = selection.into();
147        Self {
148            name: name.into(),
149            query: selection.query,
150            relation_selections: selection.relation_selections,
151            relation_filters: selection.relation_filters,
152            child_enhancements: selection.child_enhancements,
153            query_options: selection.query_options,
154            operation: None,
155        }
156    }
157
158    pub fn into_query(self) -> SelectQuery {
159        let query = apply_relation_selections(self.query, self.relation_selections);
160        apply_runtime_metadata(query, &self.query_options, &self.child_enhancements)
161    }
162    pub fn with_operation(mut self, operation: crate::QueryOperation) -> Self {
163        self.operation = Some(operation);
164        self
165    }
166}
167
168// ---------------------------------------------------------------------------
169// RelationFilter
170// ---------------------------------------------------------------------------
171
172#[derive(Clone, Debug, PartialEq)]
173pub struct RelationFilter {
174    pub name: String,
175    pub query: SelectQuery,
176    pub relation_selections: Vec<RelationSelection>,
177    pub relation_filters: Vec<RelationFilter>,
178    pub child_enhancements: Vec<QuerySelection>,
179    pub query_options: QueryOptions,
180}
181
182impl RelationFilter {
183    pub fn new(name: impl Into<String>, selection: impl Into<QuerySelection>) -> Self {
184        let selection = selection.into();
185        Self {
186            name: name.into(),
187            query: selection.query,
188            relation_selections: selection.relation_selections,
189            relation_filters: selection.relation_filters,
190            child_enhancements: selection.child_enhancements,
191            query_options: selection.query_options,
192        }
193    }
194}
195
196// ---------------------------------------------------------------------------
197// QueryOptions
198// ---------------------------------------------------------------------------
199
200#[derive(Clone, Debug, Default, PartialEq)]
201pub struct QueryOptions {
202    pub comment: Option<String>,
203    /// Native named-count operation retained for page execution; the alias is
204    /// caller supplied, never an internal COUNT column or example constant.
205    pub page_count_operation: Option<crate::QueryOperation>,
206    pub raw_sql: Option<String>,
207    pub raw_sql_search_criteria: Vec<String>,
208    pub dynamic_properties: Vec<RawDynamicProperty>,
209    pub raw_projections: Vec<RawProjection>,
210    pub relation_aggregates: Vec<RelationAggregate>,
211    pub object_group_bys: Vec<ObjectGroupBy>,
212    pub facets: Vec<FacetRequest>,
213}
214
215// ---------------------------------------------------------------------------
216// UnsafeRawSqlSegment
217// ---------------------------------------------------------------------------
218
219#[derive(Clone, Debug, PartialEq, Eq)]
220pub struct UnsafeRawSqlSegment {
221    sql: String,
222}
223
224impl UnsafeRawSqlSegment {
225    pub fn trusted(sql: impl Into<String>) -> Self {
226        Self { sql: sql.into() }
227    }
228
229    pub fn into_sql(self) -> String {
230        self.sql
231    }
232}
233
234// ---------------------------------------------------------------------------
235// RawDynamicProperty
236// ---------------------------------------------------------------------------
237
238#[derive(Clone, Debug, PartialEq, Eq)]
239pub struct RawDynamicProperty {
240    pub property_name: String,
241    pub raw_sql_segment: String,
242}
243
244impl RawDynamicProperty {
245    pub fn new(property_name: impl Into<String>, raw_sql_segment: UnsafeRawSqlSegment) -> Self {
246        Self {
247            property_name: property_name.into(),
248            raw_sql_segment: raw_sql_segment.into_sql(),
249        }
250    }
251}
252
253// ---------------------------------------------------------------------------
254// RawProjection (builder version — distinct from crate::RawSqlProjection)
255// ---------------------------------------------------------------------------
256
257#[derive(Clone, Debug, PartialEq, Eq)]
258pub struct RawProjection {
259    pub property_name: String,
260    pub raw_sql_segment: String,
261}
262
263impl RawProjection {
264    pub fn new(property_name: impl Into<String>, raw_sql_segment: UnsafeRawSqlSegment) -> Self {
265        Self {
266            property_name: property_name.into(),
267            raw_sql_segment: raw_sql_segment.into_sql(),
268        }
269    }
270}
271
272// ---------------------------------------------------------------------------
273// RelationAggregate (builder version — carries QuerySelection, not SelectQuery)
274// ---------------------------------------------------------------------------
275
276#[derive(Clone, Debug, PartialEq)]
277pub struct RelationAggregate {
278    pub relation_name: String,
279    pub alias: String,
280    pub query: QuerySelection,
281    pub single_result: bool,
282    pub operation: Option<crate::QueryOperation>,
283}
284
285impl RelationAggregate {
286    pub fn new(
287        relation_name: impl Into<String>,
288        alias: impl Into<String>,
289        query: impl Into<QuerySelection>,
290        single_result: bool,
291    ) -> Self {
292        Self {
293            relation_name: relation_name.into(),
294            alias: alias.into(),
295            query: query.into(),
296            single_result,
297            operation: None,
298        }
299    }
300    pub fn with_operation(mut self, operation: crate::QueryOperation) -> Self {
301        self.operation = Some(operation);
302        self
303    }
304}
305
306// ---------------------------------------------------------------------------
307// FacetRequest
308// ---------------------------------------------------------------------------
309
310#[derive(Clone, Debug, PartialEq)]
311pub struct FacetRequest {
312    pub facet_name: String,
313    pub relation_name: String,
314    pub query: QuerySelection,
315    pub include_all_facets: bool,
316    pub operation: Option<crate::QueryOperation>,
317}
318
319impl FacetRequest {
320    pub fn new(
321        facet_name: impl Into<String>,
322        relation_name: impl Into<String>,
323        query: impl Into<QuerySelection>,
324        include_all_facets: bool,
325    ) -> Self {
326        Self {
327            facet_name: facet_name.into(),
328            relation_name: relation_name.into(),
329            query: query.into(),
330            include_all_facets,
331            operation: None,
332        }
333    }
334    pub fn with_operation(mut self, operation: crate::QueryOperation) -> Self {
335        self.operation = Some(operation);
336        self
337    }
338}
339
340// ---------------------------------------------------------------------------
341// ObjectGroupBy (builder version — carries QuerySelection, not SelectQuery)
342// ---------------------------------------------------------------------------
343
344#[derive(Clone, Debug, PartialEq)]
345pub struct ObjectGroupBy {
346    pub property_name: String,
347    pub storage_field: String,
348    pub query: QuerySelection,
349}
350
351impl ObjectGroupBy {
352    pub fn new(
353        property_name: impl Into<String>,
354        storage_field: impl Into<String>,
355        query: impl Into<QuerySelection>,
356    ) -> Self {
357        Self {
358            property_name: property_name.into(),
359            storage_field: storage_field.into(),
360            query: query.into(),
361        }
362    }
363}
364
365// ---------------------------------------------------------------------------
366// Relation selection / runtime metadata helpers
367// ---------------------------------------------------------------------------
368
369pub fn apply_relation_selections(
370    mut query: SelectQuery,
371    relation_selections: Vec<RelationSelection>,
372) -> SelectQuery {
373    for selection in relation_selections {
374        let operation = selection.operation.clone();
375        let name = selection.name.clone();
376        let mut load = crate::RelationLoad::with_query(name, selection.into_query());
377        load.operation = operation;
378        query.relations.push(load);
379    }
380    query
381}
382
383pub fn apply_runtime_metadata(
384    mut query: SelectQuery,
385    options: &QueryOptions,
386    child_enhancements: &[QuerySelection],
387) -> SelectQuery {
388    if let Some(c) = options.comment.clone() {
389        query = query.comment(c);
390    }
391    query.raw_sql = options.raw_sql.clone();
392    query.facets = options.facets.clone();
393    query.relation_aggregates = runtime_relation_aggregates(options);
394    query.raw_sql_search_criteria = options.raw_sql_search_criteria.clone();
395    query.dynamic_properties = options
396        .dynamic_properties
397        .iter()
398        .map(|projection| {
399            CoreRawSqlProjection::new(
400                projection.property_name.clone(),
401                projection.raw_sql_segment.clone(),
402            )
403        })
404        .collect();
405    query.raw_projections = options
406        .raw_projections
407        .iter()
408        .map(|projection| {
409            CoreRawSqlProjection::new(
410                projection.property_name.clone(),
411                projection.raw_sql_segment.clone(),
412            )
413        })
414        .collect();
415    query.object_group_bys = options
416        .object_group_bys
417        .iter()
418        .map(|group_by| {
419            CoreObjectGroupBy::new(
420                group_by.property_name.clone(),
421                group_by.storage_field.clone(),
422                group_by.query.clone().into_query(),
423            )
424        })
425        .collect();
426    query.child_enhancements = child_enhancements
427        .iter()
428        .cloned()
429        .map(QuerySelection::into_query)
430        .collect();
431    query
432}
433
434// ---------------------------------------------------------------------------
435// runtime_relation_aggregates — converts builder → core RelationAggregate
436// ---------------------------------------------------------------------------
437
438pub fn runtime_relation_aggregates(options: &QueryOptions) -> Vec<RuntimeRelationAggregate> {
439    options
440        .relation_aggregates
441        .iter()
442        .map(|aggregate| {
443            let mut runtime = RuntimeRelationAggregate::new(
444                aggregate.relation_name.clone(),
445                aggregate.alias.clone(),
446                aggregate.query.clone().into_query(),
447                aggregate.single_result,
448            );
449            runtime.operation = aggregate.operation.clone();
450            runtime
451        })
452        .collect()
453}
454
455// ---------------------------------------------------------------------------
456// Facet helpers
457// ---------------------------------------------------------------------------
458
459pub fn merge_outer_filter_into_facet_aggregates(
460    selection: &mut QuerySelection,
461    outer_query: &SelectQuery,
462) {
463    let Some(filter) = outer_query.filter.clone() else {
464        return;
465    };
466    for aggregate in &mut selection.query_options.relation_aggregates {
467        if aggregate.query.query.entity == outer_query.entity {
468            aggregate.query.query = aggregate.query.query.clone().and_filter(filter.clone());
469        }
470    }
471}
472
473pub fn attach_facets<T>(rows: &mut SmartList<T>, facets: BTreeMap<String, SmartList<CompactRow>>) {
474    for (name, facet) in facets {
475        rows.add_facet(name, facet);
476    }
477}
478
479// ---------------------------------------------------------------------------
480// field_operator_expr / field_operator_column_expr
481// ---------------------------------------------------------------------------
482
483pub fn field_operator_expr(field: &str, operator: FieldOperator, values: Vec<Value>) -> Expr {
484    match operator {
485        FieldOperator::Equal => Expr::eq(field, required_value(operator, &values, 0)),
486        FieldOperator::NotEqual => Expr::ne(field, required_value(operator, &values, 0)),
487        FieldOperator::GreaterThan => Expr::gt(field, required_value(operator, &values, 0)),
488        FieldOperator::GreaterThanOrEqual => Expr::gte(field, required_value(operator, &values, 0)),
489        FieldOperator::LessThan => Expr::lt(field, required_value(operator, &values, 0)),
490        FieldOperator::LessThanOrEqual => Expr::lte(field, required_value(operator, &values, 0)),
491        FieldOperator::Between => Expr::between(
492            field,
493            required_value(operator, &values, 0),
494            required_value(operator, &values, 1),
495        ),
496        FieldOperator::In => Expr::in_list(field, values),
497        FieldOperator::NotIn => Expr::not_in_list(field, values),
498        FieldOperator::Contain => Expr::contain(field, required_text(operator, &values, 0)),
499        FieldOperator::NotContain => Expr::not_contain(field, required_text(operator, &values, 0)),
500        FieldOperator::BeginWith => Expr::begin_with(field, required_text(operator, &values, 0)),
501        FieldOperator::NotBeginWith => {
502            Expr::not_begin_with(field, required_text(operator, &values, 0))
503        }
504        FieldOperator::EndWith => Expr::end_with(field, required_text(operator, &values, 0)),
505        FieldOperator::NotEndWith => Expr::not_end_with(field, required_text(operator, &values, 0)),
506        FieldOperator::SoundsLike => Expr::sound_like(field, required_value(operator, &values, 0)),
507        FieldOperator::IsNull => Expr::is_null(field),
508        FieldOperator::IsNotNull => Expr::is_not_null(field),
509    }
510}
511
512pub fn field_operator_column_expr(field: &str, operator: FieldOperator, other_field: &str) -> Expr {
513    let binary_op = match operator {
514        FieldOperator::Equal => BinaryOp::Eq,
515        FieldOperator::NotEqual => BinaryOp::Ne,
516        FieldOperator::GreaterThan => BinaryOp::Gt,
517        FieldOperator::GreaterThanOrEqual => BinaryOp::Gte,
518        FieldOperator::LessThan => BinaryOp::Lt,
519        FieldOperator::LessThanOrEqual => BinaryOp::Lte,
520        FieldOperator::Contain => BinaryOp::Like,
521        FieldOperator::NotContain => BinaryOp::NotLike,
522        FieldOperator::BeginWith => BinaryOp::Like,
523        FieldOperator::NotBeginWith => BinaryOp::NotLike,
524        FieldOperator::EndWith => BinaryOp::Like,
525        FieldOperator::NotEndWith => BinaryOp::NotLike,
526        unsupported => panic!("{unsupported:?} is not supported for property-to-property filters"),
527    };
528    Expr::compare_columns(field, binary_op, other_field)
529}
530
531// ---------------------------------------------------------------------------
532// required_value / required_text
533// ---------------------------------------------------------------------------
534
535pub fn required_value(operator: FieldOperator, values: &[Value], index: usize) -> Value {
536    values
537        .get(index)
538        .cloned()
539        .unwrap_or_else(|| panic!("{operator:?} requires value at index {index}"))
540}
541
542pub fn required_text(operator: FieldOperator, values: &[Value], index: usize) -> String {
543    match required_value(operator, values, index) {
544        Value::Text(value) => value,
545        value => panic!("{operator:?} requires text value, got {value:?}"),
546    }
547}
548
549// ---------------------------------------------------------------------------
550// remove_default_live_filter / remove_filter_expr
551// ---------------------------------------------------------------------------
552
553pub fn remove_default_live_filter(filter: Option<Expr>) -> Option<Expr> {
554    let default_filter = Expr::gt("version", 0_i64);
555    remove_filter_expr(filter?, &default_filter)
556}
557
558pub fn remove_filter_expr(filter: Expr, target: &Expr) -> Option<Expr> {
559    if &filter == target {
560        return None;
561    }
562    match filter {
563        Expr::And(parts) => {
564            let mut retained = parts
565                .into_iter()
566                .filter_map(|part| remove_filter_expr(part, target))
567                .collect::<Vec<_>>();
568            match retained.len() {
569                0 => None,
570                1 => retained.pop(),
571                _ => Some(Expr::And(retained)),
572            }
573        }
574        other => Some(other),
575    }
576}
577
578// ---------------------------------------------------------------------------
579// Dynamic JSON helpers
580// ---------------------------------------------------------------------------
581
582pub fn dynamic_json_value_to_teaql_value(value: &JsonValue) -> Value {
583    match value {
584        JsonValue::Null => Value::Null,
585        JsonValue::Bool(value) => Value::Bool(*value),
586        JsonValue::Number(value) => value
587            .as_i64()
588            .map(Value::I64)
589            .or_else(|| value.as_u64().map(Value::U64))
590            .or_else(|| value.as_f64().map(Value::F64))
591            .unwrap_or(Value::Null),
592        JsonValue::String(value) => Value::Text(value.trim().to_owned()),
593        JsonValue::Array(values) => Value::List(
594            values
595                .iter()
596                .map(dynamic_json_value_to_teaql_value)
597                .collect(),
598        ),
599        JsonValue::Object(object) => object
600            .get("id")
601            .map(dynamic_json_value_to_teaql_value)
602            .unwrap_or(Value::Null),
603    }
604}
605
606pub fn dynamic_json_values(value: &JsonValue) -> Vec<Value> {
607    match value {
608        JsonValue::Array(values) => values
609            .iter()
610            .map(dynamic_json_value_to_teaql_value)
611            .collect(),
612        value => vec![dynamic_json_value_to_teaql_value(value)],
613    }
614}
615
616pub fn dynamic_json_operator(value: &JsonValue) -> FieldOperator {
617    match value {
618        JsonValue::String(value) if value.eq_ignore_ascii_case("__is_null__") => {
619            FieldOperator::IsNull
620        }
621        JsonValue::String(value) if value.eq_ignore_ascii_case("__is_not_null__") => {
622            FieldOperator::IsNotNull
623        }
624        JsonValue::String(_) => FieldOperator::Contain,
625        JsonValue::Number(_) | JsonValue::Bool(_) => FieldOperator::Equal,
626        JsonValue::Array(values) if values.first().map(JsonValue::is_string).unwrap_or(false) => {
627            FieldOperator::In
628        }
629        JsonValue::Array(values) if values.first().map(JsonValue::is_object).unwrap_or(false) => {
630            FieldOperator::In
631        }
632        JsonValue::Array(values) if values.len() == 2 => FieldOperator::Between,
633        _ => FieldOperator::Equal,
634    }
635}
636
637pub fn dynamic_json_filter_expr(field: &str, value: &JsonValue) -> Expr {
638    let operator = dynamic_json_operator(value);
639    field_operator_expr(field, operator, dynamic_json_values(value))
640}
641
642pub fn dynamic_json_u64_field(
643    object: &serde_json::Map<String, JsonValue>,
644    field: &str,
645) -> Option<u64> {
646    object.get(field).and_then(|value| {
647        value
648            .as_u64()
649            .or_else(|| value.as_i64().and_then(|value| u64::try_from(value).ok()))
650    })
651}