Skip to main content

rupg_analyze/
lib.rs

1//! Name resolution, type resolution, the query tree, the rewriter for views and rules.
2//!
3//! This crate first ships in milestone M2. See `spec/22-crate-layout.md` section 22.4 and `spec/23-milestones.md`.
4//!
5//! [`analyze`] takes a raw statement of `rupg-sql` and gives a [`Query`] whose expressions have their types, as `parse_analyze` of PostgreSQL does. The rules of type resolution come from `parse_coerce.c`, `parse_func.c` and `parse_oper.c` of PostgreSQL 19, so a query gets the same functions, the same casts and the same errors.
6
7#![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
114/// The OID of the schema `pg_catalog`.
115pub const PG_CATALOG_NAMESPACE: u32 = 11;
116
117/// The facts of the session that the analyzer reads.
118pub trait Env {
119    /// The OIDs of the schemas of `search_path` that exist, in order.
120    fn search_path(&self) -> Vec<u32>;
121    /// The OID of the schema with this name.
122    fn namespace(&self, name: &str) -> Option<u32>;
123    /// The name of the current database.
124    fn database(&self) -> String;
125    /// The input function of the type: the value of a string literal. The settings of the session, such as `DateStyle`, apply.
126    fn input(&self, ty: u32, text: &str, typmod: i32) -> Result<Value>;
127    /// [`Env::input`] for a literal at this place in the query text, so that a warning of the input function points at the literal.
128    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    /// The objects that the user made, or `None` when the session has no catalog.
133    fn catalog(&self) -> Option<&rupg_catalog::Catalog> {
134        None
135    }
136    /// `get_visible_ENR_by_name`: the OID of the table of the trigger when `name` is a transition table of the trigger that runs, as `SPI_register_trigger_data` registers them.
137    fn transition_table(&self, name: &str) -> Option<u32> {
138        let _ = name;
139        None
140    }
141    /// The `allow_system_table_mods` setting, which lets a statement make a relation in a system schema. `initdb` turns it on.
142    fn allow_system_table_mods(&self) -> bool {
143        false
144    }
145    /// The `check_function_bodies` setting: `CREATE FUNCTION` checks the body of a function in SQL or in PL/pgSQL when it is on.
146    fn check_function_bodies(&self) -> bool {
147        true
148    }
149    /// The OID of the materialized view that `CREATE MATERIALIZED VIEW` or `REFRESH MATERIALIZED VIEW` fills, or 0. Only the `INSERT` that fills it can write it, and only there its query takes its place.
150    fn filling(&self) -> u32 {
151        0
152    }
153    /// The row of the file of a sequence as the sessions changed it, or `None` when no session changed it.
154    fn sequence_row(&self, file: u32) -> Option<rupg_catalog::SequenceData> {
155        let _ = file;
156        None
157    }
158    /// `pg_get_expr` of a stored expression of the relation `relation`: the text of the expression with the names of the columns, which the parser can read again, as `ATPostAlterTypeParse` reads the definition of a constraint.
159    ///
160    /// # Errors
161    ///
162    /// `0A000` when the session cannot show a stored expression, and the errors of the deparse.
163    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    /// The number of the session in the table of the sessions, which names its temporary schema `pg_temp_N`, or `None` for a session that cannot have temporary objects.
171    fn temp_number(&self) -> Option<i32> {
172        None
173    }
174    /// `CommandCounterIncrement`: the lookups of the types and the functions by name and by OID see the objects of `catalog`, which the statement changed. A session with no catalog of its own does nothing.
175    fn command_counter_increment(&self, catalog: &rupg_catalog::Catalog) {
176        let _ = catalog;
177    }
178    /// The validator of the language of a new function in a procedural language, such as `plpgsql_validator`, which compiles the body. A session with no language passes the function.
179    ///
180    /// # Errors
181    ///
182    /// The errors of the compile of the body.
183    fn check_function(&self, function: &rupg_catalog::Function) -> Result<()> {
184        let _ = function;
185        Ok(())
186    }
187    /// `ExtractSetVariableArgs` and `validate_option_array_item` for a clause `SET` or `RESET` of a function: the name of the parameter as the stored setting spells it, and the value, or `None` for `RESET`. `SET ... FROM CURRENT` takes the current value.
188    ///
189    /// # Errors
190    ///
191    /// `0A000` when the session has no settings, and the errors of the name and of the value.
192    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    /// `ProcessGUCArray` with `GUC_ACTION_SAVE`: the settings of a function, each one as `name=value`, apply until [`Env::pop_function_settings`]. A session with no settings does nothing.
203    ///
204    /// # Errors
205    ///
206    /// The errors of the names and of the values.
207    fn push_function_settings(&self, config: &[String]) -> Result<()> {
208        let _ = config;
209        Ok(())
210    }
211    /// `AtEOXact_GUC` at the end of [`Env::push_function_settings`]: the settings go back to their values from before, other than the ones that a `SET` changed.
212    fn pop_function_settings(&self) {}
213    /// `evaluate_expr`: the value of an expression that reads no column, such as a value of the bound of a partition.
214    ///
215    /// # Errors
216    ///
217    /// `0A000` when the session cannot run an expression, and the errors of the functions of the expression.
218    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    /// `get_const_expr` with `showtype` -1 of a stored constant, as the detail of an error on the bound of a partition shows it.
223    ///
224    /// # Errors
225    ///
226    /// `0A000` when the session cannot show a constant, and the errors of the output function of the type.
227    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    /// The order of two values of the type, as the default operator class of `btree` of the type gives it.
235    ///
236    /// # Errors
237    ///
238    /// `0A000` when the session cannot compare values, and the error of a type with no order.
239    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
245/// `myTempNamespace`: the OID of the temporary schema of the session in the catalog, or 0 when the session has none yet.
246pub 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/// The parameters of a statement.
253#[derive(Clone, Debug, Default, PartialEq, Eq)]
254pub struct Params {
255    /// The type OID of each parameter, from `$1`. 0 means that the client gave no type.
256    pub types: Vec<u32>,
257    /// True for a statement of the extended protocol, which can use parameters that the client did not declare and gets the types from the query. A simple query has no parameters.
258    pub variable: bool,
259}
260
261/// The state of the analysis of one statement.
262pub(crate) struct Analyzer<'a> {
263    env: &'a dyn Env,
264    /// The schemas that an unqualified name of a function, an operator or another object that is not a relation or a type searches, with `pg_catalog` first when `search_path` does not name it. It has no temporary schema, as the lookups of `namespace.c` skip `myTempNamespace`.
265    path: Vec<u32>,
266    /// `activeSearchPath`: the schemas that an unqualified name of a relation or a type searches. The temporary schema of the session comes first when `search_path` does not name it.
267    temp_path: Vec<u32>,
268    /// The temporary schema of the session, or 0 when it has none yet.
269    temp: u32,
270    params: Vec<u32>,
271    variable: bool,
272    /// The warnings and notices of the analysis, such as a precision that is too large.
273    notices: Vec<Error>,
274    /// The depth of the expression that the analyzer reads now.
275    depth: usize,
276    /// The relations and the names of `FROM`.
277    scope: from::Scope,
278    /// The scopes of the queries outside the subquery that the analyzer reads now, as `parentParseState`. The last is the query just outside it.
279    outer: Vec<from::Scope>,
280    /// The part of the query that the analyzer reads now.
281    kind: agg::Kind,
282    /// `p_hasAggs`: true when the query has an aggregate call.
283    has_aggs: bool,
284    /// For each scope of `outer`, the part of that query that has the subquery and `p_hasAggs` of that query. The part is `None` for a subquery in `FROM`.
285    outer_kinds: Vec<(Option<agg::Kind>, bool)>,
286    /// `p_windowdefs`: the windows of the `WINDOW` clause of the query, then the windows of its `OVER` clauses.
287    windowdefs: Vec<rupg_sql::nodes::WindowDef>,
288    /// `p_hasWindowFuncs`: true when the query has a call of a window function.
289    has_windows: bool,
290    /// True when the next `SELECT` keeps its columns of type `unknown`, as `parse_sub_analyze` with no `resolve_unknowns` does for a part of a set operation.
291    keep_unknowns: bool,
292    /// The views whose queries the analyzer reads now, from the outermost. A view in its own query gives `42P17`, as `fireRIRrules` does.
293    views: Vec<u32>,
294    /// The base type and the typmod of the domain whose check constraint the analyzer reads now, or `None`. Then the name `value` is [`ExprKind::DomainValue`].
295    domain_value: Option<(u32, i32)>,
296    /// The name of the function in SQL whose body the analyzer reads now, and the names of its input arguments, or `None`. A name that is no column can then be an argument, as `sql_fn_post_column_ref` finds it.
297    sql_function: Option<(String, Vec<String>)>,
298    /// The number of the calls of functions that give a set that the analysis of the query has made, and the place of the last one. It takes the place of `p_last_srf`: `CASE` and `COALESCE` compare the number before and after their arguments.
299    srfs: (usize, Option<usize>),
300    /// `p_parent_cte`: the slot of the query of `WITH` in the scope of the query outside, while the analyzer starts the query of that query of `WITH`. The next `SELECT` takes it.
301    parent_cte: Option<usize>,
302    /// The number of work tables of recursive queries that the analysis has made, which gives each its own number.
303    works: usize,
304    /// The number of the statements of `WITH` that change data, which [`Cte::write`] counts.
305    writes: usize,
306    /// The variables of the PL/pgSQL function whose query the analyzer reads now, or `None`.
307    variables: Option<&'a dyn Variables>,
308    /// The target of an assignment of PL/pgSQL, for the coercion of the value in the analysis.
309    assign: Option<plpgsql::Assign>,
310    /// `p_rteperminfos`: the privileges that the statement needs on its relations, for all the levels of the statement.
311    perms: Vec<PermInfo>,
312    /// The OIDs of the functions that the statement calls, for all the levels of the statement and the queries of its views.
313    funcs: Vec<u32>,
314    /// The row types of the relations that [`Analyzer::add_table`] puts in the scope of a statement that defines them, which the catalog of the environment does not have yet.
315    row_types: BTreeMap<u32, u32>,
316    /// The index in [`Analyzer::perms`] of the privileges of the target table of the write that the analyzer reads now, or `None`.
317    target_perm: Option<usize>,
318    /// The state of the rules of the write that the analyzer reads now.
319    rule: rules::State,
320    /// The view of the write that the analyzer reads now when the write goes to the base relation of the view, as `rewriteTargetView` makes it, or `None`.
321    auto_view: Option<autoview::AutoView>,
322    /// The actions of the `WHEN` clauses of the `MERGE` that the analyzer reads now, in the order of the statement, which `view_has_instead_trigger` and `rewriteTargetView` read for a view.
323    merge_actions: Vec<MergeCommand>,
324}
325
326impl<'a> Analyzer<'a> {
327    /// Sets the search path that a view, a rule or a function kept. The temporary schema of the session comes first in the path of the lookups of relations and types.
328    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    /// `variable_coerce_param_hook`: a parameter of unknown type that the query casts to a type gets that type. `Ok(None)` means that the parameters are fixed, so the cast is a normal cast.
387    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
411/// The query of a view or of a materialized view of the catalog, from the text of the view and the `search_path` that the view keeps, or `None` when the catalog has no view with the OID. `pg_get_viewdef` shows this query.
412pub fn view_query(env: &dyn Env, oid: u32) -> Result<Option<Query>> {
413    Analyzer::new(env, &Params::default()).stored_query(oid)
414}
415
416/// The foreign keys that a write of the kind `kind` of the table `table` checks, in the order of the names of their triggers. A leaf partition checks them for its rows of an `UPDATE` or a `DELETE` of its partitioned table.
417///
418/// # Errors
419///
420/// `XX000` for a trigger of a foreign key with no foreign key.
421pub 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
436/// `parse_analyze`: the query tree of a raw statement, which is the `stmt` of a `RawStmt`. For a statement of the extended protocol, the [`Query`] has the type of each parameter.
437pub fn analyze(stmt: &Node, env: &dyn Env, params: &Params) -> Result<Query> {
438    analyze_hooked(stmt, env, params, None)
439}
440
441/// [`analyze`] with the variables of a PL/pgSQL function, if any.
442fn 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
451/// [`analyze_hooked`] with `analyzer` of the value of an assignment of PL/pgSQL to the target `assign`.
452fn 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
462/// The analysis of a statement with the analyzer `analyzer` and the variables of a PL/pgSQL function.
463fn 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    /// `transformStmt` for a statement that has a plan.
475    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    /// The end of the analysis of a statement: the query gets the types of the parameters, the notices, the privileges and the functions that the analysis found.
491    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    /// A session with the default `search_path` and the input functions of a few types.
515    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    /// The name and the type of each column.
553    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    /// The SQLSTATE, the message and the byte offset of the error.
560    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    /// Runs the cases in a thread with 8 MiB of stack. In a debug build the action functions of the parser have large frames, and a statement needs more than the 2 MiB of a test thread.
566    // The thread is not an engine task, so it uses std::thread and not the Tasks trait of rupg-platform.
567    #[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    /// The SQLSTATE, the message, the detail and the hint of the error.
698    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            // amtype is a junk column. The second amname is the same item as the first.
815            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            // A subquery in `FROM` with no `LATERAL` does not see the parts of `FROM` before it. The hint names `LATERAL` only where `LATERAL` could make the name visible.
894            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            // With `LATERAL`, the left side of a full or a right join is visible, but a reference to it is an error.
910            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            // The errors and the positions come from PostgreSQL 19.
962            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            // The errors and the positions come from PostgreSQL 19.
1107            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            // The hint of a close name searches the relations of the outer queries too.
1160            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            // A column of an outer query is a constant for LIMIT of the subquery.
1169            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            // oid is the primary key of pg_class, so relname depends on it.
1189            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}