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