Skip to main content

uqa_sql/semantics/
portals.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Cursor declaration restrictions and command descriptor diagnostics.
8
9#[cfg(test)]
10mod tests;
11
12use crate::{plan::CommandPlan, SQLError};
13
14#[derive(Clone, Copy, PartialEq, Eq)]
15pub enum PortalDeclarationContext {
16    Sql,
17    PLpgSQL,
18}
19
20pub fn cannot_open_command_cursor(command: &CommandPlan) -> SQLError {
21    let tag = match command {
22        CommandPlan::Insert(_) => "INSERT",
23        CommandPlan::Update(_) => "UPDATE",
24        CommandPlan::Delete(_) => "DELETE",
25        CommandPlan::Merge(_) => "MERGE",
26        CommandPlan::Call { .. } => "CALL",
27        CommandPlan::ShowVariable { .. } => "SHOW",
28        CommandPlan::Explain { .. } => "EXPLAIN",
29        _ => command.name(),
30    };
31    SQLError::Routine {
32        sqlstate: "42P11".into(),
33        message: format!("cannot open {tag} query as cursor"),
34    }
35}
36
37pub fn validate_query_options(
38    context: PortalDeclarationContext,
39    has_row_locks: bool,
40    hold: bool,
41    scroll: Option<bool>,
42) -> Result<(), SQLError> {
43    if has_row_locks && hold {
44        return Err(SQLError::Routine {
45            sqlstate: "0A000".into(),
46            message: "DECLARE CURSOR WITH HOLD ... FOR UPDATE is not supported".into(),
47        });
48    }
49    if has_row_locks && scroll == Some(true) {
50        return Err(SQLError::Routine {
51            sqlstate: "0A000".into(),
52            message: if context == PortalDeclarationContext::PLpgSQL {
53                "DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported".into()
54            } else {
55                "DECLARE SCROLL CURSOR ... FOR UPDATE is not supported".into()
56            },
57        });
58    }
59    Ok(())
60}
61
62pub fn command_scroll_returns_nulls(
63    command: &CommandPlan,
64    scroll: Option<bool>,
65) -> Result<bool, SQLError> {
66    if scroll == Some(true) && matches!(command, CommandPlan::Merge(_)) {
67        return Err(SQLError::Routine {
68            sqlstate: "0A000".into(),
69            message: "DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported".into(),
70        });
71    }
72    let null_returning_values = scroll == Some(true)
73        && matches!(
74            command,
75            CommandPlan::Insert(_) | CommandPlan::Update(_) | CommandPlan::Delete(_)
76        );
77    Ok(null_returning_values)
78}