common_sql/
default_constraint.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use common_time::timezone::Timezone;
16use datatypes::prelude::ConcreteDataType;
17use datatypes::schema::constraint::{CURRENT_TIMESTAMP, CURRENT_TIMESTAMP_FN};
18use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
19use snafu::ensure;
20use sqlparser::ast::ValueWithSpan;
21pub use sqlparser::ast::{
22    BinaryOperator, ColumnDef, ColumnOption, ColumnOptionDef, DataType, Expr, Function,
23    FunctionArg, FunctionArgExpr, FunctionArguments, Ident, ObjectName, SqlOption, TableConstraint,
24    TimezoneInfo, UnaryOperator, Value as SqlValue, Visit, VisitMut, Visitor, VisitorMut,
25    visit_expressions_mut, visit_statements_mut,
26};
27
28use crate::convert::{sql_number_to_value, sql_value_to_value};
29use crate::error::{Result, UnsupportedDefaultValueSnafu};
30
31pub fn parse_column_default_constraint(
32    column_name: &str,
33    data_type: &ConcreteDataType,
34    opts: &[ColumnOptionDef],
35    timezone: Option<&Timezone>,
36) -> Result<Option<ColumnDefaultConstraint>> {
37    if let Some(opt) = opts
38        .iter()
39        .find(|o| matches!(o.option, ColumnOption::Default(_)))
40    {
41        ensure!(
42            !data_type.is_json(),
43            UnsupportedDefaultValueSnafu {
44                column_name,
45                reason: "json column cannot have a default value",
46            }
47        );
48
49        let default_constraint = match &opt.option {
50            ColumnOption::Default(Expr::Value(v)) => {
51                let schema = ColumnSchema::new(column_name, data_type.clone(), true);
52                ColumnDefaultConstraint::Value(sql_value_to_value(
53                    &schema, &v.value, timezone, None, false,
54                )?)
55            }
56            ColumnOption::Default(Expr::Function(func)) => {
57                let mut func = format!("{func}").to_lowercase();
58                // normalize CURRENT_TIMESTAMP to CURRENT_TIMESTAMP()
59                if func == CURRENT_TIMESTAMP {
60                    func = CURRENT_TIMESTAMP_FN.to_string();
61                }
62                // Always use lowercase for function expression
63                ColumnDefaultConstraint::Function(func.to_lowercase())
64            }
65
66            ColumnOption::Default(Expr::UnaryOp { op, expr }) => {
67                // Specialized process for handling numerical inputs to prevent
68                // overflow errors during the parsing of negative numbers,
69                // See https://github.com/GreptimeTeam/greptimedb/issues/4351
70                if let (
71                    UnaryOperator::Minus,
72                    Expr::Value(ValueWithSpan {
73                        value: SqlValue::Number(n, _),
74                        span: _,
75                    }),
76                ) = (op, expr.as_ref())
77                {
78                    return Ok(Some(ColumnDefaultConstraint::Value(sql_number_to_value(
79                        data_type,
80                        &format!("-{n}"),
81                    )?)));
82                }
83
84                if let Expr::Value(v) = &**expr {
85                    let value = sql_value_to_value(
86                        &ColumnSchema::new(column_name, data_type.clone(), true),
87                        &v.value,
88                        timezone,
89                        Some(*op),
90                        false,
91                    )?;
92                    ColumnDefaultConstraint::Value(value)
93                } else {
94                    return UnsupportedDefaultValueSnafu {
95                        column_name,
96                        reason: format!("expr '{expr}' not supported"),
97                    }
98                    .fail();
99                }
100            }
101            ColumnOption::Default(others) => {
102                return UnsupportedDefaultValueSnafu {
103                    column_name,
104                    reason: format!("expr '{others}' not supported"),
105                }
106                .fail();
107            }
108            _ => {
109                return UnsupportedDefaultValueSnafu {
110                    column_name,
111                    reason: format!("option '{}' not supported", opt.option),
112                }
113                .fail();
114            }
115        };
116
117        Ok(Some(default_constraint))
118    } else {
119        Ok(None)
120    }
121}
122
123#[cfg(test)]
124mod test {
125    use std::assert_matches::assert_matches;
126
127    use datatypes::prelude::{ConcreteDataType, Value};
128    use datatypes::types::BooleanType;
129
130    use super::*;
131
132    #[test]
133    pub fn test_parse_column_default_constraint() {
134        let bool_value = sqlparser::ast::Value::Boolean(true);
135
136        let opts = vec![
137            ColumnOptionDef {
138                name: None,
139                option: ColumnOption::Default(Expr::Value(bool_value.into())),
140            },
141            ColumnOptionDef {
142                name: None,
143                option: ColumnOption::NotNull,
144            },
145        ];
146
147        let constraint = parse_column_default_constraint(
148            "coll",
149            &ConcreteDataType::Boolean(BooleanType),
150            &opts,
151            None,
152        )
153        .unwrap();
154
155        assert_matches!(
156            constraint,
157            Some(ColumnDefaultConstraint::Value(Value::Boolean(true)))
158        );
159
160        // Test negative number
161        let opts = vec![ColumnOptionDef {
162            name: None,
163            option: ColumnOption::Default(Expr::UnaryOp {
164                op: UnaryOperator::Minus,
165                expr: Box::new(Expr::Value(
166                    SqlValue::Number("32768".to_string(), false).into(),
167                )),
168            }),
169        }];
170
171        let constraint = parse_column_default_constraint(
172            "coll",
173            &ConcreteDataType::int16_datatype(),
174            &opts,
175            None,
176        )
177        .unwrap();
178
179        assert_matches!(
180            constraint,
181            Some(ColumnDefaultConstraint::Value(Value::Int16(-32768)))
182        );
183    }
184
185    #[test]
186    fn test_incorrect_default_value_issue_3479() {
187        let opts = vec![ColumnOptionDef {
188            name: None,
189            option: ColumnOption::Default(Expr::Value(
190                SqlValue::Number("0.047318541668048164".into(), false).into(),
191            )),
192        }];
193        let constraint = parse_column_default_constraint(
194            "coll",
195            &ConcreteDataType::float64_datatype(),
196            &opts,
197            None,
198        )
199        .unwrap()
200        .unwrap();
201        assert_eq!("0.047318541668048164", constraint.to_string());
202        let encoded: Vec<u8> = constraint.clone().try_into().unwrap();
203        let decoded = ColumnDefaultConstraint::try_from(encoded.as_ref()).unwrap();
204        assert_eq!(decoded, constraint);
205    }
206}