LCOV - code coverage report
Current view: top level - dashql-core/src - sql_literal.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 98.9 % 264 261
Test Date: 2026-11-06 17:46:50

            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(&quote_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", &timestamp(false)), Ok("timestamp '2024-01-01 10:00'".into()));
     385            1 :         assert_eq!(value("2024-01-01 10:00 UTC", &timestamp(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              : }
        

Generated by: LCOV version 2.0-1