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