Line data Source code
1 : //! The inputs of a session: the named scalar parameters of its scripts and their values.
2 : //!
3 : //! Every named scalar parameter `:min` of a script with a current analysis is an input of the session, one per name.
4 : //! Its value is the `@param :min = <literal>` tag of a script (`analyzer::param_tags`), a literal in the Hyper dialect
5 : //! (`sql_literal`). The scripts of the session that use the input hold the same value. A script binds the value of its
6 : //! own tag, a script without one binds the value of the first tag in session order, so a script holds its values on
7 : //! its own and the session reads one value per input.
8 : //!
9 : //! The type of an input is the type of a cast of the parameter, `:min::int`, the first cast in session order, or the
10 : //! type of its literal: `interval '3' day` is an interval, `10` a bigint and `'EU'` text. The unit of an interval is
11 : //! part of its value, a control picks it, so the type of the literal of an interval is `interval` without fields.
12 : //!
13 : //! The planner binds the literal in the dialect of the connection of the statement, `INTERVAL '3' DAY` on Trino. A
14 : //! literal that the dialect can't express and an input without a value are errors of the plan.
15 :
16 : use crate::planner::{Prepared, SessionPlanRequest};
17 : use dashql_buffers::formatting::FormattingDialect;
18 : use dashql_core::analyzer::{AnalyzedScript, ParameterKind, SqlType, SqlTypeKind};
19 : use dashql_core::sql_literal::SqlLiteral;
20 : use dashql_parser::parser::context::resolve_text_span;
21 : use dashql_parser::span::TextSpan;
22 : use std::collections::BTreeMap;
23 : use std::sync::Arc;
24 :
25 : /// Where the type of an input comes from
26 : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
27 : pub enum InputTypeSource {
28 : /// A cast of the parameter in a script
29 : Cast,
30 : /// The literal of the value
31 : Literal,
32 : /// Neither, the input is text until it has a value
33 : None,
34 : }
35 :
36 : /// A statement that uses an input
37 : #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
38 : pub struct InputConsumer {
39 : /// The script index of the request
40 : pub script: u32,
41 : /// The statement
42 : pub statement: u32,
43 : }
44 :
45 : /// An input of the session
46 : #[derive(Clone, Debug, PartialEq)]
47 : pub struct SessionInput {
48 : /// The name of the parameter
49 : pub name: Arc<str>,
50 : /// The type
51 : pub sql_type: SqlType,
52 : /// Where the type comes from
53 : pub type_source: InputTypeSource,
54 : /// The value of the first `@param` tag with a value in session order, None if no script has one
55 : pub literal: Option<SqlLiteral>,
56 : /// The statements that use the input, in session order
57 : pub consumers: Vec<InputConsumer>,
58 : /// The problems of the input, e.g. casts to different types
59 : pub warnings: Vec<String>,
60 : }
61 :
62 : /// The inputs of a session, in the order of their first use
63 : #[derive(Clone, Debug, Default, PartialEq)]
64 : pub struct SessionInputs {
65 : pub inputs: Vec<SessionInput>,
66 : }
67 :
68 : impl SessionInputs {
69 : /// An input by name
70 26 : pub fn get(&self, name: &str) -> Option<&SessionInput> {
71 34 : self.inputs.iter().find(|i| &*i.name == name)
72 26 : }
73 :
74 : /// The value that a script binds to an input: the value of its own tag, the value of the session otherwise
75 156 : pub fn value_of<'a>(&'a self, analyzed: &'a AnalyzedScript, name: &str) -> Option<&'a SqlLiteral> {
76 279 : if let Some(tag) = analyzed.param_values.iter().find(|t| &*t.name == name) {
77 130 : return tag.literal.as_ref();
78 26 : }
79 26 : self.get(name).and_then(|i| i.literal.as_ref())
80 156 : }
81 : }
82 :
83 : /// Collect the inputs of the scripts of a session that have a current analysis
84 478 : pub fn collect_session_inputs(request: &SessionPlanRequest) -> SessionInputs {
85 478 : let mut inputs: Vec<SessionInput> = Vec::new();
86 478 : let mut index: BTreeMap<Arc<str>, usize> = BTreeMap::new();
87 951 : for (si, script) in request.scripts.iter().enumerate() {
88 951 : let Some(p) = Prepared::lenient(script.script) else { continue };
89 936 : for parameter in &p.analyzed.parameters {
90 407 : let (ParameterKind::Scalar, Some(name)) = (parameter.kind, ¶meter.name) else { continue };
91 : // Positional parameters are no inputs
92 74 : if name.parse::<u32>().is_ok() {
93 1 : continue;
94 73 : }
95 73 : let i = *index.entry(name.clone()).or_insert_with(|| {
96 62 : inputs.push(SessionInput {
97 62 : name: name.clone(),
98 62 : sql_type: SqlType::scalar(SqlTypeKind::Text),
99 62 : type_source: InputTypeSource::None,
100 62 : literal: None,
101 62 : consumers: Vec::new(),
102 62 : warnings: Vec::new(),
103 62 : });
104 62 : inputs.len() - 1
105 62 : });
106 73 : let input = &mut inputs[i];
107 73 : if let Some(statement) = parameter.ast_statement_id {
108 73 : let consumer = InputConsumer { script: si as u32, statement };
109 73 : if !input.consumers.contains(&consumer) {
110 72 : input.consumers.push(consumer);
111 72 : }
112 0 : }
113 : // The first cast in session order
114 5 : match ¶meter.cast_type {
115 14 : Some(cast) if input.type_source == InputTypeSource::None => {
116 9 : input.sql_type = cast.clone();
117 9 : input.type_source = InputTypeSource::Cast;
118 9 : }
119 5 : Some(cast) if input.sql_type != *cast => {
120 2 : let warning = format!("input :{name} is cast to {} and to {cast}", input.sql_type);
121 2 : if !input.warnings.contains(&warning) {
122 1 : input.warnings.push(warning);
123 1 : }
124 : }
125 62 : _ => {}
126 : }
127 : }
128 : }
129 : // The values, the first tag with a value in session order
130 951 : for script in request.scripts {
131 951 : let Some(p) = Prepared::lenient(script.script) else { continue };
132 936 : for tag in &p.analyzed.param_values {
133 60 : if let (Some(&i), Some(literal)) = (index.get(&tag.name), &tag.literal) {
134 55 : inputs[i].literal.get_or_insert_with(|| literal.clone());
135 5 : }
136 : }
137 : }
138 : // An input without a cast has the type of its literal
139 478 : for input in inputs.iter_mut().filter(|i| i.type_source == InputTypeSource::None) {
140 53 : if let Some(mut literal_type) = input.literal.as_ref().and_then(SqlLiteral::sql_type) {
141 41 : if let SqlTypeKind::Interval { fields, .. } = &mut literal_type.kind {
142 8 : *fields = None;
143 33 : }
144 41 : input.sql_type = literal_type;
145 41 : input.type_source = InputTypeSource::Literal;
146 12 : }
147 : }
148 478 : SessionInputs { inputs }
149 478 : }
150 :
151 : /// The bindings of the inputs of a statement: the text span of every named scalar parameter and its literal in the
152 : /// dialect of the connection, and the problems with their text spans
153 : pub(crate) struct InputBindings {
154 : pub edits: Vec<(TextSpan, String)>,
155 : pub errors: Vec<(TextSpan, String)>,
156 : /// The bound values by name in the Hyper dialect, for the result key
157 : pub values: BTreeMap<Arc<str>, String>,
158 : }
159 :
160 : /// Bind the inputs of a statement, every statement if None
161 2105 : pub(crate) fn bind_inputs(inputs: &SessionInputs, p: &Prepared, statement: Option<u32>, dialect: FormattingDialect) -> InputBindings {
162 2105 : let mut out = InputBindings { edits: Vec::new(), errors: Vec::new(), values: BTreeMap::new() };
163 2105 : for parameter in &p.analyzed.parameters {
164 742 : let (ParameterKind::Scalar, Some(name)) = (parameter.kind, ¶meter.name) else { continue };
165 160 : if name.parse::<u32>().is_ok() || (statement.is_some() && parameter.ast_statement_id != statement) {
166 4 : continue;
167 156 : }
168 156 : let span = resolve_text_span(&p.scanned.text, &p.scanned.symbols, parameter.location);
169 156 : let Some(literal) = inputs.value_of(p.analyzed, name) else {
170 24 : out.errors.push((span, format!("input :{name} has no value")));
171 24 : continue;
172 : };
173 132 : match literal.print(dialect) {
174 132 : Ok(text) => {
175 132 : out.values.insert(name.clone(), literal.to_hyper());
176 132 : out.edits.push((span, text));
177 132 : }
178 0 : Err(e) => out.errors.push((span, format!("input :{name}: {e}"))),
179 : }
180 : }
181 2105 : out
182 2105 : }
183 :
|