1use itertools::Itertools;
6use regex::Regex;
7use sqlparser::ast::{
8 self as sql_ast, Join, JoinConstraint, JoinOperator, Select, SelectItem, SetExpr, TableAlias,
9 TableFactor, TableWithJoins,
10};
11
12use super::gen_expr::*;
13use super::gen_projection::*;
14use super::operators::translate_operator;
15use super::pq::ast::{Cte, CteKind, RelationExpr, RelationExprKind, SqlRelation, SqlTransform};
16use super::{Context, Dialect};
17use crate::debug;
18use crate::ir::pl::{JoinSide, Literal};
19use crate::ir::rq::{CId, Expr, ExprKind, RelationLiteral, RelationalQuery};
20use crate::utils::{BreakUp, Pluck};
21use crate::{Error, Result, WithErrorInfo};
22use prqlc_parser::generic::InterpolateItem;
23
24type Transform = SqlTransform<RelationExpr, ()>;
25
26pub fn translate_query(query: RelationalQuery, dialect: Option<Dialect>) -> Result<sql_ast::Query> {
27 let (pq_query, mut ctx) = super::pq::compile_query(query, dialect)?;
29
30 debug::log_stage(debug::Stage::Sql(debug::StageSql::Main));
31 let mut query = translate_relation(pq_query.main_relation, &mut ctx)?;
32
33 if !pq_query.ctes.is_empty() {
34 let mut cte_tables = Vec::new();
36 let mut recursive = false;
37 for cte in pq_query.ctes {
38 let (cte, rec) = translate_cte(cte, &mut ctx)?;
39 cte_tables.push(cte);
40 recursive = recursive || rec;
41 }
42 query.with = Some(sql_ast::With {
43 recursive,
44 cte_tables,
45 with_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),
46 });
47 }
48
49 debug::log_entry(|| debug::DebugEntryKind::ReprSqlParser(Box::new(query.clone())));
50 Ok(query)
51}
52
53fn translate_relation(relation: SqlRelation, ctx: &mut Context) -> Result<sql_ast::Query> {
54 match relation {
55 SqlRelation::AtomicPipeline(pipeline) => translate_pipeline(pipeline, ctx),
56 SqlRelation::Literal(data) => translate_relation_literal(data, ctx),
57 SqlRelation::SString(items) => translate_query_sstring(items, ctx),
58 SqlRelation::Operator { name, args } => translate_query_operator(name, args, ctx),
59 }
60}
61
62fn translate_pipeline(pipeline: Vec<Transform>, ctx: &mut Context) -> Result<sql_ast::Query> {
63 use SqlTransform::*;
64
65 let (select, set_ops) =
66 pipeline.break_up(|t| matches!(t, Union { .. } | Except { .. } | Intersect { .. }));
67
68 let select = translate_select_pipeline(select, ctx)?;
69
70 translate_set_ops_pipeline(select, set_ops, ctx)
71}
72
73fn translate_select_pipeline(
74 mut pipeline: Vec<Transform>,
75 ctx: &mut Context,
76) -> Result<sql_ast::Query> {
77 let table_count = count_tables(&pipeline);
78 log::debug!("atomic query contains {table_count} tables");
79 ctx.push_query();
80 ctx.query.omit_ident_prefix = table_count == 1;
81 ctx.query.pre_projection = true;
82
83 let mut from: Vec<_> = pipeline
84 .pluck(|t| t.into_from())
85 .into_iter()
86 .map(|source| -> Result<TableWithJoins> {
87 Ok(TableWithJoins {
88 relation: translate_relation_expr(source, ctx)?,
89 joins: vec![],
90 })
91 })
92 .try_collect()?;
93
94 let joins = pipeline
95 .pluck(|t| t.into_join())
96 .into_iter()
97 .map(|j| translate_join(j, ctx))
98 .collect::<Result<Vec<_>>>()?;
99 if !joins.is_empty() {
100 if let Some(from) = from.last_mut() {
101 from.joins = joins;
102 } else {
103 unreachable!()
104 }
105 }
106
107 let projection = pipeline
108 .pluck(|t| t.into_select())
109 .into_iter()
110 .exactly_one()
111 .unwrap();
112 let projection = translate_wildcards(&ctx.anchor, projection);
113 let mut projection = translate_select_items(projection.0, projection.1, ctx)?;
114
115 let order_by = pipeline.pluck(|t| t.into_sort());
116 let takes = pipeline.pluck(|t| t.into_take());
117 let is_distinct = pipeline.iter().any(|t| matches!(t, SqlTransform::Distinct));
118 let distinct_ons = pipeline.pluck(|t| t.into_distinct_on());
119 let distinct = if is_distinct {
120 Some(sql_ast::Distinct::Distinct)
121 } else if !distinct_ons.is_empty() {
122 Some(sql_ast::Distinct::On(
123 distinct_ons
124 .into_iter()
125 .exactly_one()
126 .unwrap()
127 .into_iter()
128 .map(|id| translate_cid(id, ctx).map(|x| x.into_ast()))
129 .collect::<Result<Vec<_>>>()?,
130 ))
131 } else {
132 None
133 };
134
135 if matches!(distinct, Some(sql_ast::Distinct::On(_))) {
139 if projection.len() == 1 {
140 if let SelectItem::UnnamedExpr(sql_ast::Expr::Value(ref v)) = projection[0] {
141 if matches!(v.value, sql_ast::Value::Null) {
142 projection[0] =
143 SelectItem::Wildcard(sql_ast::WildcardAdditionalOptions::default());
144 }
145 }
146 } else if projection.is_empty() {
147 projection.push(SelectItem::Wildcard(
148 sql_ast::WildcardAdditionalOptions::default(),
149 ));
150 }
151 }
152
153 let (mut before_agg, mut after_agg) =
155 pipeline.break_up(|t| matches!(t, Transform::Aggregate { .. } | Transform::Union { .. }));
156
157 let where_ = filter_of_conditions(before_agg.pluck(|t| t.into_filter()), ctx)?;
159 let having = filter_of_conditions(after_agg.pluck(|t| t.into_filter()), ctx)?;
160
161 let aggregate = after_agg.pluck(|t| t.into_aggregate()).into_iter().next();
163 let group_by: Vec<CId> = aggregate.map(|(part, _)| part).unwrap_or_default();
164 ctx.query.allow_stars = ctx.dialect.stars_in_group();
165 let group_by = sql_ast::GroupByExpr::Expressions(try_into_exprs(group_by, ctx, None)?, vec![]);
166 ctx.query.allow_stars = true;
167
168 ctx.query.pre_projection = false;
169
170 let ranges = takes.into_iter().map(|x| x.range).collect();
171 let take = range_of_ranges(ranges)?;
172 let offset = take.start.map(|s| s - 1).unwrap_or(0);
173 let limit = take.end.map(|e| e - offset);
174
175 let mut offset = if offset == 0 {
176 None
177 } else {
178 let kind = ExprKind::Literal(Literal::Integer(offset));
179 let expr = Expr { kind, span: None };
180 Some(sqlparser::ast::Offset {
181 value: translate_expr(expr, ctx)?.into_ast(),
182 rows: if ctx.dialect.use_fetch() {
183 sqlparser::ast::OffsetRows::Rows
184 } else {
185 sqlparser::ast::OffsetRows::None
186 },
187 })
188 };
189
190 let mut order_by: Vec<sql_ast::OrderByExpr> = order_by
192 .last()
193 .map(|sorts| {
194 sorts
195 .iter()
196 .map(|s| translate_column_sort(s, ctx))
197 .try_collect()
198 })
199 .transpose()?
200 .unwrap_or_default();
201
202 let (fetch, limit) = if ctx.dialect.use_fetch() {
203 (limit.map(|l| fetch_of_i64(l, ctx)), None)
204 } else {
205 (None, limit.map(expr_of_i64))
206 };
207
208 if fetch.is_some() {
212 if offset.is_none() {
213 let kind = ExprKind::Literal(Literal::Integer(0));
214 let expr = Expr { kind, span: None };
215 offset = Some(sqlparser::ast::Offset {
216 value: translate_expr(expr, ctx)?.into_ast(),
217 rows: sqlparser::ast::OffsetRows::Rows,
218 })
219 }
220 if order_by.is_empty() {
221 let order_expr = is_distinct
225 .then(|| first_expr_from_projection(&projection))
226 .flatten()
227 .unwrap_or_else(|| {
228 sql_ast::Expr::Value(
229 sql_ast::Value::Placeholder("(SELECT NULL)".to_string()).into(),
230 )
231 });
232 order_by.push(sql_ast::OrderByExpr {
233 expr: order_expr,
234 options: sqlparser::ast::OrderByOptions {
235 asc: None,
236 nulls_first: None,
237 },
238 with_fill: None,
239 });
240 }
241 }
242
243 ctx.pop_query();
244
245 Ok(sql_ast::Query {
246 order_by: if order_by.is_empty() {
247 None
248 } else {
249 Some(sql_ast::OrderBy {
250 kind: sqlparser::ast::OrderByKind::Expressions(order_by),
251 interpolate: None,
252 })
253 },
254 limit_clause: if limit.is_some() || offset.is_some() {
255 Some(sql_ast::LimitClause::LimitOffset {
256 limit,
257 offset,
258 limit_by: Vec::new(),
259 })
260 } else {
261 None
262 },
263 fetch,
264 ..default_query(SetExpr::Select(Box::new(Select {
265 distinct,
266 projection,
267 from,
268 selection: where_,
269 group_by,
270 having,
271 ..default_select()
272 })))
273 })
274}
275
276fn translate_set_ops_pipeline(
277 mut top: sql_ast::Query,
278 mut pipeline: Vec<Transform>,
279 context: &mut Context,
280) -> Result<sql_ast::Query> {
281 pipeline.reverse();
283
284 while let Some(transform) = pipeline.pop() {
285 use SqlTransform::*;
286
287 let op = match &transform {
288 Union { .. } => sql_ast::SetOperator::Union,
289 Except { .. } => sql_ast::SetOperator::Except,
290 Intersect { .. } => sql_ast::SetOperator::Intersect,
291 Sort(_) => continue,
292 _ => unreachable!(),
293 };
294
295 let (distinct, bottom) = match transform {
296 Union { distinct, bottom }
297 | Except { distinct, bottom }
298 | Intersect { distinct, bottom } => (distinct, bottom),
299 _ => unreachable!(),
300 };
301
302 let left = query_to_set_expr(top, context);
312
313 let right_rel = translate_relation_expr(bottom, context)?;
315 let right = if let TableFactor::Derived { subquery, .. } = right_rel {
316 query_to_set_expr(*subquery, context)
317 } else {
318 Box::new(SetExpr::Select(Box::new(sql_ast::Select {
319 projection: vec![SelectItem::Wildcard(
320 sql_ast::WildcardAdditionalOptions::default(),
321 )],
322 from: vec![TableWithJoins {
323 relation: right_rel,
324 joins: vec![],
325 }],
326 ..default_select()
327 })))
328 };
329
330 top = default_query(SetExpr::SetOperation {
331 left,
332 right,
333 set_quantifier: if distinct {
334 if context.dialect.set_ops_distinct() {
335 sql_ast::SetQuantifier::Distinct
336 } else {
337 sql_ast::SetQuantifier::None
338 }
339 } else {
340 sql_ast::SetQuantifier::All
341 },
342 op,
343 });
344 }
345
346 Ok(top)
347}
348
349fn translate_relation_expr(relation_expr: RelationExpr, ctx: &mut Context) -> Result<TableFactor> {
350 let alias = Some(&relation_expr.riid)
351 .and_then(|riid| ctx.anchor.relation_instances.get(riid))
352 .and_then(|ri| ri.table_ref.name.clone());
353
354 Ok(match relation_expr.kind {
355 RelationExprKind::Ref(tid) => {
356 let decl = ctx.anchor.lookup_table_decl(&tid).unwrap();
357
358 let table_name = decl.name.clone().unwrap();
360
361 let name = sql_ast::ObjectName(
362 translate_ident(Some(table_name.clone()), None, ctx)
363 .into_iter()
364 .map(sqlparser::ast::ObjectNamePart::Identifier)
365 .collect(),
366 );
367
368 TableFactor::Table {
369 name,
370 alias: if Some(table_name.name) == alias {
371 None
372 } else {
373 translate_table_alias(alias, ctx)
374 },
375 args: None,
376 with_hints: vec![],
377 with_ordinality: false,
378 version: None,
379 partitions: vec![],
380 json_path: None,
381 sample: None,
382 index_hints: vec![],
383 }
384 }
385 RelationExprKind::SubQuery(query) => {
386 let query = translate_relation(query, ctx)?;
387
388 let alias = translate_table_alias(alias, ctx);
389
390 TableFactor::Derived {
391 lateral: false,
392 subquery: Box::new(query),
393 alias,
394 }
395 }
396 })
397}
398
399fn translate_table_alias(alias: Option<String>, ctx: &mut Context) -> Option<TableAlias> {
400 alias
401 .map(|ident| translate_ident_part(ident, ctx))
402 .map(simple_table_alias)
403}
404
405fn translate_join(
406 (side, with, filter): (JoinSide, RelationExpr, Expr),
407 ctx: &mut Context,
408) -> Result<Join> {
409 let relation = translate_relation_expr(with, ctx)?;
410
411 let constraint = JoinConstraint::On(translate_expr(filter, ctx)?.into_ast());
412
413 Ok(Join {
414 relation,
415 join_operator: match side {
416 JoinSide::Inner => JoinOperator::Inner(constraint),
417 JoinSide::Left => JoinOperator::LeftOuter(constraint),
418 JoinSide::Right => JoinOperator::RightOuter(constraint),
419 JoinSide::Full => JoinOperator::FullOuter(constraint),
420 },
421 global: false,
422 })
423}
424
425fn translate_cte(cte: Cte, ctx: &mut Context) -> Result<(sql_ast::Cte, bool)> {
426 let decl = ctx.anchor.lookup_table_decl(&cte.tid).unwrap();
427 let cte_name = decl.name.clone().unwrap();
428
429 let cte_name = translate_ident(Some(cte_name), None, ctx).pop().unwrap();
430
431 let (query, recursive) = match cte.kind {
432 CteKind::Normal(rel) => (translate_relation(rel, ctx)?, false),
434
435 CteKind::Loop { initial, step } => {
437 let initial = query_to_set_expr(translate_relation(initial, ctx)?, ctx);
439
440 let step = query_to_set_expr(translate_relation(step, ctx)?, ctx);
441
442 let inner_query = default_query(SetExpr::SetOperation {
444 op: sql_ast::SetOperator::Union,
445 set_quantifier: sql_ast::SetQuantifier::All,
446 left: initial,
447 right: step,
448 });
449
450 (inner_query, true)
451
452 }
485 };
486
487 let cte = sql_ast::Cte {
488 alias: cte_table_alias(cte_name),
489 query: Box::new(query),
490 from: None,
491 materialized: None,
492 closing_paren_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),
493 };
494 Ok((cte, recursive))
495}
496
497fn translate_relation_literal(data: RelationLiteral, ctx: &Context) -> Result<sql_ast::Query> {
498 if data.rows.is_empty() {
506 let mut nulls: Vec<_> = (data.columns.iter())
507 .map(|col_name| SelectItem::ExprWithAlias {
508 expr: sql_ast::Expr::Value(sql_ast::Value::Null.into()),
509 alias: translate_ident_part(col_name.clone(), ctx),
510 })
511 .collect();
512
513 if nulls.is_empty() {
515 nulls.push(SelectItem::UnnamedExpr(sql_ast::Expr::Value(
516 sql_ast::Value::Null.into(),
517 )));
518 }
519
520 return Ok(default_query(sql_ast::SetExpr::Select(Box::new(Select {
521 projection: nulls,
522 selection: Some(sql_ast::Expr::Value(sql_ast::Value::Boolean(false).into())),
523 ..default_select()
524 }))));
525 }
526
527 let mut selects = Vec::with_capacity(data.rows.len());
528
529 for row in data.rows {
530 let body = sql_ast::SetExpr::Select(Box::new(Select {
531 projection: std::iter::zip(data.columns.clone(), row)
532 .map(|(col, value)| -> Result<_> {
533 Ok(SelectItem::ExprWithAlias {
534 expr: translate_literal(value, ctx)?,
535 alias: translate_ident_part(col, ctx),
536 })
537 })
538 .try_collect()?,
539 ..default_select()
540 }));
541
542 selects.push(body)
543 }
544
545 let mut body = selects.remove(0);
546 for select in selects {
547 body = SetExpr::SetOperation {
548 op: sql_ast::SetOperator::Union,
549 set_quantifier: sql_ast::SetQuantifier::All,
550 left: Box::new(body),
551 right: Box::new(select),
552 }
553 }
554
555 Ok(default_query(body))
556}
557
558pub(super) fn translate_query_sstring(
559 items: Vec<InterpolateItem<Expr>>,
560 ctx: &mut Context,
561) -> Result<sql_ast::Query> {
562 let string = translate_sstring(items, ctx)?;
563
564 let re = Regex::new(r"(?i)^SELECT\b").unwrap();
565 let prefix = string.trim().get(0..7).unwrap_or_default();
566
567 if re.is_match(prefix) {
568 if let Some(string) = string.trim().strip_prefix(prefix) {
569 return Ok(default_query(sql_ast::SetExpr::Select(Box::new(
570 sql_ast::Select {
571 projection: vec![sql_ast::SelectItem::UnnamedExpr(sql_ast::Expr::Identifier(
572 sql_ast::Ident::new(string),
573 ))],
574 ..default_select()
575 },
576 ))));
577 }
578 }
579
580 Err(
581 Error::new_simple("s-strings representing a table must start with `SELECT `".to_string())
582 .push_hint("this is a limitation by current compiler implementation"),
583 )
584}
585
586pub(super) fn translate_query_operator(
587 name: String,
588 args: Vec<Expr>,
589 ctx: &mut Context,
590) -> Result<sql_ast::Query> {
591 let from_s_string = translate_operator(name, args, ctx)?;
592
593 let s_string = format!(" * FROM {}", from_s_string.text);
594
595 Ok(default_query(sql_ast::SetExpr::Select(Box::new(
596 sql_ast::Select {
597 projection: vec![sql_ast::SelectItem::UnnamedExpr(sql_ast::Expr::Identifier(
598 sql_ast::Ident::new(s_string),
599 ))],
600 ..default_select()
601 },
602 ))))
603}
604
605fn filter_of_conditions(exprs: Vec<Expr>, context: &mut Context) -> Result<Option<sql_ast::Expr>> {
606 Ok(if let Some(cond) = all(exprs) {
607 Some(translate_expr(cond, context)?.into_ast())
608 } else {
609 None
610 })
611}
612
613fn all(mut exprs: Vec<Expr>) -> Option<Expr> {
614 let mut condition = exprs.pop()?;
615 while let Some(expr) = exprs.pop() {
616 condition = Expr {
617 kind: ExprKind::Operator {
618 name: "std.and".to_string(),
619 args: vec![expr, condition],
620 },
621 span: None,
622 };
623 }
624 Some(condition)
625}
626
627fn default_query(body: sql_ast::SetExpr) -> sql_ast::Query {
628 sql_ast::Query {
629 with: None,
630 body: Box::new(body),
631 order_by: None,
632 limit_clause: None,
633 fetch: None,
634 locks: Vec::new(),
635 for_clause: None,
636 settings: None,
637 format_clause: None,
638 pipe_operators: Vec::new(),
639 }
640}
641
642fn default_select() -> Select {
643 Select {
644 distinct: None,
645 top: None,
646 top_before_distinct: false,
647 projection: Vec::new(),
648 into: None,
649 from: Vec::new(),
650 lateral_views: Vec::new(),
651 selection: None,
652 group_by: sql_ast::GroupByExpr::Expressions(vec![], vec![]),
653 cluster_by: Vec::new(),
654 distribute_by: Vec::new(),
655 sort_by: Vec::new(),
656 having: None,
657 named_window: vec![],
658 qualify: None,
659 value_table_mode: None,
660 window_before_qualify: false,
661 connect_by: None,
662 prewhere: None,
663 exclude: None,
664 select_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),
665 flavor: sqlparser::ast::SelectFlavor::Standard,
666 }
667}
668
669fn simple_table_alias(name: sql_ast::Ident) -> TableAlias {
670 TableAlias {
671 name,
672 columns: Vec::new(),
673 explicit: true,
674 }
675}
676
677fn cte_table_alias(name: sql_ast::Ident) -> TableAlias {
678 TableAlias {
679 name,
680 columns: Vec::new(),
681 explicit: false,
682 }
683}
684
685fn query_to_set_expr(query: sql_ast::Query, context: &mut Context) -> Box<SetExpr> {
686 let is_simple = query.with.is_none()
687 && query.order_by.is_none()
688 && query.limit_clause.is_none()
689 && query.fetch.is_none()
690 && query.locks.is_empty();
691
692 if is_simple {
693 return query.body;
694 }
695
696 let set_expr = if context.dialect.prefers_subquery_parentheses_shorthand() {
705 SetExpr::Query(query.into())
706 } else {
707 SetExpr::Select(Box::new(Select {
708 projection: vec![SelectItem::Wildcard(
709 sql_ast::WildcardAdditionalOptions::default(),
710 )],
711 from: vec![TableWithJoins {
712 relation: TableFactor::Derived {
713 lateral: false,
714 subquery: Box::new(query),
715 alias: Some(simple_table_alias(sql_ast::Ident::new(
716 context.anchor.table_name.gen(),
717 ))),
718 },
719 joins: vec![],
720 }],
721 ..default_select()
722 }))
723 };
724
725 Box::new(set_expr)
726}
727
728fn count_tables(transforms: &[Transform]) -> usize {
729 let mut count = 0;
730 for transform in transforms {
731 if let Transform::Join { .. } | Transform::From(_) = transform {
732 count += 1;
733 }
734 }
735
736 count
737}
738
739fn first_expr_from_projection(projection: &[SelectItem]) -> Option<sql_ast::Expr> {
742 for item in projection {
743 match item {
744 SelectItem::UnnamedExpr(expr) => return Some(expr.clone()),
745 SelectItem::ExprWithAlias { alias, .. } => {
746 return Some(sql_ast::Expr::Identifier(alias.clone()));
747 }
748 SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) => continue,
749 }
750 }
751 None
752}
753
754#[cfg(test)]
755mod test {
756 use insta::assert_snapshot;
757
758 #[test]
759 fn test_variable_after_aggregate() {
760 let query = &r#"
761 from employees
762 group {title, emp_no} (
763 aggregate {emp_salary = average salary}
764 )
765 group {title} (
766 aggregate {avg_salary = average emp_salary}
767 )
768 "#;
769
770 let sql_ast = crate::tests::compile(query).unwrap();
771
772 assert_snapshot!(sql_ast, @r"
773 WITH table_0 AS (
774 SELECT
775 title,
776 AVG(salary) AS _expr_0
777 FROM
778 employees
779 GROUP BY
780 title,
781 emp_no
782 )
783 SELECT
784 title,
785 AVG(_expr_0) AS avg_salary
786 FROM
787 table_0
788 GROUP BY
789 title
790 ");
791 }
792
793 #[test]
794 fn test_derive_filter() {
795 let query = &r#"
805 from employees
806 derive {global_rank = rank country}
807 filter country == "USA"
808 derive {rank = rank country}
809 "#;
810
811 let sql_ast = crate::tests::compile(query).unwrap();
812
813 assert_snapshot!(sql_ast, @r"
814 WITH table_0 AS (
815 SELECT
816 *,
817 RANK() OVER () AS global_rank
818 FROM
819 employees
820 )
821 SELECT
822 *,
823 RANK() OVER () AS rank
824 FROM
825 table_0
826 WHERE
827 country = 'USA'
828 ");
829 }
830
831 #[test]
832 fn test_filter_windowed() {
833 let query = &r#"
835 from tbl1
836 filter (average bar) > 3
837 "#;
838
839 assert_snapshot!(crate::tests::compile(query).unwrap(), @r"
840 WITH table_0 AS (
841 SELECT
842 *,
843 AVG(bar) OVER () AS _expr_0
844 FROM
845 tbl1
846 )
847 SELECT
848 *
849 FROM
850 table_0
851 WHERE
852 _expr_0 > 3
853 ");
854 }
855
856 #[test]
857 fn test_distinct_on_with_aggregate() {
858 let query = &r#"
860 prql target:sql.postgres
861
862 from t1
863 group {id, name} (take 1)
864 aggregate {c=count this}
865 "#;
866
867 assert_snapshot!(crate::tests::compile(query).unwrap(), @r"
868 WITH table_0 AS (
869 SELECT
870 DISTINCT ON (id, name) *
871 FROM
872 t1
873 )
874 SELECT
875 COUNT(*) AS c
876 FROM
877 table_0
878 ");
879 }
880
881 #[test]
882 fn test_join_with_inaccessible_table() {
883 let query = r#"
885 from c = companies
886 join ca = companies_addresses (c.tax_code == ca.company)
887 group c.tax_code (
888 join a = addresses (a.id == ca.address)
889 sort {-ca.created_at}
890 take 2..
891 )
892 sort tax_code
893 "#;
894
895 let err = crate::tests::compile(query).unwrap_err();
896 assert!(err.to_string().contains("not accessible in this context"));
897 }
898}