mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-28 02:40:38 +00:00
refactor: split some convert function into sql-common crate (#6452)
refactor: split some convert function into `sql-common` crates Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>
This commit is contained in:
22
src/common/sql/Cargo.toml
Normal file
22
src/common/sql/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "common-sql"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
common-base.workspace = true
|
||||
common-datasource.workspace = true
|
||||
common-decimal.workspace = true
|
||||
common-error.workspace = true
|
||||
common-macro.workspace = true
|
||||
common-time.workspace = true
|
||||
datafusion-sql.workspace = true
|
||||
datatypes.workspace = true
|
||||
hex = "0.4"
|
||||
jsonb.workspace = true
|
||||
snafu.workspace = true
|
||||
sqlparser.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
1084
src/common/sql/src/convert.rs
Normal file
1084
src/common/sql/src/convert.rs
Normal file
File diff suppressed because it is too large
Load Diff
182
src/common/sql/src/default_constraint.rs
Normal file
182
src/common/sql/src/default_constraint.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use common_time::timezone::Timezone;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::constraint::{CURRENT_TIMESTAMP, CURRENT_TIMESTAMP_FN};
|
||||
use datatypes::schema::ColumnDefaultConstraint;
|
||||
pub use sqlparser::ast::{
|
||||
visit_expressions_mut, visit_statements_mut, BinaryOperator, ColumnDef, ColumnOption,
|
||||
ColumnOptionDef, DataType, Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments,
|
||||
Ident, ObjectName, SqlOption, TableConstraint, TimezoneInfo, UnaryOperator, Value as SqlValue,
|
||||
Visit, VisitMut, Visitor, VisitorMut,
|
||||
};
|
||||
|
||||
use crate::convert::{sql_number_to_value, sql_value_to_value};
|
||||
use crate::error::{Result, UnsupportedDefaultValueSnafu};
|
||||
|
||||
pub fn parse_column_default_constraint(
|
||||
column_name: &str,
|
||||
data_type: &ConcreteDataType,
|
||||
opts: &[ColumnOptionDef],
|
||||
timezone: Option<&Timezone>,
|
||||
) -> 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, timezone, None, false)?,
|
||||
),
|
||||
ColumnOption::Default(Expr::Function(func)) => {
|
||||
let mut func = format!("{func}").to_lowercase();
|
||||
// normalize CURRENT_TIMESTAMP to CURRENT_TIMESTAMP()
|
||||
if func == CURRENT_TIMESTAMP {
|
||||
func = CURRENT_TIMESTAMP_FN.to_string();
|
||||
}
|
||||
// Always use lowercase for function expression
|
||||
ColumnDefaultConstraint::Function(func.to_lowercase())
|
||||
}
|
||||
|
||||
ColumnOption::Default(Expr::UnaryOp { op, expr }) => {
|
||||
// Specialized process for handling numerical inputs to prevent
|
||||
// overflow errors during the parsing of negative numbers,
|
||||
// See https://github.com/GreptimeTeam/greptimedb/issues/4351
|
||||
if let (UnaryOperator::Minus, Expr::Value(SqlValue::Number(n, _))) =
|
||||
(op, expr.as_ref())
|
||||
{
|
||||
return Ok(Some(ColumnDefaultConstraint::Value(sql_number_to_value(
|
||||
data_type,
|
||||
&format!("-{n}"),
|
||||
)?)));
|
||||
}
|
||||
|
||||
if let Expr::Value(v) = &**expr {
|
||||
let value =
|
||||
sql_value_to_value(column_name, data_type, v, timezone, Some(*op), false)?;
|
||||
ColumnDefaultConstraint::Value(value)
|
||||
} else {
|
||||
return UnsupportedDefaultValueSnafu {
|
||||
column_name,
|
||||
expr: *expr.clone(),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
}
|
||||
ColumnOption::Default(others) => {
|
||||
return UnsupportedDefaultValueSnafu {
|
||||
column_name,
|
||||
expr: others.clone(),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
_ => {
|
||||
return UnsupportedDefaultValueSnafu {
|
||||
column_name,
|
||||
expr: Expr::Value(SqlValue::Null),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(default_constraint))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use datatypes::prelude::{ConcreteDataType, Value};
|
||||
use datatypes::types::BooleanType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_column_default_constraint() {
|
||||
let bool_value = sqlparser::ast::Value::Boolean(true);
|
||||
|
||||
let opts = vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Default(Expr::Value(bool_value)),
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::NotNull,
|
||||
},
|
||||
];
|
||||
|
||||
let constraint = parse_column_default_constraint(
|
||||
"coll",
|
||||
&ConcreteDataType::Boolean(BooleanType),
|
||||
&opts,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
constraint,
|
||||
Some(ColumnDefaultConstraint::Value(Value::Boolean(true)))
|
||||
);
|
||||
|
||||
// Test negative number
|
||||
let opts = vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Default(Expr::UnaryOp {
|
||||
op: UnaryOperator::Minus,
|
||||
expr: Box::new(Expr::Value(SqlValue::Number("32768".to_string(), false))),
|
||||
}),
|
||||
}];
|
||||
|
||||
let constraint = parse_column_default_constraint(
|
||||
"coll",
|
||||
&ConcreteDataType::int16_datatype(),
|
||||
&opts,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
constraint,
|
||||
Some(ColumnDefaultConstraint::Value(Value::Int16(-32768)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incorrect_default_value_issue_3479() {
|
||||
let opts = vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Default(Expr::Value(SqlValue::Number(
|
||||
"0.047318541668048164".into(),
|
||||
false,
|
||||
))),
|
||||
}];
|
||||
let constraint = parse_column_default_constraint(
|
||||
"coll",
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
&opts,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!("0.047318541668048164", constraint.to_string());
|
||||
let encoded: Vec<u8> = constraint.clone().try_into().unwrap();
|
||||
let decoded = ColumnDefaultConstraint::try_from(encoded.as_ref()).unwrap();
|
||||
assert_eq!(decoded, constraint);
|
||||
}
|
||||
}
|
||||
158
src/common/sql/src/error.rs
Normal file
158
src/common/sql/src/error.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use common_error::ext::ErrorExt;
|
||||
use common_error::status_code::StatusCode;
|
||||
use common_macro::stack_trace_debug;
|
||||
use common_time::timestamp::TimeUnit;
|
||||
use common_time::Timestamp;
|
||||
use datafusion_sql::sqlparser::ast::UnaryOperator;
|
||||
use datatypes::prelude::{ConcreteDataType, Value};
|
||||
use snafu::{Location, Snafu};
|
||||
pub use sqlparser::ast::{Expr, Value as SqlValue};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// SQL parser errors.
|
||||
// Now the error in parser does not contain backtrace to avoid generating backtrace
|
||||
// every time the parser parses an invalid SQL.
|
||||
#[derive(Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
#[stack_trace_debug]
|
||||
pub enum Error {
|
||||
#[snafu(display(
|
||||
"Column {} expect type: {:?}, actual: {:?}",
|
||||
column_name,
|
||||
expect,
|
||||
actual,
|
||||
))]
|
||||
ColumnTypeMismatch {
|
||||
column_name: String,
|
||||
expect: ConcreteDataType,
|
||||
actual: ConcreteDataType,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to parse value: {}", msg))]
|
||||
ParseSqlValue {
|
||||
msg: String,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Unsupported expr in default constraint: {:?} for column: {}",
|
||||
expr,
|
||||
column_name
|
||||
))]
|
||||
UnsupportedDefaultValue {
|
||||
column_name: String,
|
||||
expr: Expr,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Unable to convert sql value {} to datatype {:?}", value, datatype))]
|
||||
ConvertSqlValue {
|
||||
value: SqlValue,
|
||||
datatype: ConcreteDataType,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid sql value: {}", value))]
|
||||
InvalidSqlValue {
|
||||
value: String,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Unsupported unary operator {}", unary_op))]
|
||||
UnsupportedUnaryOp {
|
||||
unary_op: UnaryOperator,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid unary operator {} for value {}", unary_op, value))]
|
||||
InvalidUnaryOp {
|
||||
unary_op: UnaryOperator,
|
||||
value: Value,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to cast SQL value {} to datatype {}", sql_value, datatype))]
|
||||
InvalidCast {
|
||||
sql_value: sqlparser::ast::Value,
|
||||
datatype: ConcreteDataType,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
source: datatypes::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Unable to convert {} to datatype {:?}", value, datatype))]
|
||||
ConvertStr {
|
||||
value: String,
|
||||
datatype: ConcreteDataType,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Converting timestamp {:?} to unit {:?} overflow",
|
||||
timestamp,
|
||||
target_unit
|
||||
))]
|
||||
TimestampOverflow {
|
||||
timestamp: Timestamp,
|
||||
target_unit: TimeUnit,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Datatype error: {}", source))]
|
||||
Datatype {
|
||||
source: datatypes::error::Error,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
use Error::*;
|
||||
|
||||
match self {
|
||||
UnsupportedDefaultValue { .. } => StatusCode::Unsupported,
|
||||
ParseSqlValue { .. } => StatusCode::InvalidSyntax,
|
||||
ColumnTypeMismatch { .. }
|
||||
| InvalidSqlValue { .. }
|
||||
| UnsupportedUnaryOp { .. }
|
||||
| InvalidUnaryOp { .. }
|
||||
| InvalidCast { .. }
|
||||
| ConvertStr { .. }
|
||||
| TimestampOverflow { .. } => StatusCode::InvalidArguments,
|
||||
Datatype { source, .. } => source.status_code(),
|
||||
ConvertSqlValue { .. } => StatusCode::Unsupported,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
19
src/common/sql/src/lib.rs
Normal file
19
src/common/sql/src/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![feature(assert_matches)]
|
||||
|
||||
pub mod convert;
|
||||
pub mod default_constraint;
|
||||
pub mod error;
|
||||
Reference in New Issue
Block a user