uqa_sql/semantics/partition/
datum_text.rs1use crate::ast::{ColumnType, Expr, PartitionRangeDatum};
10use crate::expr::EngineHook;
11use crate::result::format_postgres_text;
12use crate::SQLError;
13use uqa_core::Value;
14
15pub fn partition_datum_text(
17 value: &Value,
18 ty: &ColumnType,
19 engine: Option<&dyn EngineHook>,
20) -> Result<String, SQLError> {
21 if matches!(value, Value::Null) {
22 return Ok("NULL".into());
23 }
24 let text = format_postgres_text(value, ty, engine)?;
25 Ok(match ty {
26 ColumnType::Integer if !text.starts_with('-') => text,
27 ColumnType::Numeric { .. }
28 if text.starts_with(|character: char| character.is_ascii_digit())
29 && text.contains(['e', 'E', '.']) =>
30 {
31 text
32 }
33 ColumnType::Boolean => if text == "t" { "true" } else { "false" }.into(),
34 _ => quote_literal(&text),
35 })
36}
37
38pub fn range_bound_text(
40 datums: &[PartitionRangeDatum],
41 types: &[ColumnType],
42 engine: Option<&dyn EngineHook>,
43) -> Result<String, SQLError> {
44 let mut rendered = Vec::with_capacity(datums.len());
45 for (position, datum) in datums.iter().enumerate() {
46 rendered.push(match datum {
47 PartitionRangeDatum::MinValue => "MINVALUE".into(),
48 PartitionRangeDatum::MaxValue => "MAXVALUE".into(),
49 PartitionRangeDatum::Value(expression) => {
50 let ty = types.get(position).ok_or_else(|| {
51 SQLError::Internal("partition range bound is wider than its key".into())
52 })?;
53 partition_datum_text(stored_datum(expression)?, ty, engine)?
54 }
55 });
56 }
57 Ok(format!("({})", rendered.join(", ")))
58}
59
60pub fn stored_datum(expression: &Expr) -> Result<&Value, SQLError> {
62 match expression {
63 Expr::Literal(value) | Expr::TypedLiteral { value, .. } => Ok(value),
64 other => Err(SQLError::Internal(format!(
65 "partition bound datum was not evaluated to a constant: {other:?}"
66 ))),
67 }
68}
69
70fn quote_literal(text: &str) -> String {
72 let mut quoted = String::with_capacity(text.len() + 2);
73 quoted.push('\'');
74 for character in text.chars() {
75 if character == '\'' {
76 quoted.push('\'');
77 }
78 quoted.push(character);
79 }
80 quoted.push('\'');
81 quoted
82}
83
84#[cfg(test)]
85mod tests {
86 use super::partition_datum_text;
87 use crate::ast::ColumnType;
88 use uqa_core::{DecimalValue, Value};
89
90 fn text(value: Value, ty: &ColumnType) -> String {
91 partition_datum_text(&value, ty, None).unwrap()
92 }
93
94 #[test]
95 fn constants_follow_get_const_expr_spelling() {
96 assert_eq!(text(Value::Null, &ColumnType::Integer), "NULL");
97 assert_eq!(text(Value::Int(2), &ColumnType::Integer), "2");
98 assert_eq!(text(Value::Int(-1), &ColumnType::Integer), "'-1'");
99 assert_eq!(text(Value::Int(2), &ColumnType::SmallInteger), "'2'");
100 assert_eq!(text(Value::Int(2), &ColumnType::BigInteger), "'2'");
101 let numeric = ColumnType::Numeric {
102 precision: None,
103 scale: None,
104 };
105 let decimal = |text: &str| Value::Decimal(DecimalValue::parse(text).unwrap());
106 assert_eq!(text(decimal("1.50"), &numeric), "1.50");
107 assert_eq!(text(decimal("2"), &numeric), "'2'");
108 assert_eq!(text(decimal("-3.5"), &numeric), "'-3.5'");
109 assert_eq!(text(Value::Bool(true), &ColumnType::Boolean), "true");
110 assert_eq!(
111 text(Value::Str("it's".into()), &ColumnType::Text),
112 "'it''s'"
113 );
114 assert_eq!(text(Value::Str("x\\y".into()), &ColumnType::Text), "'x\\y'");
115 }
116}