1#![forbid(unsafe_code)]
8
9mod agg;
10mod autoview;
11mod call;
12mod coerce;
13mod collate;
14mod colname;
15mod constraints_set;
16mod cte;
17mod ddl;
18mod expr;
19mod from;
20mod funcargs;
21mod insert;
22mod jsonfunc;
23mod jsontable;
24mod locking;
25mod merge;
26mod modify;
27pub mod node;
28mod perm;
29mod plpgsql;
30mod poly;
31mod prepare;
32mod record;
33mod resolve;
34mod returning;
35mod rules;
36mod searchcycle;
37mod select;
38mod setop;
39mod sort;
40mod sqljson;
41mod sublink;
42mod subscript;
43mod transform;
44pub mod tsdict;
45mod typcache;
46mod typename;
47pub mod types;
48mod values;
49mod virtual_columns;
50mod walk;
51mod window;
52mod xmlexpr;
53
54use std::collections::BTreeMap;
55
56use rupg_common::{Error, Result, SqlState};
57use rupg_sql::nodes::Node;
58use rupg_types::{Value, oid};
59
60use crate::coerce::AtOpt;
61
62pub use call::OutArg;
63pub use coerce::{Context, Path, can_coerce, explicit_cast, find_path};
64pub use colname::figure_colname;
65pub use constraints_set::set_constraints;
66pub use cte::{Cte, CteRef};
67pub use ddl::{
68 Allowed, Body, CopyRelation, CopyTarget, Defined, Fill, Message, PartitionCheck,
69 REGRESS_SYMBOLS, ReindexOptions, RuleTree, SchemaElements, Script, SqlBody, Take, TopLevel,
70 Transform, acl_dependents, acl_roles, attnums, copy_filter, copy_insert, copy_relation,
71 copy_target, define, find_library, function_body, function_script, function_sqlbody,
72 is_definition, lock_table, owned_objects, reindex_options, rewrite_insert, rule_tree,
73 tablespace_options, trigger_when, vacuum_relations,
74};
75pub use expr::{
76 Aggref, BoolOp, BoolTest, Case, CastForm, CurrentOf, Expr, ExprKind, FieldStore, Func,
77 FuncForm, GroupingFunc, IsJson, JoinRow, JsonBehavior, JsonBehaviorKind, JsonCtor,
78 JsonCtorKind, JsonEncoding, JsonExpr, JsonExprOp, JsonFormat, JsonFormatKind, JsonItemType,
79 JsonReturning, JsonTable, JsonTableColumn, JsonTablePlan, JsonTableScan, JsonValue,
80 JsonWrapper, NamedArg, NullTreatment, PlaceHolder, RowCompare, RowCompareKind, SqlValue,
81 SubLink, SubLinkKind, Subscript, Var, WindowFunc, XmlExpr, XmlOp, func_cost,
82};
83pub use from::{
84 Column, FromFunction, FromItem, Join, JoinKind, Relation, TABLE_OID_ATTNUM, TableSample,
85};
86pub use funcargs::{arg_defaults, expand_arguments};
87pub use insert::{
88 Action, Deferrable, Derived, Foreign, OnConflict, Unique, ViewCheck, Write, WriteKind,
89};
90pub use locking::{RowMark, check_outer_joins, first_row_mark, locked_tables};
91pub use merge::{MatchKind, Merge, MergeClause, MergeCommand};
92pub use perm::{PermInfo, option_on};
93pub use plpgsql::{
94 Variable, Variables, analyze_assign, analyze_assign_part, analyze_with, column_type,
95 plpgsql_cast, relation_by_names, relation_row_type, row_type_fields,
96};
97pub use poly::resolve as resolve_polymorphic;
98pub use prepare::{execute_params, param_types};
99pub use record::{out_columns, record_fields};
100pub use returning::{MERGE_ACTION, RETURNING_NEW, RETURNING_OLD, Shown};
101pub use rules::{Product, analyze_product, copy_rules_error, query_rewrite};
102pub use searchcycle::{CycleClause, SearchClause};
103pub use select::{MinMax, Query, Target};
104pub use setop::{SetKind, SetOp, SetTree, strength_name};
105pub use sort::{GroupingSet, SortGroup, expand_grouping_sets};
106pub use typcache::{default_opclass, equality_for_ordering, is_binary_coercible, sort_operators};
107pub use typename::parse_type;
108pub use virtual_columns::{expand_virtual, expand_virtual_body};
109pub use window::{
110 FRAMEOPTION_END_OFFSET, FRAMEOPTION_END_OFFSET_FOLLOWING, FRAMEOPTION_END_UNBOUNDED_FOLLOWING,
111 FRAMEOPTION_START_OFFSET, Frame, FrameBound, FrameExclusion, FrameMode, WindowClause,
112};
113
114pub const PG_CATALOG_NAMESPACE: u32 = 11;
116
117pub trait Env {
119 fn search_path(&self) -> Vec<u32>;
121 fn namespace(&self, name: &str) -> Option<u32>;
123 fn database(&self) -> String;
125 fn input(&self, ty: u32, text: &str, typmod: i32) -> Result<Value>;
127 fn input_at(&self, ty: u32, text: &str, typmod: i32, at: Option<usize>) -> Result<Value> {
129 let _ = at;
130 self.input(ty, text, typmod)
131 }
132 fn catalog(&self) -> Option<&rupg_catalog::Catalog> {
134 None
135 }
136 fn transition_table(&self, name: &str) -> Option<u32> {
138 let _ = name;
139 None
140 }
141 fn allow_system_table_mods(&self) -> bool {
143 false
144 }
145 fn check_function_bodies(&self) -> bool {
147 true
148 }
149 fn filling(&self) -> u32 {
151 0
152 }
153 fn sequence_row(&self, file: u32) -> Option<rupg_catalog::SequenceData> {
155 let _ = file;
156 None
157 }
158 fn deparse(&self, text: &str, relation: u32) -> Result<String> {
164 let _ = (text, relation);
165 Err(Error::new(
166 SqlState::FEATURE_NOT_SUPPORTED,
167 "the text of a stored expression is not supported here",
168 ))
169 }
170 fn temp_number(&self) -> Option<i32> {
172 None
173 }
174 fn command_counter_increment(&self, catalog: &rupg_catalog::Catalog) {
176 let _ = catalog;
177 }
178 fn check_function(&self, function: &rupg_catalog::Function) -> Result<()> {
184 let _ = function;
185 Ok(())
186 }
187 fn function_setting(
193 &self,
194 set: &rupg_sql::nodes::VariableSetStmt,
195 ) -> Result<(String, Option<String>)> {
196 let _ = set;
197 Err(Error::new(
198 SqlState::FEATURE_NOT_SUPPORTED,
199 "the settings of a function are not supported here",
200 ))
201 }
202 fn push_function_settings(&self, config: &[String]) -> Result<()> {
208 let _ = config;
209 Ok(())
210 }
211 fn pop_function_settings(&self) {}
213 fn evaluate(&self, expr: &Expr) -> Result<Value> {
219 let _ = expr;
220 Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "an expression is not supported here"))
221 }
222 fn deparse_constant(&self, text: &str) -> Result<String> {
228 let _ = text;
229 Err(Error::new(
230 SqlState::FEATURE_NOT_SUPPORTED,
231 "the text of a constant is not supported here",
232 ))
233 }
234 fn compare(&self, ty: u32, a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
240 let _ = (ty, a, b);
241 Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "a comparison is not supported here"))
242 }
243}
244
245pub fn temp_namespace(env: &dyn Env, catalog: &rupg_catalog::Catalog) -> u32 {
247 env.temp_number()
248 .and_then(|n| catalog.schema_by_name(&format!("pg_temp_{n}")))
249 .map_or(0, |s| s.oid)
250}
251
252#[derive(Clone, Debug, Default, PartialEq, Eq)]
254pub struct Params {
255 pub types: Vec<u32>,
257 pub variable: bool,
259}
260
261pub(crate) struct Analyzer<'a> {
263 env: &'a dyn Env,
264 path: Vec<u32>,
266 temp_path: Vec<u32>,
268 temp: u32,
270 params: Vec<u32>,
271 variable: bool,
272 notices: Vec<Error>,
274 depth: usize,
276 scope: from::Scope,
278 outer: Vec<from::Scope>,
280 kind: agg::Kind,
282 has_aggs: bool,
284 outer_kinds: Vec<(Option<agg::Kind>, bool)>,
286 windowdefs: Vec<rupg_sql::nodes::WindowDef>,
288 has_windows: bool,
290 keep_unknowns: bool,
292 views: Vec<u32>,
294 domain_value: Option<(u32, i32)>,
296 sql_function: Option<(String, Vec<String>)>,
298 srfs: (usize, Option<usize>),
300 parent_cte: Option<usize>,
302 works: usize,
304 writes: usize,
306 variables: Option<&'a dyn Variables>,
308 assign: Option<plpgsql::Assign>,
310 perms: Vec<PermInfo>,
312 funcs: Vec<u32>,
314 row_types: BTreeMap<u32, u32>,
316 target_perm: Option<usize>,
318 rule: rules::State,
320 auto_view: Option<autoview::AutoView>,
322 merge_actions: Vec<MergeCommand>,
324}
325
326impl<'a> Analyzer<'a> {
327 fn set_path(&mut self, path: &[u32]) {
329 let temp = self.temp;
330 self.path = path.iter().copied().filter(|&ns| temp == 0 || ns != temp).collect();
331 self.temp_path.clone_from(&self.path);
332 if temp != 0 {
333 self.temp_path.insert(0, temp);
334 }
335 }
336
337 fn new(env: &'a dyn Env, params: &Params) -> Analyzer<'a> {
338 let mut path = env.search_path();
339 if !path.contains(&PG_CATALOG_NAMESPACE) {
340 path.insert(0, PG_CATALOG_NAMESPACE);
341 }
342 let temp = env.catalog().map_or(0, |catalog| temp_namespace(env, catalog));
343 let mut temp_path = path.clone();
344 if temp != 0 {
345 path.retain(|&ns| ns != temp);
346 if !temp_path.contains(&temp) {
347 temp_path.insert(0, temp);
348 }
349 }
350 Analyzer {
351 env,
352 path,
353 temp_path,
354 temp,
355 params: params.types.clone(),
356 variable: params.variable,
357 notices: Vec::new(),
358 depth: 0,
359 scope: from::Scope::default(),
360 outer: Vec::new(),
361 kind: agg::Kind::Other,
362 has_aggs: false,
363 outer_kinds: Vec::new(),
364 windowdefs: Vec::new(),
365 has_windows: false,
366 keep_unknowns: false,
367 views: Vec::new(),
368 domain_value: None,
369 sql_function: None,
370 srfs: (0, None),
371 parent_cte: None,
372 works: 0,
373 writes: 0,
374 variables: None,
375 assign: None,
376 perms: Vec::new(),
377 funcs: Vec::new(),
378 row_types: BTreeMap::new(),
379 target_perm: None,
380 rule: rules::State::default(),
381 auto_view: None,
382 merge_actions: Vec::new(),
383 }
384 }
385
386 pub(crate) fn param_type(
388 &mut self,
389 n: usize,
390 target: u32,
391 at: Option<usize>,
392 ) -> Result<Option<u32>> {
393 if !self.variable || target == oid::UNKNOWN {
394 return Ok(None);
395 }
396 let Some(slot) = self.params.get_mut(n - 1) else { return Ok(None) };
397 if *slot == 0 || *slot == oid::UNKNOWN {
398 *slot = target;
399 } else if *slot != target {
400 return Err(Error::new(
401 SqlState::AMBIGUOUS_PARAMETER,
402 format!("inconsistent types deduced for parameter ${n}"),
403 )
404 .with_detail(format!("{} versus {}", types::name(*slot), types::name(target)))
405 .at_opt(at));
406 }
407 Ok(Some(target))
408 }
409}
410
411pub fn view_query(env: &dyn Env, oid: u32) -> Result<Option<Query>> {
413 Analyzer::new(env, &Params::default()).stored_query(oid)
414}
415
416pub fn foreign_keys(env: &dyn Env, table: u32, kind: WriteKind) -> Result<Vec<Foreign>> {
422 let Some(rel) = env.catalog().and_then(|c| c.relation(table)) else {
423 return Ok(Vec::new());
424 };
425 let columns: Vec<Column> = rel
426 .columns
427 .iter()
428 .map(|c| {
429 let (name, ty, typmod) = from::scope_column(c);
430 Column { name, ty, typmod, not_null: c.not_null && !c.not_null_invalid }
431 })
432 .collect();
433 Analyzer::new(env, &Params::default()).foreign_keys(table, kind, &columns)
434}
435
436pub fn analyze(stmt: &Node, env: &dyn Env, params: &Params) -> Result<Query> {
438 analyze_hooked(stmt, env, params, None)
439}
440
441fn analyze_hooked(
443 stmt: &Node,
444 env: &dyn Env,
445 params: &Params,
446 variables: Option<&dyn Variables>,
447) -> Result<Query> {
448 analyze_in(Analyzer::new(env, params), stmt, variables)
449}
450
451fn analyze_assigned<'a>(
453 mut analyzer: Analyzer<'a>,
454 stmt: &Node,
455 variables: &'a dyn Variables,
456 assign: plpgsql::Assign,
457) -> Result<Query> {
458 analyzer.assign = Some(assign);
459 analyze_in(analyzer, stmt, Some(variables))
460}
461
462fn analyze_in<'a>(
464 mut analyzer: Analyzer<'a>,
465 stmt: &Node,
466 variables: Option<&'a dyn Variables>,
467) -> Result<Query> {
468 analyzer.variables = variables;
469 let query = analyzer.statement(stmt)?;
470 analyzer.finish(query)
471}
472
473impl Analyzer<'_> {
474 fn statement(&mut self, stmt: &Node) -> Result<Query> {
476 match stmt {
477 Node::SelectStmt(select) => self.select(select),
478 Node::InsertStmt(insert) => self.insert(insert),
479 Node::UpdateStmt(update) => self.update(update),
480 Node::DeleteStmt(delete) => self.delete(delete),
481 Node::MergeStmt(merge) => self.merge(merge),
482 Node::CallStmt(call) => self.call(call),
483 _ => Err(Error::new(
484 SqlState::FEATURE_NOT_SUPPORTED,
485 "this statement is not supported yet",
486 )),
487 }
488 }
489
490 fn finish(self, mut query: Query) -> Result<Query> {
492 if self.variable {
493 for (i, ty) in self.params.iter().enumerate() {
494 if *ty == 0 || *ty == oid::UNKNOWN {
495 return Err(Error::new(
496 SqlState::INDETERMINATE_DATATYPE,
497 format!("could not determine data type of parameter ${}", i + 1),
498 ));
499 }
500 }
501 }
502 query.params = self.params;
503 query.notices = self.notices;
504 query.perms = self.perms;
505 query.funcs = self.funcs;
506 Ok(query)
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 pub(crate) struct TestEnv;
516
517 impl Env for TestEnv {
518 fn search_path(&self) -> Vec<u32> {
519 vec![2200]
520 }
521
522 fn namespace(&self, name: &str) -> Option<u32> {
523 match name {
524 "pg_catalog" => Some(PG_CATALOG_NAMESPACE),
525 "public" => Some(2200),
526 _ => None,
527 }
528 }
529
530 fn database(&self) -> String {
531 "postgres".into()
532 }
533
534 fn input(&self, ty: u32, text: &str, _typmod: i32) -> Result<Value> {
535 Ok(match ty {
536 oid::BOOL => Value::Bool(rupg_types::bool_in(text)?),
537 oid::INT4 => Value::Int4(rupg_types::int4_in(text)?),
538 oid::INT8 => Value::Int8(rupg_types::int8_in(text)?),
539 oid::FLOAT8 => Value::Float8(rupg_types::float8_in(text)?),
540 oid::NUMERIC => Value::Numeric(rupg_types::numeric_in(text, -1)?),
541 _ => Value::text(text),
542 })
543 }
544 }
545
546 fn run(sql: &str, params: &Params) -> Result<Query> {
547 let (stmts, _) = rupg_sql::parse(sql)?;
548 let Some(Some(Node::RawStmt(raw))) = stmts.first() else { panic!("no statement in {sql}") };
549 analyze(raw.stmt.as_ref().expect("a statement"), &TestEnv, params)
550 }
551
552 fn columns(sql: &str) -> Vec<(String, u32)> {
554 let query =
555 run(sql, &Params::default()).unwrap_or_else(|e| panic!("{sql}: {}", e.message()));
556 query.targets.into_iter().map(|t| (t.name, t.expr.ty)).collect()
557 }
558
559 fn error(sql: &str) -> (String, String, Option<usize>) {
561 let e = run(sql, &Params::default()).expect_err(sql);
562 (e.state().as_str().to_string(), e.message().to_string(), e.position())
563 }
564
565 #[allow(clippy::disallowed_methods)]
568 pub(crate) fn big_stack(cases: fn()) {
569 std::thread::Builder::new().stack_size(8 << 20).spawn(cases).unwrap().join().unwrap();
570 }
571
572 #[test]
573 fn types_of_columns() {
574 big_stack(|| {
575 let one = |sql: &str| columns(sql).remove(0).1;
576 assert_eq!(
577 columns("SELECT 1, 'a' AS b"),
578 [("?column?".into(), oid::INT4), ("b".into(), oid::TEXT)]
579 );
580 assert_eq!(one("SELECT 1 + 2.5"), oid::NUMERIC);
581 assert_eq!(one("SELECT 'a' || 1"), oid::TEXT);
582 assert_eq!(one("SELECT 2147483648"), oid::INT8);
583 assert_eq!(one("SELECT 9223372036854775808"), oid::NUMERIC);
584 assert_eq!(one("SELECT CASE WHEN true THEN 1 ELSE 2.0 END"), oid::NUMERIC);
585 assert_eq!(one("SELECT GREATEST(1, 2.5)"), oid::NUMERIC);
586 assert_eq!(one("SELECT current_user"), oid::NAME);
587 assert_eq!(one("SELECT now()"), oid::TIMESTAMPTZ);
588 assert_eq!(one("SELECT ARRAY[1, 2]"), oid::INT4_ARRAY);
589 assert_eq!(one("SELECT '{1}'::int[]"), oid::INT4_ARRAY);
590 assert_eq!(one("SELECT 1 IN (1, 2) AND 1 BETWEEN 0 AND 2"), oid::BOOL);
591 assert_eq!(one("SELECT NULLIF(1, 2)"), oid::INT4);
592 assert_eq!(one("SELECT COALESCE(NULL, 1)"), oid::INT4);
593 assert_eq!(one("SELECT abs(-1)"), oid::INT4);
594 assert_eq!(one("SELECT length('abc')"), oid::INT4);
595 let query = run("SELECT 'abc'::varchar(2)", &Params::default()).unwrap();
596 assert_eq!((query.targets[0].expr.ty, query.targets[0].expr.typmod), (oid::VARCHAR, 6));
597 let query = run("SELECT localtimestamp(9)", &Params::default()).unwrap();
598 assert_eq!(
599 query.notices[0].message(),
600 "TIMESTAMP(9) precision reduced to maximum allowed, 6"
601 );
602 });
603 }
604
605 #[test]
606 fn errors() {
607 big_stack(|| {
608 let e = |state: &str, message: &str, at: usize| {
609 (state.to_string(), message.to_string(), Some(at))
610 };
611 assert_eq!(error("SELECT x"), e("42703", "column \"x\" does not exist", 7));
612 assert_eq!(
613 error("SELECT t.x"),
614 e("42P01", "missing FROM-clause entry for table \"t\"", 7)
615 );
616 assert_eq!(
617 error("SELECT *"),
618 e("42601", "SELECT * with no tables specified is not valid", 7)
619 );
620 assert_eq!(error("SELECT $1"), e("42P02", "there is no parameter $1", 7));
621 assert_eq!(
622 error("SELECT 1 + 'a'"),
623 e("22P02", "invalid input syntax for type integer: \"a\"", 11)
624 );
625 assert_eq!(
626 error("SELECT 1 IN (1, 'a')"),
627 e("22P02", "invalid input syntax for type integer: \"a\"", 16)
628 );
629 assert_eq!(
630 error("SELECT foo(1)"),
631 e("42883", "function foo(integer) does not exist", 7)
632 );
633 assert_eq!(
634 error("SELECT lower(1)"),
635 e("42883", "function lower(integer) does not exist", 7)
636 );
637 assert_eq!(
638 error("SELECT 1 + true"),
639 e("42883", "operator does not exist: integer + boolean", 9)
640 );
641 assert_eq!(
642 error("SELECT ARRAY[]"),
643 e("42P18", "cannot determine type of empty array", 7)
644 );
645 assert_eq!(
646 error("SELECT 'a'::varchar(0)"),
647 e("22023", "length for type varchar must be at least 1", 12)
648 );
649 assert_eq!(
650 error("SELECT CASE WHEN 1 THEN 1 END"),
651 e("42804", "argument of CASE/WHEN must be type boolean, not type integer", 17)
652 );
653 assert_eq!(
654 error("SELECT 'a' COLLATE \"xx\""),
655 e("42704", "collation \"xx\" for encoding \"UTF8\" does not exist", 11)
656 );
657 assert_eq!(
658 error("SELECT 1 COLLATE \"C\""),
659 e("42804", "collations are not supported by type integer", 9)
660 );
661 assert_eq!(
662 error("SELECT 1 = ANY (1)"),
663 e("42809", "op ANY/ALL (array) requires array on right side", 9)
664 );
665 assert_eq!(
666 error("SELECT NULLIF(1, 'a'::text)"),
667 e("42883", "operator does not exist: integer = text", 7)
668 );
669 assert_eq!(
670 error("SELECT 1 IS DISTINCT FROM 'x'::text"),
671 e("42883", "operator does not exist: integer = text", 9)
672 );
673 assert_eq!(
674 error("SELECT ARRAY[1, 'a'::text]"),
675 e("42804", "ARRAY types integer and text cannot be matched", 16)
676 );
677 assert_eq!(
678 error("SELECT COALESCE(1, 'a'::text)"),
679 e("42804", "COALESCE types integer and text cannot be matched", 19)
680 );
681 });
682 }
683
684 #[test]
685 fn parameters() {
686 big_stack(|| {
687 let variable = Params { types: Vec::new(), variable: true };
688 let query = run("SELECT $1 + 1, $2", &variable).unwrap();
689 assert_eq!(query.params, [oid::INT4, oid::TEXT]);
690 let e = run("SELECT $1 IS NULL", &variable).unwrap_err();
691 assert_eq!(e.message(), "could not determine data type of parameter $1");
692 let declared = Params { types: vec![oid::INT8], variable: true };
693 assert_eq!(run("SELECT $1 + 1", &declared).unwrap().targets[0].expr.ty, oid::INT8);
694 });
695 }
696
697 fn full_error(sql: &str) -> [String; 4] {
699 let e = run(sql, &Params::default()).expect_err(sql);
700 [
701 e.state().as_str().to_string(),
702 e.message().to_string(),
703 e.detail().unwrap_or_default().to_string(),
704 e.hint().unwrap_or_default().to_string(),
705 ]
706 }
707
708 #[test]
709 fn from_clause() {
710 big_stack(|| {
711 assert_eq!(
712 columns("SELECT * FROM pg_namespace"),
713 [
714 ("oid".into(), oid::OID),
715 ("nspname".into(), oid::NAME),
716 ("nspowner".into(), oid::OID),
717 ("nspacl".into(), oid::ACLITEM_ARRAY)
718 ]
719 );
720 assert_eq!(
721 columns("SELECT a.x, n FROM pg_namespace a(x, n)"),
722 [("x".into(), oid::OID), ("n".into(), oid::NAME)]
723 );
724 let names = |sql: &str| -> Vec<String> {
725 columns(sql).into_iter().map(|(name, _)| name).collect()
726 };
727 assert_eq!(
728 names("SELECT * FROM pg_namespace a JOIN pg_namespace b USING (oid, nspname)"),
729 ["oid", "nspname", "nspowner", "nspacl", "nspowner", "nspacl"]
730 );
731 assert_eq!(names("SELECT u.* FROM pg_am a JOIN pg_am b USING (oid) AS u"), ["oid"]);
732 assert_eq!(
733 names("SELECT a.tableoid, b.* FROM pg_am a NATURAL JOIN pg_am b"),
734 ["tableoid", "oid", "amname", "amhandler", "amtype"]
735 );
736 let query = run("SELECT oid, oid::int + 0 FROM pg_type", &Params::default()).unwrap();
737 assert_eq!(query.targets[0].origin, Some((1247, 1)));
738 assert_eq!(query.targets[1].origin, None);
739 let query =
740 run("SELECT oid FROM pg_am a FULL JOIN pg_am b USING (oid)", &Params::default())
741 .unwrap();
742 assert_eq!(query.targets[0].origin, None);
743 assert!(matches!(query.targets[0].expr.kind, ExprKind::Coalesce(_)));
744 });
745 }
746
747 #[test]
748 fn order_by_errors() {
749 big_stack(|| {
750 let e = |state: &str, message: &str, at: usize| {
751 (state.to_string(), message.to_string(), Some(at))
752 };
753 assert_eq!(
754 error("SELECT oid FROM pg_am ORDER BY 2"),
755 e("42P10", "ORDER BY position 2 is not in select list", 31)
756 );
757 assert_eq!(
758 error("SELECT oid FROM pg_am ORDER BY 'x'"),
759 e("42601", "non-integer constant in ORDER BY", 31)
760 );
761 assert_eq!(
762 error("SELECT amname AS a, oid AS a FROM pg_am ORDER BY a"),
763 e("42702", "ORDER BY \"a\" is ambiguous", 49)
764 );
765 assert_eq!(
766 error("SELECT point(1, 2) AS p ORDER BY p"),
767 e("42883", "could not identify an ordering operator for type point", 33)
768 );
769 assert_eq!(
770 error("SELECT DISTINCT amname FROM pg_am ORDER BY oid"),
771 e(
772 "42P10",
773 "for SELECT DISTINCT, ORDER BY expressions must appear in select list",
774 43
775 )
776 );
777 assert_eq!(
778 error("SELECT DISTINCT ON (oid) amname FROM pg_am ORDER BY amname"),
779 e(
780 "42P10",
781 "SELECT DISTINCT ON expressions must match initial ORDER BY expressions",
782 20
783 )
784 );
785 assert_eq!(
786 error("SELECT amname FROM pg_am LIMIT oid"),
787 e("42P10", "argument of LIMIT must not contain variables", 31)
788 );
789 assert_eq!(
790 error("SELECT amname FROM pg_am LIMIT true"),
791 e("42804", "argument of LIMIT must be type bigint, not type boolean", 31)
792 );
793 assert_eq!(
794 error("SELECT amname FROM pg_am ORDER BY amname USING ="),
795 e("42809", "operator = is not a valid ordering operator", 47)
796 );
797 let (state, message, _) =
798 error("SELECT amname FROM pg_am ORDER BY 1 FETCH FIRST NULL ROWS WITH TIES");
799 assert_eq!(
800 (state.as_str(), message.as_str()),
801 ("2201W", "row count cannot be null in FETCH FIRST ... WITH TIES clause")
802 );
803 });
804 }
805
806 #[test]
807 fn order_by_clauses() {
808 big_stack(|| {
809 let query = run(
810 "SELECT amname FROM pg_am ORDER BY amtype DESC NULLS LAST, 1, amname",
811 &Params::default(),
812 )
813 .unwrap();
814 assert_eq!(query.targets.len(), 2);
816 assert!(!query.targets[0].junk && query.targets[1].junk);
817 let items: Vec<_> =
818 query.sort.iter().map(|s| (s.target, s.operator, s.nulls_first)).collect();
819 assert_eq!(items, [(1, 633, false), (0, 660, false)]);
820 let query = run(
821 "SELECT DISTINCT ON (amtype) amtype, amname FROM pg_am ORDER BY amtype, oid",
822 &Params::default(),
823 )
824 .unwrap();
825 assert!(query.distinct_on);
826 assert_eq!(query.distinct.iter().map(|s| s.target).collect::<Vec<_>>(), [0]);
827 assert_eq!(query.sort.iter().map(|s| s.target).collect::<Vec<_>>(), [0, 2]);
828 let query =
829 run("SELECT DISTINCT amname, 1 FROM pg_am LIMIT 2 OFFSET 1", &Params::default())
830 .unwrap();
831 assert_eq!(query.distinct.iter().map(|s| s.target).collect::<Vec<_>>(), [0, 1]);
832 assert_eq!(query.limit.map(|e| e.ty), Some(oid::INT8));
833 assert_eq!(query.offset.map(|e| e.ty), Some(oid::INT8));
834 });
835 }
836
837 #[test]
838 fn from_errors() {
839 big_stack(|| {
840 let e = |state: &str, message: &str, at: usize| {
841 (state.to_string(), message.to_string(), Some(at))
842 };
843 assert_eq!(
844 error("SELECT 1 FROM nosuch"),
845 e("42P01", "relation \"nosuch\" does not exist", 14)
846 );
847 assert_eq!(
848 error("SELECT 1 FROM x.pg_class"),
849 e("42P01", "relation \"x.pg_class\" does not exist", 14)
850 );
851 assert_eq!(
852 error("SELECT 1 FROM a.b.pg_class"),
853 e("0A000", "cross-database references are not implemented: \"a.b.pg_class\"", 14)
854 );
855 assert_eq!(
856 error("SELECT oid FROM pg_am a, pg_am b"),
857 e("42702", "column reference \"oid\" is ambiguous", 7)
858 );
859 assert_eq!(
860 error("SELECT ctid FROM pg_am"),
861 e("0A000", "the system column \"ctid\" is not supported yet", 7)
862 );
863 let s = |v: [&str; 4]| v.map(str::to_string);
864 assert_eq!(
865 full_error("SELECT amnam FROM pg_am"),
866 s([
867 "42703",
868 "column \"amnam\" does not exist",
869 "",
870 "Perhaps you meant to reference the column \"pg_am.amname\"."
871 ])
872 );
873 assert_eq!(
874 full_error("SELECT pg_am.amname FROM pg_am a"),
875 s([
876 "42P01",
877 "invalid reference to FROM-clause entry for table \"pg_am\"",
878 "",
879 "Perhaps you meant to reference the table alias \"a\"."
880 ])
881 );
882 assert_eq!(
883 full_error(
884 "SELECT 1 FROM pg_am a JOIN (pg_am b JOIN pg_am c ON a.oid = c.oid) ON true"
885 ),
886 s([
887 "42P01",
888 "invalid reference to FROM-clause entry for table \"a\"",
889 "There is an entry for table \"a\", but it cannot be referenced from this part of the query.",
890 ""
891 ])
892 );
893 let entry = "There is an entry for table \"a\", but it cannot be referenced from this part of the query.";
895 let lateral = "To reference that table, you must mark this subquery with LATERAL.";
896 let invalid = "invalid reference to FROM-clause entry for table \"a\"";
897 assert_eq!(
898 full_error("SELECT s.* FROM pg_am a, (SELECT a.amname) s"),
899 s(["42P01", invalid, entry, lateral])
900 );
901 assert_eq!(
902 full_error("SELECT 1 FROM pg_am a JOIN (SELECT a.amname) s ON true"),
903 s(["42P01", invalid, entry, lateral])
904 );
905 assert_eq!(
906 full_error("SELECT 1 FROM pg_am a FULL JOIN (SELECT a.amname) s ON true"),
907 s(["42P01", invalid, entry, ""])
908 );
909 let combining =
911 "The combining JOIN type must be INNER or LEFT for a LATERAL reference.";
912 assert_eq!(
913 full_error("SELECT 1 FROM pg_am a FULL JOIN LATERAL (SELECT a.amname) s ON true"),
914 s(["42P10", invalid, combining, ""])
915 );
916 assert_eq!(
917 full_error("SELECT 1 FROM pg_am a RIGHT JOIN LATERAL (SELECT amname) s ON true"),
918 s(["42P10", invalid, combining, ""])
919 );
920 assert_eq!(
921 full_error("SELECT s.* FROM pg_am a, (SELECT amname) s"),
922 s([
923 "42703",
924 "column \"amname\" does not exist",
925 "There is a column named \"amname\" in table \"a\", but it cannot be referenced from this part of the query.",
926 "To reference that column, you must mark this subquery with LATERAL."
927 ])
928 );
929 assert_eq!(
930 full_error("SELECT a.amname FROM (pg_am a JOIN pg_am b USING (oid)) j"),
931 s([
932 "42P01",
933 "invalid reference to FROM-clause entry for table \"a\"",
934 "There is an entry for table \"a\", but it cannot be referenced from this part of the query.",
935 ""
936 ])
937 );
938 assert_eq!(
939 full_error("SELECT 1 FROM pg_am a, pg_am a"),
940 s(["42712", "table name \"a\" specified more than once", "", ""])
941 );
942 assert_eq!(
943 full_error("SELECT 1 FROM pg_am a JOIN pg_am b USING (nosuch)"),
944 s([
945 "42703",
946 "column \"nosuch\" specified in USING clause does not exist in left table",
947 "",
948 ""
949 ])
950 );
951 assert_eq!(
952 full_error("SELECT 1 FROM pg_am a(x, y, z, w, v)"),
953 s(["42P10", "table \"a\" has 4 columns available but 5 columns specified", "", ""])
954 );
955 });
956 }
957
958 #[test]
959 fn aggregate_errors() {
960 big_stack(|| {
961 let cases = [
963 ("SELECT sum(*) FROM pg_class", "42883", "function sum() does not exist", 7),
964 (
965 "SELECT lower(DISTINCT relname) FROM pg_class",
966 "42809",
967 "DISTINCT specified, but lower is not an aggregate function",
968 7,
969 ),
970 (
971 "SELECT count(*) FROM pg_class WHERE count(*) > 1",
972 "42803",
973 "aggregate functions are not allowed in WHERE",
974 36,
975 ),
976 (
977 "SELECT 1 FROM pg_class JOIN pg_namespace n ON count(*) > 0",
978 "42803",
979 "aggregate functions are not allowed in JOIN conditions",
980 46,
981 ),
982 (
983 "SELECT count(*) FROM pg_class GROUP BY count(*)",
984 "42803",
985 "aggregate functions are not allowed in GROUP BY",
986 39,
987 ),
988 (
989 "SELECT count(*) FROM pg_class LIMIT count(*)",
990 "42803",
991 "aggregate functions are not allowed in LIMIT",
992 36,
993 ),
994 (
995 "SELECT sum(count(*)) FROM pg_class",
996 "42803",
997 "aggregate function calls cannot be nested",
998 11,
999 ),
1000 (
1001 "SELECT count(*) FILTER (WHERE count(*) > 1) FROM pg_class",
1002 "42803",
1003 "aggregate functions are not allowed in FILTER",
1004 30,
1005 ),
1006 (
1007 "SELECT relname, count(*) FROM pg_class",
1008 "42803",
1009 "column \"pg_class.relname\" must appear in the GROUP BY clause or be used in an aggregate function",
1010 7,
1011 ),
1012 (
1013 "SELECT c.relname FROM pg_class c GROUP BY c.relkind",
1014 "42803",
1015 "column \"c.relname\" must appear in the GROUP BY clause or be used in an aggregate function",
1016 7,
1017 ),
1018 (
1019 "SELECT mode(relnatts) FROM pg_class",
1020 "42809",
1021 "WITHIN GROUP is required for ordered-set aggregate mode",
1022 7,
1023 ),
1024 (
1025 "SELECT count(*) WITHIN GROUP (ORDER BY relname) FROM pg_class",
1026 "42809",
1027 "count is not an ordered-set aggregate, so it cannot have WITHIN GROUP",
1028 7,
1029 ),
1030 (
1031 "SELECT count(DISTINCT relname ORDER BY relkind) FROM pg_class",
1032 "42P10",
1033 "in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list",
1034 39,
1035 ),
1036 (
1037 "SELECT count() FROM pg_class",
1038 "42809",
1039 "count(*) must be used to call a parameterless aggregate function",
1040 7,
1041 ),
1042 (
1043 "SELECT now(*) FROM pg_class",
1044 "42809",
1045 "now(*) specified, but now is not an aggregate function",
1046 7,
1047 ),
1048 (
1049 "SELECT count(*) FILTER (WHERE 1) FROM pg_class",
1050 "42804",
1051 "argument of FILTER must be type boolean, not type integer",
1052 30,
1053 ),
1054 (
1055 "SELECT relkind FROM pg_class GROUP BY relkind HAVING relnatts > 1",
1056 "42803",
1057 "column \"pg_class.relnatts\" must appear in the GROUP BY clause or be used in an aggregate function",
1058 53,
1059 ),
1060 (
1061 "SELECT relkind AS relname FROM pg_class GROUP BY relname",
1062 "42803",
1063 "column \"pg_class.relkind\" must appear in the GROUP BY clause or be used in an aggregate function",
1064 7,
1065 ),
1066 (
1067 "SELECT count(*) AS c FROM pg_class GROUP BY 1",
1068 "42803",
1069 "aggregate functions are not allowed in GROUP BY",
1070 7,
1071 ),
1072 (
1073 "SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace GROUP BY n.oid",
1074 "42803",
1075 "column \"c.relname\" must appear in the GROUP BY clause or be used in an aggregate function",
1076 18,
1077 ),
1078 (
1079 "SELECT oid FROM pg_class HAVING true",
1080 "42803",
1081 "column \"pg_class.oid\" must appear in the GROUP BY clause or be used in an aggregate function",
1082 7,
1083 ),
1084 (
1085 "SELECT array_agg(DISTINCT relacl::text::point) FROM pg_class",
1086 "42883",
1087 "could not identify an equality operator for type point",
1088 26,
1089 ),
1090 (
1091 "SELECT upper(relname) FROM pg_class GROUP BY lower(relname)",
1092 "42803",
1093 "column \"pg_class.relname\" must appear in the GROUP BY clause or be used in an aggregate function",
1094 13,
1095 ),
1096 ];
1097 for (sql, state, message, at) in cases {
1098 assert_eq!(error(sql), (state.to_string(), message.to_string(), Some(at)), "{sql}");
1099 }
1100 });
1101 }
1102
1103 #[test]
1104 fn subquery_errors() {
1105 big_stack(|| {
1106 let cases = [
1108 ("SELECT (SELECT 1, 2)", "42601", "subquery must return only one column", 7),
1109 ("SELECT 1 IN (SELECT 1, 2)", "42601", "subquery has too many columns", 9),
1110 ("SELECT 1 IN (SELECT FROM pg_am)", "42601", "subquery has too few columns", 9),
1111 (
1112 "SELECT (SELECT c.amname) FROM pg_am c GROUP BY c.amtype",
1113 "42803",
1114 "subquery uses ungrouped column \"c.amname\" from outer query",
1115 15,
1116 ),
1117 (
1118 "SELECT 1 = ANY (SELECT 'a'::text)",
1119 "42883",
1120 "operator does not exist: integer = text",
1121 9,
1122 ),
1123 (
1124 "SELECT 1 + ANY (SELECT 1)",
1125 "42804",
1126 "row comparison operator must yield type boolean, not type integer",
1127 9,
1128 ),
1129 (
1130 "SELECT (SELECT nosuch FROM pg_am) FROM pg_class",
1131 "42703",
1132 "column \"nosuch\" does not exist",
1133 15,
1134 ),
1135 (
1136 "SELECT (SELECT x.oid FROM pg_am) FROM pg_class c",
1137 "42P01",
1138 "missing FROM-clause entry for table \"x\"",
1139 15,
1140 ),
1141 (
1142 "SELECT 1 FROM pg_am LIMIT (SELECT oid FROM pg_am a2 WHERE a2.oid = pg_am.oid)",
1143 "42P10",
1144 "argument of LIMIT must not contain variables",
1145 67,
1146 ),
1147 ];
1148 for (sql, state, message, at) in cases {
1149 assert_eq!(error(sql), (state.to_string(), message.to_string(), Some(at)), "{sql}");
1150 }
1151 assert_eq!(
1152 error("SELECT ARRAY(SELECT NULL::pg_node_tree)"),
1153 (
1154 "42704".to_string(),
1155 "could not find array type for data type pg_node_tree".to_string(),
1156 None
1157 )
1158 );
1159 assert_eq!(
1161 full_error("SELECT (SELECT amnam FROM pg_am) FROM pg_class")[3],
1162 "Perhaps you meant to reference the column \"pg_am.amname\"."
1163 );
1164 assert_eq!(
1165 full_error("SELECT (SELECT relnam FROM pg_am) FROM pg_class")[3],
1166 "Perhaps you meant to reference the column \"pg_class.relname\" or the column \"pg_class.relam\"."
1167 );
1168 let query = run(
1170 "SELECT (SELECT a2.amname FROM pg_am a2 LIMIT a.oid::int8) FROM pg_am a",
1171 &Params::default(),
1172 )
1173 .expect("an outer column in LIMIT");
1174 let ExprKind::SubLink(sub) = &query.targets[0].expr.kind else { panic!("no SubLink") };
1175 assert!(sub.query.limit.as_ref().and_then(Expr::first_var).is_none());
1176 });
1177 }
1178
1179 #[test]
1180 fn groups() {
1181 big_stack(|| {
1182 let ok =
1183 |sql: &str| run(sql, &Params::default()).unwrap_or_else(|e| panic!("{sql}: {e:?}"));
1184 let query =
1185 ok("SELECT relkind, count(*) FROM pg_class GROUP BY relkind HAVING count(*) > 1");
1186 assert!(query.grouped && query.having.is_some());
1187 assert_eq!(query.group.iter().map(|g| g.target).collect::<Vec<_>>(), [0]);
1188 assert!(ok("SELECT relname FROM pg_class GROUP BY oid").grouped);
1190 assert!(ok("SELECT lower(relname) FROM pg_class GROUP BY lower(relname)").grouped);
1191 assert!(ok("SELECT count(relname ORDER BY relname, relkind) FROM pg_class").grouped);
1192 assert!(ok("SELECT 1 HAVING true").grouped);
1193 assert!(ok("SELECT count(*) FROM pg_class GROUP BY ()").group.is_empty());
1194 assert!(!ok("SELECT relname FROM pg_class").grouped);
1195 });
1196 }
1197}