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:
Yingwen
2025-04-30 16:23:50 +08:00
committed by GitHub
parent a706edbb73
commit 44e75b142d
10 changed files with 515 additions and 28 deletions

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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)]

View File

@@ -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)

View File

@@ -406,6 +406,11 @@ impl QueryContext {
pub fn get_snapshot(&self, region_id: u64) -> Option<u64> {
self.snapshot_seqs.read().unwrap().get(&region_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 {

View File

@@ -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(),

View File

@@ -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());
}
}

View 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

View 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;

View File

@@ -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;