mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-27 14:16:18 +00:00
feat: impl default constraint for column (#273)
* feat: impl default value for column in schema * test: add test for column's default value * refactor: rename ColumnDefaultValue to ColumnDefaultConstraint * fix: timestamp column may be a constant vector * fix: test_shutdown_pg_server * fix: typo Co-authored-by: LFC <bayinamine@gmail.com> * fix: typo Co-authored-by: LFC <bayinamine@gmail.com> * fix: typo Co-authored-by: LFC <bayinamine@gmail.com> * chore: use table_info directly Co-authored-by: LFC <bayinamine@gmail.com> * refactor: by CR comments Co-authored-by: LFC <bayinamine@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
common-error = { path = "../common/error" }
|
||||
common-time = { path = "../common/time" }
|
||||
datatypes = { path = "../datatypes" }
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
sqlparser = "0.15.0"
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
pub use sqlparser::ast::{
|
||||
ColumnDef, ColumnOption, ColumnOptionDef, DataType, Expr, Ident, ObjectName, SqlOption,
|
||||
TableConstraint, Value,
|
||||
ColumnDef, ColumnOption, ColumnOptionDef, DataType, Expr, Function, FunctionArg,
|
||||
FunctionArgExpr, Ident, ObjectName, SqlOption, TableConstraint, Value,
|
||||
};
|
||||
|
||||
+33
-1
@@ -1,9 +1,12 @@
|
||||
use std::any::Any;
|
||||
|
||||
use common_error::prelude::*;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use sqlparser::parser::ParserError;
|
||||
use sqlparser::tokenizer::TokenizerError;
|
||||
|
||||
use crate::ast::Expr;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// SQL parser errors.
|
||||
@@ -29,6 +32,17 @@ pub enum Error {
|
||||
source: ParserError,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Unsupported expr in default constraint: {} for column: {}",
|
||||
expr,
|
||||
column_name
|
||||
))]
|
||||
UnsupportedDefaultValue {
|
||||
column_name: String,
|
||||
expr: Expr,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
// Syntax error from sql parser.
|
||||
#[snafu(display("Syntax error, sql: {}, source: {}", sql, source))]
|
||||
Syntax { sql: String, source: ParserError },
|
||||
@@ -50,6 +64,21 @@ pub enum Error {
|
||||
t: crate::ast::DataType,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to parse value: {}, {}", msg, backtrace))]
|
||||
ParseSqlValue { msg: String, backtrace: Backtrace },
|
||||
|
||||
#[snafu(display(
|
||||
"Column {} expect type: {:?}, actual: {:?}",
|
||||
column_name,
|
||||
expect,
|
||||
actual
|
||||
))]
|
||||
ColumnTypeMismatch {
|
||||
column_name: String,
|
||||
expect: ConcreteDataType,
|
||||
actual: ConcreteDataType,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
@@ -57,13 +86,16 @@ impl ErrorExt for Error {
|
||||
use Error::*;
|
||||
|
||||
match self {
|
||||
Unsupported { .. } => StatusCode::Unsupported,
|
||||
UnsupportedDefaultValue { .. } | Unsupported { .. } => StatusCode::Unsupported,
|
||||
Unexpected { .. }
|
||||
| Syntax { .. }
|
||||
| InvalidTimeIndex { .. }
|
||||
| Tokenizer { .. }
|
||||
| InvalidSql { .. }
|
||||
| ParseSqlValue { .. }
|
||||
| SqlTypeNotSupported { .. } => StatusCode::InvalidSyntax,
|
||||
|
||||
ColumnTypeMismatch { .. } => StatusCode::InvalidArguments,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+267
-8
@@ -6,15 +6,24 @@ pub mod show_database;
|
||||
pub mod show_kind;
|
||||
pub mod statement;
|
||||
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::ColumnSchema;
|
||||
use datatypes::types::DateTimeType;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::ast::{ColumnDef, ColumnOption, DataType as SqlDataType, ObjectName};
|
||||
use crate::error::{self, Result};
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
|
||||
use datatypes::types::DateTimeType;
|
||||
use datatypes::value::Value;
|
||||
use snafu::ensure;
|
||||
|
||||
use crate::ast::{
|
||||
ColumnDef, ColumnOption, ColumnOptionDef, DataType as SqlDataType, Expr, ObjectName,
|
||||
Value as SqlValue,
|
||||
};
|
||||
use crate::error::{
|
||||
self, ColumnTypeMismatchSnafu, ParseSqlValueSnafu, Result, UnsupportedDefaultValueSnafu,
|
||||
};
|
||||
|
||||
/// Converts maybe fully-qualified table name (`<catalog>.<schema>.<table>` or `<table>` when
|
||||
/// catalog and schema are default) to tuple.
|
||||
/// catalog and schema are default) to tuple.
|
||||
pub fn table_idents_to_full_name(
|
||||
obj_name: &ObjectName,
|
||||
) -> Result<(Option<String>, Option<String>, String)> {
|
||||
@@ -35,15 +44,175 @@ pub fn table_idents_to_full_name(
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string_to_value(
|
||||
column_name: &str,
|
||||
s: String,
|
||||
data_type: &ConcreteDataType,
|
||||
) -> Result<Value> {
|
||||
ensure!(
|
||||
data_type.is_string(),
|
||||
ColumnTypeMismatchSnafu {
|
||||
column_name,
|
||||
expect: data_type.clone(),
|
||||
actual: ConcreteDataType::string_datatype(),
|
||||
}
|
||||
);
|
||||
|
||||
match data_type {
|
||||
ConcreteDataType::String(_) => Ok(Value::String(s.into())),
|
||||
ConcreteDataType::Date(_) => {
|
||||
if let Ok(date) = common_time::date::Date::from_str(&s) {
|
||||
Ok(Value::Date(date))
|
||||
} else {
|
||||
ParseSqlValueSnafu {
|
||||
msg: format!("Failed to parse {} to Date value", s),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
ConcreteDataType::DateTime(_) => {
|
||||
if let Ok(datetime) = common_time::datetime::DateTime::from_str(&s) {
|
||||
Ok(Value::DateTime(datetime))
|
||||
} else {
|
||||
ParseSqlValueSnafu {
|
||||
msg: format!("Failed to parse {} to DateTime value", s),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! parse_number_to_value {
|
||||
($data_type: expr, $n: ident, $(($Type: ident, $PrimitiveType: ident)), +) => {
|
||||
match $data_type {
|
||||
$(
|
||||
ConcreteDataType::$Type(_) => {
|
||||
let n = parse_sql_number::<$PrimitiveType>($n)?;
|
||||
Ok(Value::from(n))
|
||||
},
|
||||
)+
|
||||
_ => ParseSqlValueSnafu {
|
||||
msg: format!("Fail to parse number {}, invalid column type: {:?}",
|
||||
$n, $data_type
|
||||
)}.fail(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a sql value into datatype's value
|
||||
pub fn sql_number_to_value(data_type: &ConcreteDataType, n: &str) -> Result<Value> {
|
||||
parse_number_to_value!(
|
||||
data_type,
|
||||
n,
|
||||
(UInt8, u8),
|
||||
(UInt16, u16),
|
||||
(UInt32, u32),
|
||||
(UInt64, u64),
|
||||
(Int8, i8),
|
||||
(Int16, i16),
|
||||
(Int32, i32),
|
||||
(Int64, i64),
|
||||
(Float64, f64),
|
||||
(Float32, f32),
|
||||
(Timestamp, i64)
|
||||
)
|
||||
// TODO(hl): also Date/DateTime
|
||||
}
|
||||
|
||||
fn parse_sql_number<R: FromStr + std::fmt::Debug>(n: &str) -> Result<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
match n.parse::<R>() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) => ParseSqlValueSnafu {
|
||||
msg: format!("Fail to parse number {}, {:?}", n, e),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sql_value_to_value(
|
||||
column_name: &str,
|
||||
data_type: &ConcreteDataType,
|
||||
sql_val: &SqlValue,
|
||||
) -> Result<Value> {
|
||||
Ok(match sql_val {
|
||||
SqlValue::Number(n, _) => sql_number_to_value(data_type, n)?,
|
||||
SqlValue::Null => Value::Null,
|
||||
SqlValue::Boolean(b) => {
|
||||
ensure!(
|
||||
data_type.is_boolean(),
|
||||
ColumnTypeMismatchSnafu {
|
||||
column_name,
|
||||
expect: data_type.clone(),
|
||||
actual: ConcreteDataType::boolean_datatype(),
|
||||
}
|
||||
);
|
||||
|
||||
(*b).into()
|
||||
}
|
||||
SqlValue::DoubleQuotedString(s) | SqlValue::SingleQuotedString(s) => {
|
||||
parse_string_to_value(column_name, s.to_owned(), data_type)?
|
||||
}
|
||||
_ => todo!("Other sql value"),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_column_default_constraint(
|
||||
column_name: &str,
|
||||
data_type: &ConcreteDataType,
|
||||
opts: &[ColumnOptionDef],
|
||||
) -> Result<Option<ColumnDefaultConstraint>> {
|
||||
if let Some(opt) = opts
|
||||
.iter()
|
||||
.find(|o| matches!(o.option, ColumnOption::Default(_)))
|
||||
{
|
||||
let default_constraint = match &opt.option {
|
||||
ColumnOption::Default(Expr::Value(v)) => {
|
||||
ColumnDefaultConstraint::Value(sql_value_to_value(column_name, data_type, v)?)
|
||||
}
|
||||
ColumnOption::Default(Expr::Function(func)) => {
|
||||
// Always use lowercase for function expression
|
||||
ColumnDefaultConstraint::Function(format!("{}", func).to_lowercase())
|
||||
}
|
||||
ColumnOption::Default(expr) => {
|
||||
return UnsupportedDefaultValueSnafu {
|
||||
column_name,
|
||||
expr: expr.clone(),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(Some(default_constraint))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `ColumnSchema` from `ColumnDef`.
|
||||
pub fn column_def_to_schema(column_def: &ColumnDef) -> Result<ColumnSchema> {
|
||||
let is_nullable = column_def
|
||||
.options
|
||||
.iter()
|
||||
.any(|o| matches!(o.option, ColumnOption::Null));
|
||||
|
||||
let name = column_def.name.value.clone();
|
||||
let data_type = sql_data_type_to_concrete_data_type(&column_def.data_type)?;
|
||||
let default_constraint =
|
||||
parse_column_default_constraint(&name, &data_type, &column_def.options)?;
|
||||
|
||||
Ok(ColumnSchema {
|
||||
name: column_def.name.value.clone(),
|
||||
data_type: sql_data_type_to_concrete_data_type(&column_def.data_type)?,
|
||||
name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
default_constraint,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,6 +255,8 @@ fn sql_data_type_to_concrete_data_type(data_type: &SqlDataType) -> Result<Concre
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use datatypes::value::OrderedFloat;
|
||||
|
||||
use super::*;
|
||||
use crate::ast::Ident;
|
||||
|
||||
@@ -130,4 +301,92 @@ mod tests {
|
||||
ConcreteDataType::timestamp_millis_datatype(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_number_to_value() {
|
||||
let v = sql_number_to_value(&ConcreteDataType::float64_datatype(), "3.0").unwrap();
|
||||
assert_eq!(Value::Float64(OrderedFloat(3.0)), v);
|
||||
|
||||
let v = sql_number_to_value(&ConcreteDataType::int32_datatype(), "999").unwrap();
|
||||
assert_eq!(Value::Int32(999), v);
|
||||
|
||||
let v = sql_number_to_value(&ConcreteDataType::string_datatype(), "999");
|
||||
assert!(v.is_err(), "parse value error is: {:?}", v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_value_to_value() {
|
||||
let sql_val = SqlValue::Null;
|
||||
assert_eq!(
|
||||
Value::Null,
|
||||
sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val).unwrap()
|
||||
);
|
||||
|
||||
let sql_val = SqlValue::Boolean(true);
|
||||
assert_eq!(
|
||||
Value::Boolean(true),
|
||||
sql_value_to_value("a", &ConcreteDataType::boolean_datatype(), &sql_val).unwrap()
|
||||
);
|
||||
|
||||
let sql_val = SqlValue::Number("3.0".to_string(), false);
|
||||
assert_eq!(
|
||||
Value::Float64(OrderedFloat(3.0)),
|
||||
sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val).unwrap()
|
||||
);
|
||||
|
||||
let sql_val = SqlValue::Number("3.0".to_string(), false);
|
||||
let v = sql_value_to_value("a", &ConcreteDataType::boolean_datatype(), &sql_val);
|
||||
assert!(v.is_err());
|
||||
assert!(format!("{:?}", v)
|
||||
.contains("Fail to parse number 3.0, invalid column type: Boolean(BooleanType)"));
|
||||
|
||||
let sql_val = SqlValue::Boolean(true);
|
||||
let v = sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val);
|
||||
assert!(v.is_err());
|
||||
assert!(format!("{:?}", v).contains(
|
||||
"column_name: \"a\", expect: Float64(Float64), actual: Boolean(BooleanType)"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_date_literal() {
|
||||
let value = sql_value_to_value(
|
||||
"date",
|
||||
&ConcreteDataType::date_datatype(),
|
||||
&SqlValue::DoubleQuotedString("2022-02-22".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(ConcreteDataType::date_datatype(), value.data_type());
|
||||
if let Value::Date(d) = value {
|
||||
assert_eq!("2022-02-22", d.to_string());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_datetime_literal() {
|
||||
let value = sql_value_to_value(
|
||||
"datetime_col",
|
||||
&ConcreteDataType::datetime_datatype(),
|
||||
&SqlValue::DoubleQuotedString("2022-02-22 00:01:03".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(ConcreteDataType::date_datatype(), value.data_type());
|
||||
if let Value::DateTime(d) = value {
|
||||
assert_eq!("2022-02-22 00:01:03", d.to_string());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_illegal_datetime_literal() {
|
||||
assert!(sql_value_to_value(
|
||||
"datetime_col",
|
||||
&ConcreteDataType::datetime_datatype(),
|
||||
&SqlValue::DoubleQuotedString("2022-02-22 00:01:61".to_string()),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user