Skip to main content

rupg_analyze/
window.rs

1//! Window functions, as `ParseFuncOrColumn` and `transformWindowFuncCall` make a call and `transformWindowDefinitions` makes the windows of a query.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_pgcatalog::builtin::{self, ProcRow};
5use rupg_sql::nodes::{
6    Equal, FRAMEOPTION_DEFAULTS, FRAMEOPTION_END_OFFSET_PRECEDING, FRAMEOPTION_EXCLUDE_CURRENT_ROW,
7    FRAMEOPTION_EXCLUDE_GROUP, FRAMEOPTION_EXCLUDE_TIES, FRAMEOPTION_GROUPS, FRAMEOPTION_RANGE,
8    FRAMEOPTION_ROWS, FRAMEOPTION_START_OFFSET_FOLLOWING, FRAMEOPTION_START_OFFSET_PRECEDING,
9    FRAMEOPTION_START_UNBOUNDED_FOLLOWING, FRAMEOPTION_START_UNBOUNDED_PRECEDING, Node, WindowDef,
10};
11use rupg_types::oid;
12
13use crate::Analyzer;
14use crate::agg::{Kind, Parts, SRF_HINT};
15use crate::coerce::{AtOpt, Context, can_coerce};
16use crate::expr::{Expr, ExprKind, WindowFunc};
17use crate::resolve::FUNC_MAX_ARGS;
18use crate::select::Target;
19use crate::sort::{SortGroup, invalid_reference};
20use crate::typcache::ordering_properties;
21use crate::typename::place;
22use crate::types;
23
24/// `FRAMEOPTION_END_UNBOUNDED_FOLLOWING`. The grammar makes the bits of the end of a frame with a shift of the bits of the start, so it does not name this bit.
25pub const FRAMEOPTION_END_UNBOUNDED_FOLLOWING: i32 = FRAMEOPTION_START_UNBOUNDED_FOLLOWING << 1;
26/// `FRAMEOPTION_END_OFFSET_FOLLOWING`, which the grammar does not name.
27pub const FRAMEOPTION_END_OFFSET_FOLLOWING: i32 = FRAMEOPTION_START_OFFSET_FOLLOWING << 1;
28/// `FRAMEOPTION_START_OFFSET`: the start of the frame has an offset.
29pub const FRAMEOPTION_START_OFFSET: i32 =
30    FRAMEOPTION_START_OFFSET_PRECEDING | FRAMEOPTION_START_OFFSET_FOLLOWING;
31/// `FRAMEOPTION_END_OFFSET`: the end of the frame has an offset.
32pub const FRAMEOPTION_END_OFFSET: i32 =
33    FRAMEOPTION_END_OFFSET_PRECEDING | FRAMEOPTION_END_OFFSET_FOLLOWING;
34
35/// `BTINRANGE_PROC`: the number of the `in_range` support function of a btree family.
36const BTINRANGE_PROC: i16 = 3;
37
38/// The frame of a window, as the fields `frameOptions`, `startOffset`, `endOffset`, `startInRangeFunc`, `endInRangeFunc`, `inRangeAsc` and `inRangeNullsFirst` of `WindowClause`.
39#[derive(Clone, Debug, PartialEq)]
40pub struct Frame {
41    /// `frameOptions`: the bits `FRAMEOPTION_*` of the mode, the bounds and the exclusion.
42    pub options: i32,
43    /// The offset of the start, of type `bigint` in `ROWS` and `GROUPS` mode.
44    pub start: Option<Expr>,
45    /// The offset of the end, of type `bigint` in `ROWS` and `GROUPS` mode.
46    pub end: Option<Expr>,
47    /// `startInRangeFunc`: the `in_range` function of the start in `RANGE` mode with an offset, or 0.
48    pub start_in_range: u32,
49    /// `endInRangeFunc`: the `in_range` function of the end in `RANGE` mode with an offset, or 0.
50    pub end_in_range: u32,
51    /// `inRangeAsc`: true when the column of `ORDER BY` sorts in ascending order.
52    pub in_range_asc: bool,
53    /// `inRangeNullsFirst`: true when the nulls of the column of `ORDER BY` come first.
54    pub in_range_nulls_first: bool,
55}
56
57impl Default for Frame {
58    fn default() -> Self {
59        Frame {
60            options: FRAMEOPTION_DEFAULTS,
61            start: None,
62            end: None,
63            start_in_range: 0,
64            end_in_range: 0,
65            in_range_asc: false,
66            in_range_nulls_first: false,
67        }
68    }
69}
70
71/// The mode of a frame.
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum FrameMode {
74    Range,
75    Rows,
76    Groups,
77}
78
79/// The start or the end of a frame.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum FrameBound {
82    UnboundedPreceding,
83    /// An offset before the current row.
84    Preceding,
85    CurrentRow,
86    /// An offset after the current row.
87    Following,
88    UnboundedFollowing,
89}
90
91/// The rows that the clause `EXCLUDE` removes from a frame.
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum FrameExclusion {
94    NoOthers,
95    CurrentRow,
96    /// The current row and its peers.
97    Group,
98    /// The peers of the current row, but not the current row.
99    Ties,
100}
101
102impl Frame {
103    /// The mode of the frame.
104    pub fn mode(&self) -> FrameMode {
105        if self.options & FRAMEOPTION_ROWS != 0 {
106            FrameMode::Rows
107        } else if self.options & FRAMEOPTION_GROUPS != 0 {
108            FrameMode::Groups
109        } else {
110            FrameMode::Range
111        }
112    }
113
114    /// The start of the frame.
115    pub fn start_bound(&self) -> FrameBound {
116        self.bound(0)
117    }
118
119    /// The end of the frame.
120    pub fn end_bound(&self) -> FrameBound {
121        self.bound(1)
122    }
123
124    /// The bound that the bits of the start give, with a shift of 1 for the end.
125    fn bound(&self, shift: i32) -> FrameBound {
126        let has = |bit: i32| self.options & (bit << shift) != 0;
127        if has(FRAMEOPTION_START_UNBOUNDED_PRECEDING) {
128            FrameBound::UnboundedPreceding
129        } else if has(FRAMEOPTION_START_OFFSET_PRECEDING) {
130            FrameBound::Preceding
131        } else if has(FRAMEOPTION_START_OFFSET_FOLLOWING) {
132            FrameBound::Following
133        } else if has(FRAMEOPTION_START_UNBOUNDED_FOLLOWING) {
134            FrameBound::UnboundedFollowing
135        } else {
136            FrameBound::CurrentRow
137        }
138    }
139
140    /// The rows that the frame does not have.
141    pub fn exclusion(&self) -> FrameExclusion {
142        if self.options & FRAMEOPTION_EXCLUDE_CURRENT_ROW != 0 {
143            FrameExclusion::CurrentRow
144        } else if self.options & FRAMEOPTION_EXCLUDE_GROUP != 0 {
145            FrameExclusion::Group
146        } else if self.options & FRAMEOPTION_EXCLUDE_TIES != 0 {
147            FrameExclusion::Ties
148        } else {
149            FrameExclusion::NoOthers
150        }
151    }
152}
153
154/// A window of a query, as `WindowClause`.
155#[derive(Clone, Debug, PartialEq)]
156pub struct WindowClause {
157    /// The name of a window of the `WINDOW` clause, or `None` for a window that only an `OVER` clause gives.
158    pub name: Option<String>,
159    /// The name of the window that this window copies.
160    pub refname: Option<String>,
161    /// The items of `PARTITION BY`.
162    pub partition: Vec<SortGroup>,
163    /// The items of `ORDER BY`.
164    pub order: Vec<SortGroup>,
165    /// `copiedOrder`: true when the items of `ORDER BY` come from the window of `refname`.
166    pub copied_order: bool,
167    /// The frame of the window.
168    pub frame: Frame,
169}
170
171/// An error with the SQLSTATE `42P20`.
172fn windowing(message: String, at: Option<usize>) -> Error {
173    Error::new(SqlState::WINDOWING_ERROR, message).at_opt(at)
174}
175
176/// The error of a feature of windows that the analyzer does not have yet.
177fn not_yet(what: &str, at: Option<usize>) -> Error {
178    Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")).at_opt(at)
179}
180
181/// The error of a window that the query does not define.
182fn no_window(name: &str, at: Option<usize>) -> Error {
183    Error::new(SqlState::UNDEFINED_OBJECT, format!("window \"{name}\" does not exist")).at_opt(at)
184}
185
186impl Analyzer<'_> {
187    /// The checks of `ParseFuncOrColumn` for a call with `OVER`, then `transformWindowFuncCall`: the place of the call and the number of its window. `aggregate` has the name of the function for an aggregate.
188    pub(crate) fn window_call(
189        &mut self,
190        proc: &ProcRow,
191        aggregate: Option<&str>,
192        args: Vec<Expr>,
193        parts: Parts<'_>,
194        ty: u32,
195        at: Option<usize>,
196    ) -> Result<Expr> {
197        let Some(over) = parts.over else {
198            return Err(Error::internal("a window function call with no OVER"));
199        };
200        let unsupported =
201            |message: &str| Error::new(SqlState::FEATURE_NOT_SUPPORTED, message).at_opt(at);
202        if parts.distinct {
203            return Err(unsupported("DISTINCT is not implemented for window functions"));
204        }
205        if let Some(name) = aggregate {
206            if args.len() > FUNC_MAX_ARGS - 1 {
207                return Err(Error::new(
208                    SqlState::TOO_MANY_ARGUMENTS,
209                    format!("aggregates cannot have more than {} arguments", FUNC_MAX_ARGS - 1),
210                )
211                .at_opt(at));
212            }
213            if args.is_empty() && !parts.star {
214                return Err(Error::new(
215                    SqlState::WRONG_OBJECT_TYPE,
216                    format!("{name}(*) must be used to call a parameterless aggregate function"),
217                )
218                .at_opt(at));
219            }
220        }
221        if !parts.order.is_empty() {
222            return Err(unsupported("aggregate ORDER BY is not implemented for window functions"));
223        }
224        if aggregate.is_none() && parts.filter.is_some() {
225            return Err(unsupported(
226                "FILTER is not implemented for non-aggregate window functions",
227            ));
228        }
229        if self.srfs.0 != parts.srfs {
230            return Err(Error::new(
231                SqlState::FEATURE_NOT_SUPPORTED,
232                "window function calls cannot contain set-returning function calls",
233            )
234            .with_hint(SRF_HINT)
235            .at_opt(self.srfs.1));
236        }
237        if proc.retset {
238            return Err(Error::new(
239                SqlState::INVALID_FUNCTION_DEFINITION,
240                "window functions cannot return sets",
241            )
242            .at_opt(at));
243        }
244        if self.has_windows
245            && let Some(inner) = args.iter().find_map(Expr::first_window)
246        {
247            return Err(windowing("window function calls cannot be nested".into(), inner.location));
248        }
249        let message = match self.kind {
250            Kind::Select | Kind::OrderBy | Kind::DistinctOn => None,
251            Kind::JoinOn => Some("window functions are not allowed in JOIN conditions".to_owned()),
252            Kind::FromFunction => {
253                Some("window functions are not allowed in functions in FROM".to_owned())
254            }
255            Kind::WindowPartition
256            | Kind::WindowOrder
257            | Kind::WindowRange
258            | Kind::WindowRows
259            | Kind::WindowGroups => {
260                Some("window functions are not allowed in window definitions".to_owned())
261            }
262            Kind::Check => Some("window functions are not allowed in check constraints".to_owned()),
263            Kind::Where
264            | Kind::Having
265            | Kind::Filter
266            | Kind::GroupBy
267            | Kind::Limit
268            | Kind::Offset
269            | Kind::Values
270            | Kind::ValuesSingle
271            | Kind::UpdateSource
272            | Kind::MergeWhen
273            | Kind::Returning
274            | Kind::ColumnDefault
275            | Kind::FunctionDefault
276            | Kind::IndexExpression
277            | Kind::StatsExpression
278            | Kind::IndexPredicate
279            | Kind::ExecuteParameter
280            | Kind::Transform
281            | Kind::TriggerWhen
282            | Kind::CallArgument
283            | Kind::PartitionBound
284            | Kind::PartitionExpression
285            | Kind::Generated => {
286                Some(format!("window functions are not allowed in {}", self.kind.name()))
287            }
288            Kind::Other => return Err(not_yet("a window function in this place", at)),
289        };
290        if let Some(message) = message {
291            return Err(windowing(message, at));
292        }
293        let winref = if let Some(name) = &over.name {
294            let found = self.windowdefs.iter().position(|d| d.name.as_ref() == Some(name));
295            match found {
296                Some(i) => i + 1,
297                None => return Err(no_window(name, place(over.location))),
298            }
299        } else {
300            // A window of an `OVER` clause that is the same as a window before it is that window.
301            let same = |d: &WindowDef| {
302                d.refname == over.refname
303                    && d.partitionClause.equal(&over.partitionClause)
304                    && d.orderClause.equal(&over.orderClause)
305                    && d.frameOptions == over.frameOptions
306                    && d.startOffset.equal(&over.startOffset)
307                    && d.endOffset.equal(&over.endOffset)
308            };
309            match self.windowdefs.iter().position(same) {
310                Some(i) => i + 1,
311                None => {
312                    self.windowdefs.push(over.clone());
313                    self.windowdefs.len()
314                }
315            }
316        };
317        self.has_windows = true;
318        let call = WindowFunc {
319            oid: proc.oid,
320            args,
321            winref,
322            star: parts.star,
323            agg: aggregate.is_some(),
324            filter: parts.filter.map(Box::new),
325            nulls: parts.nulls,
326        };
327        Ok(Expr { kind: ExprKind::Window(Box::new(call)), ty, typmod: -1, location: at })
328    }
329
330    /// `transformWindowDefinitions`: the windows of the query, in the order of their numbers. A window can copy a window before it.
331    pub(crate) fn window_clauses(
332        &mut self,
333        targets: &mut Vec<Target>,
334    ) -> Result<Vec<WindowClause>> {
335        let defs = self.windowdefs.clone();
336        let mut result: Vec<WindowClause> = Vec::with_capacity(defs.len());
337        for def in &defs {
338            let at = place(def.location);
339            let named = |name: &str| result.iter().find(|w| w.name.as_deref() == Some(name));
340            if let Some(name) = &def.name
341                && named(name).is_some()
342            {
343                return Err(windowing(format!("window \"{name}\" is already defined"), at));
344            }
345            let base = match &def.refname {
346                Some(refname) => {
347                    Some(named(refname).ok_or_else(|| no_window(refname, at))?.clone())
348                }
349                None => None,
350            };
351            let order = self.window_order(&def.orderClause, targets)?;
352            let partition = self.window_partition(&def.partitionClause, targets, &order)?;
353            let mut window = WindowClause {
354                name: def.name.as_deref().map(str::to_owned),
355                refname: def.refname.as_deref().map(str::to_owned),
356                partition,
357                order,
358                copied_order: false,
359                frame: Frame::default(),
360            };
361            if let Some(base) = base {
362                let refname = &base.name.unwrap_or_default();
363                if !window.partition.is_empty() {
364                    return Err(windowing(
365                        format!("cannot override PARTITION BY clause of window \"{refname}\""),
366                        at,
367                    ));
368                }
369                window.partition = base.partition;
370                if !window.order.is_empty() && !base.order.is_empty() {
371                    return Err(windowing(
372                        format!("cannot override ORDER BY clause of window \"{refname}\""),
373                        at,
374                    ));
375                }
376                if window.order.is_empty() {
377                    window.order = base.order;
378                    window.copied_order = true;
379                }
380                if base.frame.options != FRAMEOPTION_DEFAULTS {
381                    let error = windowing(
382                        format!("cannot copy window \"{refname}\" because it has a frame clause"),
383                        at,
384                    );
385                    // A bare `OVER (name)` can name the window with no parentheses.
386                    let bare = def.name.is_none()
387                        && def.orderClause.is_empty()
388                        && def.frameOptions == FRAMEOPTION_DEFAULTS;
389                    return Err(if bare {
390                        error.with_hint("Omit the parentheses in this OVER clause.")
391                    } else {
392                        error
393                    });
394                }
395            }
396            let options = def.frameOptions;
397            window.frame.options = options;
398            // RANGE with an offset needs exactly one item of `ORDER BY`, and its btree family has the `in_range` functions.
399            let mut family = (0, 0);
400            if options & FRAMEOPTION_RANGE != 0
401                && options & (FRAMEOPTION_START_OFFSET | FRAMEOPTION_END_OFFSET) != 0
402            {
403                let [item] = window.order.as_slice() else {
404                    return Err(windowing(
405                        "RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"
406                            .into(),
407                        at,
408                    ));
409                };
410                let (opfamily, intype, descending) = ordering_properties(item.operator)
411                    .ok_or_else(|| {
412                        Error::internal(format!(
413                            "operator {} is not a valid ordering operator",
414                            item.operator
415                        ))
416                    })?;
417                family = (opfamily, intype);
418                window.frame.in_range_asc = !descending;
419                window.frame.in_range_nulls_first = item.nulls_first;
420            }
421            if options & FRAMEOPTION_GROUPS != 0 && window.order.is_empty() {
422                return Err(windowing("GROUPS mode requires an ORDER BY clause".into(), at));
423            }
424            (window.frame.start, window.frame.start_in_range) =
425                self.frame_offset(options, family, def.startOffset.as_ref())?;
426            (window.frame.end, window.frame.end_in_range) =
427                self.frame_offset(options, family, def.endOffset.as_ref())?;
428            result.push(window);
429        }
430        Ok(result)
431    }
432
433    /// `transformFrameOffset`: the offset of a bound of the frame, and the `in_range` function of the bound in `RANGE` mode. In `ROWS` and `GROUPS` mode the offset is a `bigint`. In `RANGE` mode the `in_range` functions of the family of the item of `ORDER BY` give the type of the offset. The offset cannot read a column of the query.
434    fn frame_offset(
435        &mut self,
436        options: i32,
437        (family, intype): (u32, u32),
438        node: Option<&Node>,
439    ) -> Result<(Option<Expr>, u32)> {
440        let Some(node) = node else { return Ok((None, 0)) };
441        let (construct, expr, func) = if options & FRAMEOPTION_ROWS != 0 {
442            let expr = self.with_kind(Kind::WindowRows, |a| a.transform(Some(node)))?;
443            ("ROWS", self.coerce_to_specific(expr, oid::INT8, "ROWS")?, 0)
444        } else if options & FRAMEOPTION_RANGE != 0 {
445            let expr = self.with_kind(Kind::WindowRange, |a| a.transform(Some(node)))?;
446            let ty = expr.ty;
447            // An offset of a type that is not known yet takes the type of the column first.
448            let preferred = if ty == oid::UNKNOWN { intype } else { ty };
449            let (mut funcs, mut matches) = (0, 0);
450            let (mut selected, mut func) = (0, 0);
451            for proc in builtin::amprocs(family, intype) {
452                if proc.number != BTINRANGE_PROC {
453                    continue;
454                }
455                funcs += 1;
456                if !can_coerce(&[ty], &[proc.right], Context::Implicit) {
457                    continue;
458                }
459                matches += 1;
460                if selected != preferred {
461                    (selected, func) = (proc.right, proc.proc);
462                }
463            }
464            let unsupported = |message: String| {
465                Error::new(SqlState::FEATURE_NOT_SUPPORTED, message).at_opt(expr.place())
466            };
467            let column = types::name(intype);
468            if funcs == 0 {
469                return Err(unsupported(format!(
470                    "RANGE with offset PRECEDING/FOLLOWING is not supported for column type {column}"
471                )));
472            }
473            if matches == 0 {
474                return Err(unsupported(format!(
475                    "RANGE with offset PRECEDING/FOLLOWING is not supported for column type {column} and offset type {}",
476                    types::name(ty)
477                ))
478                .with_hint("Cast the offset value to an appropriate type."));
479            }
480            if matches != 1 && selected != preferred {
481                return Err(unsupported(format!(
482                    "RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type {column} and offset type {}",
483                    types::name(ty)
484                ))
485                .with_hint("Cast the offset value to the exact intended type."));
486            }
487            ("RANGE", self.coerce_to_specific(expr, selected, "RANGE")?, func)
488        } else {
489            let expr = self.with_kind(Kind::WindowGroups, |a| a.transform(Some(node)))?;
490            ("GROUPS", self.coerce_to_specific(expr, oid::INT8, "GROUPS")?, 0)
491        };
492        if let Some(var) = expr.first_var() {
493            return Err(invalid_reference(
494                format!("argument of {construct} must not contain variables"),
495                var.location,
496            ));
497        }
498        Ok((Some(expr), func))
499    }
500}