Line data Source code
1 : //! SQL literals in the Hyper dialect, printed for the dialect of a connection.
2 : //!
3 : //! The values of parameters (`@param :min = 10`, see docs/design/session_inputs.md) are literals written in the Hyper
4 : //! dialect, whatever the connection of a script speaks: a number, a string, `true`, `false` or `null`, or a typed
5 : //! literal, e.g. `date '2024-01-01'`, `timestamptz '2024-01-01 10:00+02'` or `interval '3' day`.
6 : //!
7 : //! A literal is the expression of `select <literal>`, read with the parser of DashQL. Its type comes from the reader of
8 : //! types of the analyzer (`analyzer::sql_types`), the type of `timestamp '...'` is the type `timestamp`. The formatter
9 : //! prints it, in the Hyper dialect as the text of a tag and in the dialect of a connection where the planner binds it.
10 : //! A literal that a dialect can't express is an error, it is never sent as written.
11 : //!
12 : //! An interval is a number and one unit, `interval '3' day`, which Hyper and Trino read alike. Interval text with
13 : //! several units or without one, `interval '1 day 2 hours'` or `interval '3 days'`, is no literal: Trino has no form of
14 : //! it, and DashQL doesn't read the text of intervals.
15 :
16 : use crate::analyzer::sql_types::{generic_kind, interval_keyword, read_sql_type};
17 : use crate::analyzer::clfn_calls::number;
18 : use crate::analyzer::{SqlType, SqlTypeKind};
19 : use crate::formatter::Formatter;
20 : use crate::utils::{quote_string_literal, unquote_string_literal};
21 : use dashql_buffers::formatting::{FormattingConfigT, FormattingDialect, FormattingMode, KeywordCase};
22 : use dashql_buffers::parser::{AttributeKey as K, ExpressionOperator, IntervalType as IntervalTypeEnum, Node, NodeType};
23 : use dashql_parser::attrs;
24 : use dashql_parser::parser::{parse, ParsedScript};
25 : use dashql_parser::scanner::scan;
26 : use serde_json::Value;
27 : use std::fmt;
28 : use std::sync::Arc;
29 :
30 : /// The prefix that makes a literal a statement
31 : const SELECT: &str = "select ";
32 :
33 : /// A literal, the expression of `select <literal>`
34 : #[derive(Clone)]
35 : pub struct SqlLiteral {
36 : /// The parsed `select <literal>`
37 : parsed: Arc<ParsedScript>,
38 : /// The node of the literal
39 : node: u32,
40 : /// The literal in the Hyper dialect, as the formatter prints it
41 : hyper: String,
42 : /// The type, None for null
43 : sql_type: Option<SqlType>,
44 : /// The value as a control shows it
45 : value: String,
46 : }
47 :
48 : impl PartialEq for SqlLiteral {
49 2 : fn eq(&self, other: &Self) -> bool {
50 2 : self.hyper == other.hyper
51 2 : }
52 : }
53 :
54 : impl Eq for SqlLiteral {}
55 :
56 : impl fmt::Debug for SqlLiteral {
57 1 : fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 1 : write!(f, "SqlLiteral({})", self.hyper)
59 1 : }
60 : }
61 :
62 : /// The content of the string of a typed literal, e.g. `2024-01-01` of `date '2024-01-01'`
63 113 : fn string_content(parsed: &ParsedScript, node: Option<&Node>) -> Result<String, String> {
64 113 : let node = node.ok_or("a typed literal needs a string")?;
65 113 : let text = parsed.node_text(node);
66 113 : match node.node_type() {
67 112 : NodeType::LITERAL_STRING => unquote_string_literal(text).ok_or_else(|| format!("{text} is no plain string")),
68 1 : _ => Err(format!("{text} is no string")),
69 : }
70 113 : }
71 :
72 : /// The type and the value of a literal node, an error if the node is no literal
73 246 : fn read_literal(parsed: &ParsedScript, node: &Node) -> Result<(Option<SqlType>, String), String> {
74 246 : let text = parsed.node_text(node);
75 246 : let scalar = |kind| Some(SqlType::scalar(kind));
76 246 : match node.node_type() {
77 76 : NodeType::LITERAL_INTEGER => Ok((scalar(SqlTypeKind::Int8), text.to_string())),
78 2 : NodeType::LITERAL_FLOAT => Ok((scalar(SqlTypeKind::Numeric { precision: None, scale: None }), text.to_string())),
79 2 : NodeType::LITERAL_NULL => Ok((None, String::new())),
80 16 : NodeType::BOOL => Ok((scalar(SqlTypeKind::Bool), (node.children_begin_or_value() != 0).to_string())),
81 47 : NodeType::LITERAL_STRING => Ok((scalar(SqlTypeKind::Text), string_content(parsed, Some(node))?)),
82 : // -2.5
83 : NodeType::OBJECT_SQL_NARY_EXPRESSION => {
84 14 : let [op, args] = attrs!(parsed.children(node), [K::SQL_EXPRESSION_OPERATOR, K::SQL_EXPRESSION_ARGS]);
85 14 : let negate = op.is_some_and(|o| ExpressionOperator(o.children_begin_or_value() as _) == ExpressionOperator::NEGATE);
86 14 : match (negate, args.map(|a| parsed.children(a)).unwrap_or(&[])) {
87 11 : (true, [arg]) if matches!(arg.node_type(), NodeType::LITERAL_INTEGER | NodeType::LITERAL_FLOAT) => {
88 9 : let (sql_type, value) = read_literal(parsed, arg)?;
89 9 : Ok((sql_type, format!("-{value}")))
90 : }
91 5 : _ => Err(format!("{text} is no literal")),
92 : }
93 : }
94 : // timestamp '...', numeric(10,2) '1.5'
95 : NodeType::OBJECT_SQL_CONST_TYPE_CAST => {
96 6 : let [ty, value] = attrs!(parsed.children(node), [K::SQL_CONST_CAST_TYPE, K::SQL_CONST_CAST_VALUE]);
97 6 : let sql_type = ty.and_then(|t| read_sql_type(parsed, t)).ok_or_else(|| format!("{text} has no type"))?;
98 6 : Ok((Some(sql_type), string_content(parsed, value)?))
99 : }
100 : // date '...', timestamptz '...', json '...'
101 : NodeType::OBJECT_SQL_CONST_FUNCTION_CAST => {
102 28 : let [args, name, value] =
103 28 : attrs!(parsed.children(node), [K::SQL_CONST_CAST_FUNC_ARGS_LIST, K::SQL_CONST_CAST_FUNC_NAME, K::SQL_CONST_CAST_VALUE]);
104 28 : if args.is_some() {
105 1 : return Err(format!("{text} is no literal"));
106 27 : }
107 27 : let name = name.map(|n| parsed.node_text(n).to_ascii_lowercase()).unwrap_or_default();
108 27 : let kind = generic_kind(&name).unwrap_or(SqlTypeKind::Generic { name: name.into(), modifiers: Vec::new() });
109 27 : Ok((scalar(kind), string_content(parsed, value)?))
110 : }
111 : // interval '3' day
112 : NodeType::OBJECT_SQL_CONST_INTERVAL_CAST => {
113 42 : let [interval, value] = attrs!(parsed.children(node), [K::SQL_CONST_CAST_INTERVAL, K::SQL_CONST_CAST_VALUE]);
114 42 : let invalid = || format!("{text} is no interval literal, an interval is a number and a unit, e.g. interval '3' day");
115 35 : let [fields, precision] = match interval {
116 35 : Some(i) if i.node_type() == NodeType::OBJECT_SQL_INTERVAL_TYPE => {
117 34 : attrs!(parsed.children(i), [K::SQL_INTERVAL_TYPE, K::SQL_INTERVAL_PRECISION])
118 : }
119 8 : _ => return Err(invalid()),
120 : };
121 34 : let unit = fields.map(|f| IntervalTypeEnum(f.children_begin_or_value() as _));
122 34 : let Some(unit) = unit.filter(|u| precision.is_none() && interval_unit(*u).is_some()) else { return Err(invalid()) };
123 33 : let amount = string_content(parsed, value).map_err(|_| invalid())?;
124 32 : if !is_interval_amount(&amount, unit) {
125 4 : return Err(invalid());
126 28 : }
127 28 : let sql_type = SqlType::scalar(SqlTypeKind::Interval { fields: Some(unit), precision: None });
128 : // The control shows the amount with the unit, `3 day`
129 28 : Ok((Some(sql_type), format!("{amount} {}", interval_unit(unit).unwrap_or_default())))
130 : }
131 13 : _ => Err(format!("{text} is no literal")),
132 : }
133 246 : }
134 :
135 : /// The units of an interval literal
136 : const INTERVAL_UNITS: [IntervalTypeEnum; 6] =
137 : [IntervalTypeEnum::YEAR, IntervalTypeEnum::MONTH, IntervalTypeEnum::DAY, IntervalTypeEnum::HOUR, IntervalTypeEnum::MINUTE, IntervalTypeEnum::SECOND];
138 :
139 : /// The keyword of the unit of an interval literal, None for ranges like `day to second`
140 62 : fn interval_unit(unit: IntervalTypeEnum) -> Option<&'static str> {
141 62 : INTERVAL_UNITS.contains(&unit).then(|| interval_keyword(unit)).flatten()
142 62 : }
143 :
144 : /// Is the text the amount of an interval literal of a unit? An integer, seconds may have a fraction, `1.5`.
145 32 : fn is_interval_amount(amount: &str, unit: IntervalTypeEnum) -> bool {
146 37 : let digits = |s: &str| !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit());
147 32 : let unsigned = amount.strip_prefix('-').unwrap_or(amount);
148 32 : match unsigned.split_once('.') {
149 28 : None => digits(unsigned),
150 4 : Some((whole, fraction)) => unit == IntervalTypeEnum::SECOND && digits(whole) && digits(fraction),
151 : }
152 32 : }
153 :
154 : /// The config of the formatter of a literal, one line in lower case
155 367 : fn formatting_config(dialect: FormattingDialect) -> FormattingConfigT {
156 367 : let mut config = FormattingConfigT::default();
157 367 : config.dialect = dialect;
158 367 : config.mode = FormattingMode::INLINE;
159 367 : config.max_width = u32::MAX;
160 367 : config.keyword_case = KeywordCase::LOWER;
161 367 : config
162 367 : }
163 :
164 : impl SqlLiteral {
165 : /// Read a literal in the Hyper dialect
166 244 : pub fn parse(text: &str) -> Result<SqlLiteral, String> {
167 244 : let text = text.trim();
168 244 : let hint = "a literal is a number, a string, true, false, null, or a typed literal like date '2024-01-01' or interval '3' day";
169 244 : let invalid = || format!("{text} is no literal, {hint}");
170 244 : if text.is_empty() {
171 1 : return Err(format!("the literal is empty, {hint}"));
172 243 : }
173 243 : let parsed = Arc::new(parse(Arc::new(scan(&format!("{SELECT}{text}"), 0, 0))));
174 243 : if !parsed.errors.is_empty() || parsed.statements.len() != 1 {
175 4 : return Err(invalid());
176 239 : }
177 : // The value of the one result target of the select, which is the whole text, `1 from t` is no literal
178 1255 : let mut values = parsed.nodes.iter().enumerate().filter(|(_, n)| n.attribute_key() == K::SQL_RESULT_TARGET_VALUE);
179 239 : let (Some((node, value)), None) = (values.next(), values.next()) else { return Err(invalid()) };
180 239 : if parsed.node_text(value) != text {
181 2 : return Err(invalid());
182 237 : }
183 237 : let (sql_type, display) = read_literal(&parsed, value).map_err(|e| format!("{e}, {hint}"))?;
184 204 : let mut formatter = Formatter::new(&parsed);
185 204 : let hyper = formatter.format_node_at(node, &formatting_config(FormattingDialect::HYPER));
186 204 : if !formatter.is_fully_formatted() {
187 0 : return Err(invalid());
188 204 : }
189 204 : Ok(SqlLiteral { parsed, node: node as u32, hyper, sql_type, value: display })
190 244 : }
191 :
192 : /// The literal of the value of a control with a type. A number type reads a number, a boolean `true` or `false`,
193 : /// a date, a time and a timestamp read the text of their literal, an interval reads a number and a unit, `3 day`,
194 : /// and any other type a string.
195 : /// Without a type the value is a literal if it is one, e.g. `10`, `true` or `date '2024-01-01'`, and a string
196 : /// otherwise.
197 30 : pub fn from_value(value: &str, ty: Option<&SqlType>) -> Result<SqlLiteral, String> {
198 30 : let value = value.trim();
199 30 : let string = || SqlLiteral::parse("e_string_literal(value));
200 30 : let Some(ty) = ty.filter(|t| t.array_dimensions == 0) else {
201 7 : return SqlLiteral::parse(value).or_else(|_| string());
202 : };
203 23 : let typed = |keyword: &str| SqlLiteral::parse(&format!("{keyword} {}", quote_string_literal(value)));
204 23 : let is = |literal: &SqlLiteral, check: fn(&SqlType) -> bool| literal.sql_type.as_ref().is_some_and(check);
205 20 : match &ty.kind {
206 23 : _ if ty.is_numeric() => {
207 3 : let literal = SqlLiteral::parse(value).ok().filter(|l| is(l, SqlType::is_numeric));
208 3 : let literal = literal.ok_or_else(|| format!("{ty} needs a number, not \"{value}\""))?;
209 2 : if ty.is_integer() && !is(&literal, SqlType::is_integer) {
210 1 : return Err(format!("{ty} needs an integer, not \"{value}\""));
211 1 : }
212 1 : Ok(literal)
213 : }
214 3 : SqlTypeKind::Bool => SqlLiteral::parse(value)
215 3 : .ok()
216 3 : .filter(|l| is(l, SqlType::is_bool))
217 3 : .ok_or_else(|| format!("{ty} needs true or false, not \"{value}\"")),
218 1 : SqlTypeKind::Date => typed("date"),
219 2 : SqlTypeKind::Time { with_time_zone, .. } => typed(if *with_time_zone { "timetz" } else { "time" }),
220 2 : SqlTypeKind::Timestamp { with_time_zone, .. } => typed(if *with_time_zone { "timestamptz" } else { "timestamp" }),
221 : SqlTypeKind::Interval { .. } => {
222 9 : let invalid = || format!("{ty} needs a number and a unit, e.g. 3 day, not \"{value}\"");
223 9 : let mut words = value.split_whitespace();
224 9 : let (Some(amount), Some(unit), None) = (words.next(), words.next(), words.next()) else { return Err(invalid()) };
225 5 : let unit = unit.to_ascii_lowercase();
226 22 : let Some(keyword) = INTERVAL_UNITS.into_iter().filter_map(interval_keyword).find(|keyword| *keyword == unit) else { return Err(invalid()) };
227 4 : SqlLiteral::parse(&format!("interval {} {keyword}", quote_string_literal(amount))).map_err(|_| invalid())
228 : }
229 3 : _ => string(),
230 : }
231 30 : }
232 :
233 : /// The type of the literal, None for null
234 63 : pub fn sql_type(&self) -> Option<SqlType> {
235 63 : self.sql_type.clone()
236 63 : }
237 :
238 : /// The value as a control shows it: the number, the content of a string, the text of a typed literal
239 5 : pub fn display_value(&self) -> String {
240 5 : self.value.clone()
241 5 : }
242 :
243 : /// The value as JSON, for a request: a number, a boolean, a null, or the text that a control shows
244 9 : pub fn to_json(&self) -> Value {
245 9 : match self.sql_type.as_ref().map(|t| &t.kind) {
246 0 : None => Value::Null,
247 3 : Some(SqlTypeKind::Bool) => Value::Bool(self.value == "true"),
248 3 : Some(SqlTypeKind::Int8 | SqlTypeKind::Numeric { .. }) => number(&self.value),
249 3 : Some(_) => Value::String(self.value.clone()),
250 : }
251 9 : }
252 :
253 : /// The literal in the Hyper dialect, the text of an `@param` tag
254 191 : pub fn to_hyper(&self) -> String {
255 191 : self.hyper.clone()
256 191 : }
257 :
258 : /// The literal in a dialect, as an expression that stands in the place of a parameter: a negative number is in
259 : /// parentheses, `x-:n` would become a comment otherwise
260 163 : pub fn print(&self, dialect: FormattingDialect) -> Result<String, String> {
261 163 : let mut formatter = Formatter::new(&self.parsed);
262 163 : let text = formatter.format_node_at(self.node as usize, &formatting_config(dialect));
263 163 : if !formatter.is_fully_formatted() {
264 0 : let name = crate::formatter::program::formatting_dialect_to_string(dialect);
265 0 : return Err(format!("{} can't be written in the {name} dialect", self.hyper));
266 163 : }
267 163 : Ok(if text.starts_with('-') { format!("({text})") } else { text })
268 163 : }
269 : }
270 :
271 : #[cfg(test)]
272 : mod tests {
273 : use super::*;
274 :
275 37 : fn read(text: &str) -> SqlLiteral {
276 37 : SqlLiteral::parse(text).unwrap_or_else(|e| panic!("{text}: {e}"))
277 37 : }
278 :
279 7 : fn trino(text: &str) -> String {
280 7 : read(text).print(FormattingDialect::TRINO).unwrap_or_else(|e| panic!("{text}: {e}"))
281 7 : }
282 :
283 6 : fn hyper(text: &str) -> String {
284 6 : read(text).print(FormattingDialect::HYPER).unwrap()
285 6 : }
286 :
287 13 : fn kind(text: &str) -> Option<SqlTypeKind> {
288 13 : read(text).sql_type().map(|t| t.kind)
289 13 : }
290 :
291 : #[test]
292 1 : fn reads_literals_and_their_types() {
293 1 : assert_eq!(kind("10"), Some(SqlTypeKind::Int8));
294 1 : assert_eq!(kind("-2.5"), Some(SqlTypeKind::Numeric { precision: None, scale: None }));
295 1 : assert_eq!(kind("'it''s'"), Some(SqlTypeKind::Text));
296 1 : assert_eq!(kind("TRUE"), Some(SqlTypeKind::Bool));
297 1 : assert_eq!(kind("null"), None);
298 1 : assert_eq!(kind("date '2024-01-01'"), Some(SqlTypeKind::Date));
299 1 : assert_eq!(kind("timestamp '2024-01-01 10:00'"), Some(SqlTypeKind::Timestamp { precision: None, with_time_zone: false }));
300 1 : assert_eq!(kind("timestamp with time zone '2024-01-01'"), Some(SqlTypeKind::Timestamp { precision: None, with_time_zone: true }));
301 1 : assert_eq!(kind("timestamptz '2024-01-01'"), Some(SqlTypeKind::Timestamp { precision: None, with_time_zone: true }));
302 1 : assert_eq!(kind("interval '3' day"), Some(SqlTypeKind::Interval { fields: Some(IntervalTypeEnum::DAY), precision: None }));
303 1 : assert_eq!(kind("interval '-2' MONTH"), Some(SqlTypeKind::Interval { fields: Some(IntervalTypeEnum::MONTH), precision: None }));
304 1 : assert_eq!(read("'it''s'").display_value(), "it's");
305 1 : assert_eq!(read("interval '3' day").display_value(), "3 day");
306 1 : assert_eq!(read("interval '1.5' second").display_value(), "1.5 second");
307 1 : assert_eq!(read("-2").display_value(), "-2");
308 1 : }
309 :
310 : #[test]
311 1 : fn rejects_expressions() {
312 10 : for text in ["", "1 + 2", "now()", "x", "'a' || 'b'", "interval(3) '1 s'", "select 1", "1; select 2", "1 from t", "-'a'"] {
313 10 : assert!(SqlLiteral::parse(text).is_err(), "{text}");
314 : }
315 1 : }
316 :
317 : #[test]
318 1 : fn reads_intervals_of_a_number_and_a_unit() {
319 : // Interval text is no literal, Trino has no form of it and DashQL doesn't read it
320 7 : for text in [
321 1 : "interval '3 days'",
322 1 : "interval '1 day 2 hours'",
323 1 : "interval '01:30'",
324 1 : "interval 'soon' day",
325 1 : "interval '1.5' hour",
326 1 : "interval '1 02' day to hour",
327 1 : "interval 3 day",
328 1 : ] {
329 7 : let error = SqlLiteral::parse(text).err().unwrap_or_else(|| panic!("{text} is a literal"));
330 7 : assert!(error.contains("an interval is a number and a unit"), "{text}: {error}");
331 : }
332 1 : }
333 :
334 : #[test]
335 1 : fn prints_hyper() {
336 1 : assert_eq!(hyper("10"), "10");
337 1 : assert_eq!(hyper("-2"), "(-2)");
338 1 : assert_eq!(hyper("'EU'"), "'EU'");
339 1 : assert_eq!(hyper("TRUE"), "TRUE");
340 1 : assert_eq!(hyper("date '2024-01-01'"), "date '2024-01-01'");
341 1 : assert_eq!(hyper("interval '3' day"), "interval '3' day");
342 1 : assert_eq!(read("'it''s'").to_hyper(), "'it''s'");
343 1 : }
344 :
345 : #[test]
346 1 : fn prints_trino() {
347 1 : assert_eq!(trino("'it''s'"), "'it''s'");
348 1 : assert_eq!(trino("null"), "null");
349 1 : assert_eq!(trino("date '2024-01-01'"), "date '2024-01-01'");
350 1 : assert_eq!(trino("timestamptz '2024-01-01 10:00 UTC'"), "timestamp '2024-01-01 10:00 UTC'");
351 1 : assert_eq!(trino("timestamp with time zone '2024-01-01 10:00 UTC'"), "timestamp '2024-01-01 10:00 UTC'");
352 1 : assert_eq!(trino("interval '3' day"), "interval '3' day");
353 1 : assert_eq!(trino("interval '-14' month"), "interval '-14' month");
354 1 : }
355 :
356 : #[test]
357 1 : fn makes_literals_of_values() {
358 1 : let int = SqlType::scalar(SqlTypeKind::Int4);
359 1 : let interval = SqlType::scalar(SqlTypeKind::Interval { fields: None, precision: None });
360 1 : let date = SqlType::scalar(SqlTypeKind::Date);
361 1 : let text = SqlType::scalar(SqlTypeKind::Text);
362 20 : let value = |v: &str, t: Option<&SqlType>| SqlLiteral::from_value(v, t).map(|l| l.to_hyper());
363 1 : assert_eq!(value("10", Some(&int)), Ok("10".into()));
364 1 : assert!(value("1.5", Some(&int)).is_err());
365 1 : assert!(value("ten", Some(&int)).is_err());
366 1 : assert_eq!(value("3 day", Some(&interval)), Ok("interval '3' day".into()));
367 1 : assert_eq!(value(" 1.5 SECOND ", Some(&interval)), Ok("interval '1.5' second".into()));
368 7 : for text in ["3 days", "1 day 2 hour", "3", "day", "soon day", "1.5 hour", "3'; drop table t; -- day"] {
369 7 : assert!(value(text, Some(&interval)).is_err(), "{text}");
370 : }
371 1 : assert_eq!(value("2024-01-01", Some(&date)), Ok("date '2024-01-01'".into()));
372 1 : assert_eq!(value("it's", Some(&text)), Ok("'it''s'".into()));
373 1 : assert_eq!(value("10", Some(&text)), Ok("'10'".into()));
374 : // Without a type, a literal stays one and anything else is a string
375 1 : assert_eq!(value("10", None), Ok("10".into()));
376 1 : assert_eq!(value("true", None), Ok("true".into()));
377 1 : assert_eq!(value("interval '2' day", None), Ok("interval '2' day".into()));
378 1 : assert_eq!(value("EU", None), Ok("'EU'".into()));
379 1 : assert_eq!(value("it's", None), Ok("'it''s'".into()));
380 1 : }
381 :
382 : #[test]
383 1 : fn makes_literals_of_booleans_times_and_arrays() {
384 6 : let scalar = |kind| SqlType::scalar(kind);
385 10 : let value = |v: &str, t: &SqlType| SqlLiteral::from_value(v, Some(t)).map(|l| l.to_hyper());
386 1 : let boolean = scalar(SqlTypeKind::Bool);
387 1 : assert_eq!(value("TRUE", &boolean), Ok("TRUE".into()));
388 1 : let error = value("yes", &boolean).unwrap_err();
389 1 : assert!(error.contains("needs true or false"), "{error}");
390 1 : assert!(value("1", &boolean).is_err());
391 :
392 2 : let time = |with_time_zone| scalar(SqlTypeKind::Time { precision: None, with_time_zone });
393 2 : let timestamp = |with_time_zone| scalar(SqlTypeKind::Timestamp { precision: None, with_time_zone });
394 1 : assert_eq!(value("10:00", &time(false)), Ok("time '10:00'".into()));
395 1 : assert_eq!(value("10:00+01", &time(true)), Ok("timetz '10:00+01'".into()));
396 1 : assert_eq!(value("2024-01-01 10:00", ×tamp(false)), Ok("timestamp '2024-01-01 10:00'".into()));
397 1 : assert_eq!(value("2024-01-01 10:00 UTC", ×tamp(true)), Ok("timestamptz '2024-01-01 10:00 UTC'".into()));
398 :
399 : // Arrays have no controls, their values are read like values without a type
400 1 : let ints = SqlType { kind: SqlTypeKind::Int4, array_dimensions: 1 };
401 1 : assert_eq!(value("10", &ints), Ok("10".into()));
402 1 : assert_eq!(value("EU", &ints), Ok("'EU'".into()));
403 : // A generic type reads a string
404 1 : let json = scalar(SqlTypeKind::Generic { name: "json".into(), modifiers: vec![] });
405 1 : assert_eq!(value("{}", &json), Ok("'{}'".into()));
406 1 : }
407 :
408 : #[test]
409 1 : fn reads_typed_literals_of_any_type() {
410 1 : assert_eq!(kind("numeric(10,2) '1.5'"), Some(SqlTypeKind::Numeric { precision: Some(10), scale: Some(2) }));
411 1 : assert_eq!(kind("json '{}'"), Some(SqlTypeKind::Generic { name: "json".into(), modifiers: vec![] }));
412 1 : assert_eq!(read("json '{}'").display_value(), "{}");
413 3 : for text in ["date 1", "-true", "geography(4326) 'x'"] {
414 3 : assert!(SqlLiteral::parse(text).is_err(), "{text}");
415 : }
416 1 : }
417 :
418 : #[test]
419 1 : fn literals_compare_by_their_hyper_text() {
420 1 : assert_eq!(read("10"), read(" 10 "));
421 1 : assert_ne!(read("10"), read("'10'"));
422 1 : assert_eq!(format!("{:?}", read("'EU'")), "SqlLiteral('EU')");
423 1 : }
424 : }
|