mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-04 12:22:55 +00:00
feat: cast strings to numerics automatically in mysql connections (#6015)
* chore: insert support string to numeric auto cast * test: add sqlness test * chore: remove log * test: fix sql test * style: fix clippy * test: test invalid number * feat: do not convert to default if unable to parse * chore: update comment * test: update sqlness test * test: update prepare test
This commit is contained in:
@@ -129,6 +129,7 @@ impl<'a> StatementToRegion<'a> {
|
||||
column_schema,
|
||||
&sql_row[i],
|
||||
Some(&query_ctx.timezone()),
|
||||
query_ctx.auto_string_to_numeric(),
|
||||
)?;
|
||||
grpc_row.values.push(value);
|
||||
}
|
||||
@@ -205,10 +206,14 @@ fn column_names<'a>(stmt: &'a Insert, table_schema: &'a SchemaRef) -> Vec<&'a St
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts SQL value to gRPC value according to the column schema.
|
||||
/// If `auto_string_to_numeric` is true, tries to cast the string value to numeric values,
|
||||
/// and fills the default value if the cast fails.
|
||||
fn sql_value_to_grpc_value(
|
||||
column_schema: &ColumnSchema,
|
||||
sql_val: &SqlValue,
|
||||
timezone: Option<&Timezone>,
|
||||
auto_string_to_numeric: bool,
|
||||
) -> Result<GrpcValue> {
|
||||
let column = &column_schema.name;
|
||||
let value = if replace_default(sql_val) {
|
||||
@@ -222,8 +227,15 @@ fn sql_value_to_grpc_value(
|
||||
column: column.clone(),
|
||||
})?
|
||||
} else {
|
||||
statements::sql_value_to_value(column, &column_schema.data_type, sql_val, timezone, None)
|
||||
.context(ParseSqlSnafu)?
|
||||
statements::sql_value_to_value(
|
||||
column,
|
||||
&column_schema.data_type,
|
||||
sql_val,
|
||||
timezone,
|
||||
None,
|
||||
auto_string_to_numeric,
|
||||
)
|
||||
.context(ParseSqlSnafu)?
|
||||
};
|
||||
|
||||
let grpc_value = value_to_grpc_value(value);
|
||||
|
||||
@@ -185,7 +185,7 @@ fn values_to_vectors_by_exact_types(
|
||||
args.iter()
|
||||
.zip(exact_types.iter())
|
||||
.map(|(value, data_type)| {
|
||||
let value = sql_value_to_value(DUMMY_COLUMN, data_type, value, tz, None)
|
||||
let value = sql_value_to_value(DUMMY_COLUMN, data_type, value, tz, None, false)
|
||||
.context(error::ParseSqlValueSnafu)?;
|
||||
|
||||
Ok(value_to_vector(value))
|
||||
@@ -202,7 +202,9 @@ fn values_to_vectors_by_valid_types(
|
||||
args.iter()
|
||||
.map(|value| {
|
||||
for data_type in valid_types {
|
||||
if let Ok(value) = sql_value_to_value(DUMMY_COLUMN, data_type, value, tz, None) {
|
||||
if let Ok(value) =
|
||||
sql_value_to_value(DUMMY_COLUMN, data_type, value, tz, None, false)
|
||||
{
|
||||
return Ok(value_to_vector(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1648,8 +1648,15 @@ fn convert_value(
|
||||
timezone: &Timezone,
|
||||
unary_op: Option<UnaryOperator>,
|
||||
) -> Result<Value> {
|
||||
sql_value_to_value("<NONAME>", &data_type, value, Some(timezone), unary_op)
|
||||
.context(ParseSqlValueSnafu)
|
||||
sql_value_to_value(
|
||||
"<NONAME>",
|
||||
&data_type,
|
||||
value,
|
||||
Some(timezone),
|
||||
unary_op,
|
||||
false,
|
||||
)
|
||||
.context(ParseSqlValueSnafu)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -241,10 +241,12 @@ pub fn convert_value(param: &ParamValue, t: &ConcreteDataType) -> Result<ScalarV
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an MySQL expression to a scalar value.
|
||||
/// It automatically handles the conversion of strings to numeric values.
|
||||
pub fn convert_expr_to_scalar_value(param: &Expr, t: &ConcreteDataType) -> Result<ScalarValue> {
|
||||
match param {
|
||||
Expr::Value(v) => {
|
||||
let v = sql_value_to_value("", t, v, None, None);
|
||||
let v = sql_value_to_value("", t, v, None, None, true);
|
||||
match v {
|
||||
Ok(v) => v
|
||||
.try_to_scalar_value(t)
|
||||
@@ -256,7 +258,7 @@ pub fn convert_expr_to_scalar_value(param: &Expr, t: &ConcreteDataType) -> Resul
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp { op, expr } if let Expr::Value(v) = &**expr => {
|
||||
let v = sql_value_to_value("", t, v, None, Some(*op));
|
||||
let v = sql_value_to_value("", t, v, None, Some(*op), true);
|
||||
match v {
|
||||
Ok(v) => v
|
||||
.try_to_scalar_value(t)
|
||||
|
||||
@@ -406,6 +406,11 @@ impl QueryContext {
|
||||
pub fn get_snapshot(&self, region_id: u64) -> Option<u64> {
|
||||
self.snapshot_seqs.read().unwrap().get(®ion_id).cloned()
|
||||
}
|
||||
|
||||
/// Returns `true` if the session can cast strings to numbers in MySQL style.
|
||||
pub fn auto_string_to_numeric(&self) -> bool {
|
||||
matches!(self.channel, Channel::Mysql)
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryContextBuilder {
|
||||
|
||||
@@ -355,6 +355,14 @@ pub enum Error {
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Unable to convert {} to datatype {:?}", value, datatype))]
|
||||
ConvertStr {
|
||||
value: String,
|
||||
datatype: ConcreteDataType,
|
||||
#[snafu(implicit)]
|
||||
location: Location,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
@@ -391,7 +399,8 @@ impl ErrorExt for Error {
|
||||
| InvalidInterval { .. }
|
||||
| InvalidUnaryOp { .. }
|
||||
| InvalidPartitionNumber { .. }
|
||||
| UnsupportedUnaryOp { .. } => StatusCode::InvalidArguments,
|
||||
| UnsupportedUnaryOp { .. }
|
||||
| ConvertStr { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
SerializeColumnDefaultConstraint { source, .. } => source.status_code(),
|
||||
ConvertToGrpcDataType { source, .. } => source.status_code(),
|
||||
|
||||
@@ -53,11 +53,11 @@ use crate::ast::{
|
||||
Value as SqlValue,
|
||||
};
|
||||
use crate::error::{
|
||||
self, ColumnTypeMismatchSnafu, ConvertSqlValueSnafu, ConvertToGrpcDataTypeSnafu,
|
||||
ConvertValueSnafu, DatatypeSnafu, InvalidCastSnafu, InvalidSqlValueSnafu, InvalidUnaryOpSnafu,
|
||||
ParseSqlValueSnafu, Result, SerializeColumnDefaultConstraintSnafu, SetFulltextOptionSnafu,
|
||||
SetSkippingIndexOptionSnafu, TimestampOverflowSnafu, UnsupportedDefaultValueSnafu,
|
||||
UnsupportedUnaryOpSnafu,
|
||||
self, ColumnTypeMismatchSnafu, ConvertSqlValueSnafu, ConvertStrSnafu,
|
||||
ConvertToGrpcDataTypeSnafu, ConvertValueSnafu, DatatypeSnafu, InvalidCastSnafu,
|
||||
InvalidSqlValueSnafu, InvalidUnaryOpSnafu, ParseSqlValueSnafu, Result,
|
||||
SerializeColumnDefaultConstraintSnafu, SetFulltextOptionSnafu, SetSkippingIndexOptionSnafu,
|
||||
TimestampOverflowSnafu, UnsupportedDefaultValueSnafu, UnsupportedUnaryOpSnafu,
|
||||
};
|
||||
use crate::statements::create::Column;
|
||||
pub use crate::statements::option_map::OptionMap;
|
||||
@@ -70,7 +70,14 @@ fn parse_string_to_value(
|
||||
s: String,
|
||||
data_type: &ConcreteDataType,
|
||||
timezone: Option<&Timezone>,
|
||||
auto_string_to_numeric: bool,
|
||||
) -> Result<Value> {
|
||||
if auto_string_to_numeric {
|
||||
if let Some(value) = auto_cast_to_numeric(&s, data_type)? {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
|
||||
ensure!(
|
||||
data_type.is_stringifiable(),
|
||||
ColumnTypeMismatchSnafu {
|
||||
@@ -133,6 +140,42 @@ fn parse_string_to_value(
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts string to value of specified numeric data type.
|
||||
/// If the string cannot be parsed, returns an error.
|
||||
///
|
||||
/// Returns None if the data type doesn't support auto casting.
|
||||
fn auto_cast_to_numeric(s: &str, data_type: &ConcreteDataType) -> Result<Option<Value>> {
|
||||
let value = match data_type {
|
||||
ConcreteDataType::Boolean(_) => s.parse::<bool>().map(Value::Boolean).ok(),
|
||||
ConcreteDataType::Int8(_) => s.parse::<i8>().map(Value::Int8).ok(),
|
||||
ConcreteDataType::Int16(_) => s.parse::<i16>().map(Value::Int16).ok(),
|
||||
ConcreteDataType::Int32(_) => s.parse::<i32>().map(Value::Int32).ok(),
|
||||
ConcreteDataType::Int64(_) => s.parse::<i64>().map(Value::Int64).ok(),
|
||||
ConcreteDataType::UInt8(_) => s.parse::<u8>().map(Value::UInt8).ok(),
|
||||
ConcreteDataType::UInt16(_) => s.parse::<u16>().map(Value::UInt16).ok(),
|
||||
ConcreteDataType::UInt32(_) => s.parse::<u32>().map(Value::UInt32).ok(),
|
||||
ConcreteDataType::UInt64(_) => s.parse::<u64>().map(Value::UInt64).ok(),
|
||||
ConcreteDataType::Float32(_) => s
|
||||
.parse::<f32>()
|
||||
.map(|v| Value::Float32(OrderedF32::from(v)))
|
||||
.ok(),
|
||||
ConcreteDataType::Float64(_) => s
|
||||
.parse::<f64>()
|
||||
.map(|v| Value::Float64(OrderedF64::from(v)))
|
||||
.ok(),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
match value {
|
||||
Some(value) => Ok(Some(value)),
|
||||
None => ConvertStrSnafu {
|
||||
value: s,
|
||||
datatype: data_type.clone(),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_string(s: &str) -> Result<Value> {
|
||||
match hex::decode(s) {
|
||||
Ok(b) => Ok(Value::Binary(Bytes::from(b))),
|
||||
@@ -228,12 +271,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts SQL value to value according to the data type.
|
||||
/// If `auto_string_to_numeric` is true, tries to cast the string value to numeric values,
|
||||
/// and returns error if the cast fails.
|
||||
pub fn sql_value_to_value(
|
||||
column_name: &str,
|
||||
data_type: &ConcreteDataType,
|
||||
sql_val: &SqlValue,
|
||||
timezone: Option<&Timezone>,
|
||||
unary_op: Option<UnaryOperator>,
|
||||
auto_string_to_numeric: bool,
|
||||
) -> Result<Value> {
|
||||
let mut value = match sql_val {
|
||||
SqlValue::Number(n, _) => sql_number_to_value(data_type, n)?,
|
||||
@@ -250,9 +297,13 @@ pub fn sql_value_to_value(
|
||||
|
||||
(*b).into()
|
||||
}
|
||||
SqlValue::DoubleQuotedString(s) | SqlValue::SingleQuotedString(s) => {
|
||||
parse_string_to_value(column_name, s.clone(), data_type, timezone)?
|
||||
}
|
||||
SqlValue::DoubleQuotedString(s) | SqlValue::SingleQuotedString(s) => parse_string_to_value(
|
||||
column_name,
|
||||
s.clone(),
|
||||
data_type,
|
||||
timezone,
|
||||
auto_string_to_numeric,
|
||||
)?,
|
||||
SqlValue::HexStringLiteral(s) => {
|
||||
// Should not directly write binary into json column
|
||||
ensure!(
|
||||
@@ -371,7 +422,7 @@ fn parse_column_default_constraint(
|
||||
{
|
||||
let default_constraint = match &opt.option {
|
||||
ColumnOption::Default(Expr::Value(v)) => ColumnDefaultConstraint::Value(
|
||||
sql_value_to_value(column_name, data_type, v, timezone, None)?,
|
||||
sql_value_to_value(column_name, data_type, v, timezone, None, false)?,
|
||||
),
|
||||
ColumnOption::Default(Expr::Function(func)) => {
|
||||
let mut func = format!("{func}").to_lowercase();
|
||||
@@ -397,7 +448,8 @@ fn parse_column_default_constraint(
|
||||
}
|
||||
|
||||
if let Expr::Value(v) = &**expr {
|
||||
let value = sql_value_to_value(column_name, data_type, v, timezone, Some(*op))?;
|
||||
let value =
|
||||
sql_value_to_value(column_name, data_type, v, timezone, Some(*op), false)?;
|
||||
ColumnDefaultConstraint::Value(value)
|
||||
} else {
|
||||
return UnsupportedDefaultValueSnafu {
|
||||
@@ -440,8 +492,6 @@ pub fn has_primary_key_option(column_def: &ColumnDef) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(yingwen): Make column nullable by default, and checks invalid case like
|
||||
// a column is not nullable but has a default value null.
|
||||
/// Create a `ColumnSchema` from `Column`.
|
||||
pub fn column_to_schema(
|
||||
column: &Column,
|
||||
@@ -809,7 +859,8 @@ mod tests {
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None
|
||||
None,
|
||||
false
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
@@ -822,7 +873,8 @@ mod tests {
|
||||
&ConcreteDataType::boolean_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None
|
||||
None,
|
||||
false
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
@@ -835,7 +887,8 @@ mod tests {
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None
|
||||
None,
|
||||
false
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
@@ -847,6 +900,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
assert!(format!("{v:?}").contains("Failed to parse number '3.0' to boolean column type"));
|
||||
@@ -858,6 +912,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
assert!(
|
||||
@@ -874,6 +929,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Binary(Bytes::from(b"Hello world!".as_slice())), v);
|
||||
@@ -885,6 +941,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -899,6 +956,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
assert!(
|
||||
@@ -913,6 +971,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
assert!(format!("{v:?}").contains("invalid character"), "v is {v:?}",);
|
||||
@@ -924,6 +983,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
|
||||
@@ -934,6 +994,7 @@ mod tests {
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -948,13 +1009,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_date_literal() {
|
||||
fn test_parse_date_literal() {
|
||||
let value = sql_value_to_value(
|
||||
"date",
|
||||
&ConcreteDataType::date_datatype(),
|
||||
&SqlValue::DoubleQuotedString("2022-02-22".to_string()),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(ConcreteDataType::date_datatype(), value.data_type());
|
||||
@@ -971,6 +1033,7 @@ mod tests {
|
||||
&SqlValue::DoubleQuotedString("2022-02-22".to_string()),
|
||||
Some(&Timezone::from_tz_string("+07:00").unwrap()),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(ConcreteDataType::date_datatype(), value.data_type());
|
||||
@@ -988,6 +1051,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01+08:00".to_string(),
|
||||
&ConcreteDataType::timestamp_millisecond_datatype(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
@@ -1005,6 +1069,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01+08:00".to_string(),
|
||||
&ConcreteDataType::timestamp_datatype(TimeUnit::Second),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
@@ -1022,6 +1087,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01+08:00".to_string(),
|
||||
&ConcreteDataType::timestamp_datatype(TimeUnit::Microsecond),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
@@ -1039,6 +1105,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01+08:00".to_string(),
|
||||
&ConcreteDataType::timestamp_datatype(TimeUnit::Nanosecond),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
@@ -1056,6 +1123,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01+08".to_string(),
|
||||
&ConcreteDataType::timestamp_datatype(TimeUnit::Nanosecond),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -1065,6 +1133,7 @@ mod tests {
|
||||
"2022-02-22T00:01:01".to_string(),
|
||||
&ConcreteDataType::timestamp_datatype(TimeUnit::Nanosecond),
|
||||
Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap()),
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
@@ -1086,6 +1155,7 @@ mod tests {
|
||||
r#"{"a": "b"}"#.to_string(),
|
||||
&ConcreteDataType::json_datatype(),
|
||||
None,
|
||||
false,
|
||||
) {
|
||||
Ok(Value::Binary(b)) => {
|
||||
assert_eq!(
|
||||
@@ -1105,6 +1175,7 @@ mod tests {
|
||||
r#"Nicola Kovac is the best rifler in the world"#.to_string(),
|
||||
&ConcreteDataType::json_datatype(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.is_err())
|
||||
}
|
||||
@@ -1476,7 +1547,8 @@ mod tests {
|
||||
&ConcreteDataType::string_datatype(),
|
||||
&SqlValue::Placeholder("default".into()),
|
||||
None,
|
||||
None
|
||||
None,
|
||||
false
|
||||
)
|
||||
.is_err());
|
||||
assert!(sql_value_to_value(
|
||||
@@ -1485,6 +1557,7 @@ mod tests {
|
||||
&SqlValue::Placeholder("default".into()),
|
||||
None,
|
||||
Some(UnaryOperator::Minus),
|
||||
false
|
||||
)
|
||||
.is_err());
|
||||
assert!(sql_value_to_value(
|
||||
@@ -1493,6 +1566,7 @@ mod tests {
|
||||
&SqlValue::Number("3".into(), false),
|
||||
None,
|
||||
Some(UnaryOperator::Minus),
|
||||
false
|
||||
)
|
||||
.is_err());
|
||||
assert!(sql_value_to_value(
|
||||
@@ -1500,8 +1574,322 @@ mod tests {
|
||||
&ConcreteDataType::uint16_datatype(),
|
||||
&SqlValue::Number("3".into(), false),
|
||||
None,
|
||||
None
|
||||
None,
|
||||
false
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_value_auto_numeric() {
|
||||
// Test string to boolean with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"true".to_string(),
|
||||
&ConcreteDataType::boolean_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Boolean(true), result);
|
||||
|
||||
// Test invalid string to boolean with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_boolean".to_string(),
|
||||
&ConcreteDataType::boolean_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to int8
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"42".to_string(),
|
||||
&ConcreteDataType::int8_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Int8(42), result);
|
||||
|
||||
// Test invalid string to int8 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_an_int8".to_string(),
|
||||
&ConcreteDataType::int8_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to int16
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"1000".to_string(),
|
||||
&ConcreteDataType::int16_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Int16(1000), result);
|
||||
|
||||
// Test invalid string to int16 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_an_int16".to_string(),
|
||||
&ConcreteDataType::int16_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to int32
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"100000".to_string(),
|
||||
&ConcreteDataType::int32_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Int32(100000), result);
|
||||
|
||||
// Test invalid string to int32 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_an_int32".to_string(),
|
||||
&ConcreteDataType::int32_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to int64
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"1000000".to_string(),
|
||||
&ConcreteDataType::int64_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Int64(1000000), result);
|
||||
|
||||
// Test invalid string to int64 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_an_int64".to_string(),
|
||||
&ConcreteDataType::int64_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to uint8
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"200".to_string(),
|
||||
&ConcreteDataType::uint8_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::UInt8(200), result);
|
||||
|
||||
// Test invalid string to uint8 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_uint8".to_string(),
|
||||
&ConcreteDataType::uint8_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to uint16
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"60000".to_string(),
|
||||
&ConcreteDataType::uint16_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::UInt16(60000), result);
|
||||
|
||||
// Test invalid string to uint16 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_uint16".to_string(),
|
||||
&ConcreteDataType::uint16_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to uint32
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"4000000000".to_string(),
|
||||
&ConcreteDataType::uint32_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::UInt32(4000000000), result);
|
||||
|
||||
// Test invalid string to uint32 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_uint32".to_string(),
|
||||
&ConcreteDataType::uint32_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to uint64
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"18446744073709551615".to_string(),
|
||||
&ConcreteDataType::uint64_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::UInt64(18446744073709551615), result);
|
||||
|
||||
// Test invalid string to uint64 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_uint64".to_string(),
|
||||
&ConcreteDataType::uint64_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to float32
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"3.5".to_string(),
|
||||
&ConcreteDataType::float32_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Float32(OrderedF32::from(3.5)), result);
|
||||
|
||||
// Test invalid string to float32 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_float32".to_string(),
|
||||
&ConcreteDataType::float32_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test string to float64
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"3.5".to_string(),
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Float64(OrderedF64::from(3.5)), result);
|
||||
|
||||
// Test invalid string to float64 with auto cast
|
||||
let result = parse_string_to_value(
|
||||
"col",
|
||||
"not_a_float64".to_string(),
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_string_to_numeric() {
|
||||
// Test with auto_string_to_numeric=true
|
||||
let sql_val = SqlValue::SingleQuotedString("123".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::int32_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Int32(123), v);
|
||||
|
||||
// Test with a float string
|
||||
let sql_val = SqlValue::SingleQuotedString("3.5".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::float64_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Float64(OrderedFloat(3.5)), v);
|
||||
|
||||
// Test with auto_string_to_numeric=false
|
||||
let sql_val = SqlValue::SingleQuotedString("123".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::int32_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
|
||||
// Test with an invalid numeric string but auto_string_to_numeric=true
|
||||
// Should return an error now with the new auto_cast_to_numeric behavior
|
||||
let sql_val = SqlValue::SingleQuotedString("not_a_number".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::int32_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(v.is_err());
|
||||
|
||||
// Test with boolean type
|
||||
let sql_val = SqlValue::SingleQuotedString("true".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::boolean_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Value::Boolean(true), v);
|
||||
|
||||
// Non-numeric types should still be handled normally
|
||||
let sql_val = SqlValue::SingleQuotedString("hello".to_string());
|
||||
let v = sql_value_to_value(
|
||||
"a",
|
||||
&ConcreteDataType::string_datatype(),
|
||||
&sql_val,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(v.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
41
tests/cases/standalone/common/insert/mysql_insert.result
Normal file
41
tests/cases/standalone/common/insert/mysql_insert.result
Normal file
@@ -0,0 +1,41 @@
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
CREATE TABLE integers (
|
||||
val INT,
|
||||
ts TIMESTAMP,
|
||||
TIME INDEX(ts)
|
||||
);
|
||||
|
||||
affected_rows: 0
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES (11, 1), (12, 2);
|
||||
|
||||
affected_rows: 2
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES ('13', 3), ('14', 4);
|
||||
|
||||
affected_rows: 2
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES ('15a', 5), ('16', 6);
|
||||
|
||||
Failed to execute query, err: MySqlError { ERROR 1210 (HY000): (InvalidArguments): Unable to convert 15a to datatype Int32(Int32Type) }
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
SELECT * FROM integers ORDER BY ts;
|
||||
|
||||
+-----+----------------------------+
|
||||
| val | ts |
|
||||
+-----+----------------------------+
|
||||
| 11 | 1970-01-01 00:00:00.001000 |
|
||||
| 12 | 1970-01-01 00:00:00.002000 |
|
||||
| 13 | 1970-01-01 00:00:00.003000 |
|
||||
| 14 | 1970-01-01 00:00:00.004000 |
|
||||
+-----+----------------------------+
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
DROP TABLE integers;
|
||||
|
||||
affected_rows: 0
|
||||
|
||||
21
tests/cases/standalone/common/insert/mysql_insert.sql
Normal file
21
tests/cases/standalone/common/insert/mysql_insert.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
CREATE TABLE integers (
|
||||
val INT,
|
||||
ts TIMESTAMP,
|
||||
TIME INDEX(ts)
|
||||
);
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES (11, 1), (12, 2);
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES ('13', 3), ('14', 4);
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
INSERT INTO integers VALUES ('15a', 5), ('16', 6);
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
SELECT * FROM integers ORDER BY ts;
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
DROP TABLE integers;
|
||||
@@ -22,7 +22,7 @@ EXECUTE stmt USING 1;
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
EXECUTE stmt USING 'a';
|
||||
|
||||
Failed to execute query, err: MySqlError { ERROR 1210 (HY000): (InvalidArguments): Invalid request parameter: Column expect type: Int32(Int32Type), actual: String(StringType) }
|
||||
Failed to execute query, err: MySqlError { ERROR 1210 (HY000): (InvalidArguments): Invalid request parameter: Unable to convert a to datatype Int32(Int32Type) }
|
||||
|
||||
-- SQLNESS PROTOCOL MYSQL
|
||||
DEALLOCATE stmt;
|
||||
|
||||
Reference in New Issue
Block a user