diff --git a/README.md b/README.md index e2fe7958..a8ef7159 100755 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ let fnck_sql = DataBaseBuilder::path("./data") - UInteger - Bigint - UBigint + - Char - Varchar - DDL - Begin (Server only) @@ -177,6 +178,7 @@ let fnck_sql = DataBaseBuilder::path("./data") - UBigint - Float - Double + - Char - Varchar - Date - DateTime diff --git a/src/bin/server.rs b/src/bin/server.rs index 1cd70384..c2e222e9 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -198,7 +198,9 @@ fn encode_tuples<'a>(schema: &Schema, tuples: Vec) -> PgWireResult encoder.encode_field(&value.u64().map(|v| v as i64)), LogicalType::Float => encoder.encode_field(&value.float()), LogicalType::Double => encoder.encode_field(&value.double()), - LogicalType::Varchar(_) => encoder.encode_field(&value.utf8()), + LogicalType::Char(_) | LogicalType::Varchar(_) => { + encoder.encode_field(&value.utf8()) + } LogicalType::Date => encoder.encode_field(&value.date()), LogicalType::DateTime => encoder.encode_field(&value.datetime()), LogicalType::Decimal(_, _) => todo!(), @@ -224,6 +226,7 @@ fn into_pg_type(data_type: &LogicalType) -> PgWireResult { LogicalType::Double => Type::FLOAT8, LogicalType::Varchar(_) => Type::VARCHAR, LogicalType::Date | LogicalType::DateTime => Type::DATE, + LogicalType::Char(_) => Type::CHAR, LogicalType::Decimal(_, _) => todo!(), _ => { return Err(PgWireError::UserError(Box::new(ErrorInfo::new( diff --git a/src/execution/volcano/ddl/add_column.rs b/src/execution/volcano/ddl/add_column.rs index 67afa14e..01d7c22a 100644 --- a/src/execution/volcano/ddl/add_column.rs +++ b/src/execution/volcano/ddl/add_column.rs @@ -30,7 +30,7 @@ impl WriteExecutor for AddColumn { impl AddColumn { #[try_stream(boxed, ok = Tuple, error = DatabaseError)] - async fn _execute(self, transaction: &mut T) { + async fn _execute(mut self, transaction: &mut T) { let AddColumnOperator { table_name, column, @@ -38,6 +38,13 @@ impl AddColumn { } = &self.op; let mut unique_values = column.desc().is_unique.then(Vec::new); let mut tuples = Vec::new(); + let schema = self.input.output_schema(); + let mut types = Vec::with_capacity(schema.len() + 1); + + for column_ref in schema.iter() { + types.push(*column_ref.datatype()); + } + types.push(*column.datatype()); #[for_await] for tuple in build_read(self.input, transaction) { @@ -54,7 +61,7 @@ impl AddColumn { tuples.push(tuple); } for tuple in tuples { - transaction.append(table_name, tuple, true)?; + transaction.append(table_name, tuple, &types, true)?; } let col_id = transaction.add_column(table_name, column, *if_not_exists)?; diff --git a/src/execution/volcano/ddl/drop_column.rs b/src/execution/volcano/ddl/drop_column.rs index 0fc51f07..ebb9ae40 100644 --- a/src/execution/volcano/ddl/drop_column.rs +++ b/src/execution/volcano/ddl/drop_column.rs @@ -46,7 +46,14 @@ impl DropColumn { ))?; } let mut tuples = Vec::new(); + let mut types = Vec::with_capacity(tuple_columns.len() - 1); + for (i, column_ref) in tuple_columns.iter().enumerate() { + if i == column_index { + continue; + } + types.push(*column_ref.datatype()); + } #[for_await] for tuple in build_read(self.input, transaction) { let mut tuple: Tuple = tuple?; @@ -55,7 +62,7 @@ impl DropColumn { tuples.push(tuple); } for tuple in tuples { - transaction.append(&table_name, tuple, true)?; + transaction.append(&table_name, tuple, &types, true)?; } transaction.drop_column(&table_name, &column_name)?; diff --git a/src/execution/volcano/dml/copy_from_file.rs b/src/execution/volcano/dml/copy_from_file.rs index 1f28a275..935ebc46 100644 --- a/src/execution/volcano/dml/copy_from_file.rs +++ b/src/execution/volcano/dml/copy_from_file.rs @@ -3,7 +3,7 @@ use crate::errors::DatabaseError; use crate::execution::volcano::{BoxedExecutor, WriteExecutor}; use crate::planner::operator::copy_from_file::CopyFromFileOperator; use crate::storage::Transaction; -use crate::types::tuple::Tuple; +use crate::types::tuple::{types, Tuple}; use crate::types::tuple_builder::TupleBuilder; use futures_async_stream::try_stream; use std::fs::File; @@ -30,6 +30,7 @@ impl WriteExecutor for CopyFromFile { impl CopyFromFile { #[try_stream(boxed, ok = Tuple, error = DatabaseError)] pub async fn _execute(self, transaction: &mut T) { + let types = types(&self.op.schema_ref); let (tx, mut rx) = tokio::sync::mpsc::channel(1); let (tx1, mut rx1) = tokio::sync::mpsc::channel(1); // # Cancellation @@ -39,7 +40,7 @@ impl CopyFromFile { let handle = tokio::task::spawn_blocking(|| self.read_file_blocking(tx)); let mut size = 0_usize; while let Some(chunk) = rx.recv().await { - transaction.append(&table_name, chunk, false)?; + transaction.append(&table_name, chunk, &types, false)?; size += 1; } handle.await??; diff --git a/src/execution/volcano/dml/insert.rs b/src/execution/volcano/dml/insert.rs index 3be35465..70145974 100644 --- a/src/execution/volcano/dml/insert.rs +++ b/src/execution/volcano/dml/insert.rs @@ -60,6 +60,7 @@ impl Insert { .ok_or_else(|| DatabaseError::NotNull)?; if let Some(table_catalog) = transaction.table(table_name.clone()).cloned() { + let types = table_catalog.types(); #[for_await] for tuple in build_read(input, transaction) { let Tuple { values, .. } = tuple?; @@ -99,7 +100,7 @@ impl Insert { } } for tuple in tuples { - transaction.append(&table_name, tuple, is_overwrite)?; + transaction.append(&table_name, tuple, &types, is_overwrite)?; } } } diff --git a/src/execution/volcano/dml/update.rs b/src/execution/volcano/dml/update.rs index 3fca6469..bb4677e7 100644 --- a/src/execution/volcano/dml/update.rs +++ b/src/execution/volcano/dml/update.rs @@ -6,6 +6,7 @@ use crate::planner::operator::update::UpdateOperator; use crate::planner::LogicalPlan; use crate::storage::Transaction; use crate::types::index::Index; +use crate::types::tuple::types; use crate::types::tuple::Tuple; use futures_async_stream::try_stream; use std::collections::HashMap; @@ -44,6 +45,7 @@ impl Update { } = self; let values_schema = values.output_schema().clone(); let input_schema = input.output_schema().clone(); + let types = types(&input_schema); if let Some(table_catalog) = transaction.table(table_name.clone()).cloned() { let mut value_map = HashMap::new(); @@ -94,7 +96,7 @@ impl Update { transaction.add_index(&table_name, index, tuple.id.as_ref().unwrap())?; } - transaction.append(&table_name, tuple, is_overwrite)?; + transaction.append(&table_name, tuple, &types, is_overwrite)?; } } } diff --git a/src/execution/volcano/dql/describe.rs b/src/execution/volcano/dql/describe.rs index 62f5fa1b..c24c60b2 100644 --- a/src/execution/volcano/dql/describe.rs +++ b/src/execution/volcano/dql/describe.rs @@ -51,14 +51,21 @@ impl Describe { }; for column in table.columns() { + let datatype = column.datatype(); let values = vec![ Arc::new(DataValue::Utf8(Some(column.name().to_string()))), - Arc::new(DataValue::Utf8(Some(column.datatype().to_string()))), + Arc::new(DataValue::Utf8(Some(datatype.to_string()))), + Arc::new(DataValue::Utf8(Some( + datatype + .raw_len() + .map(|len| len.to_string()) + .unwrap_or_else(|| "DYNAMIC".to_string()), + ))), Arc::new(DataValue::Utf8(Some(column.nullable.to_string()))), key_fn(column), column .default_value() - .unwrap_or_else(|| Arc::new(DataValue::none(column.datatype()))), + .unwrap_or_else(|| Arc::new(DataValue::none(datatype))), ]; yield Tuple { id: None, values }; } diff --git a/src/expression/value_compute.rs b/src/expression/value_compute.rs index e018cb09..32c07c84 100644 --- a/src/expression/value_compute.rs +++ b/src/expression/value_compute.rs @@ -502,7 +502,7 @@ impl DataValue { _ => return Err(DatabaseError::UnsupportedBinaryOperator(unified_type, *op)), } } - LogicalType::Varchar(_) => { + LogicalType::Varchar(_) | LogicalType::Char(_) => { let left_value = unpack_utf8(self.clone().cast(&unified_type)?); let right_value = unpack_utf8(right.clone().cast(&unified_type)?); diff --git a/src/optimizer/core/histogram.rs b/src/optimizer/core/histogram.rs index ae9c5bca..b74b36d7 100644 --- a/src/optimizer/core/histogram.rs +++ b/src/optimizer/core/histogram.rs @@ -255,7 +255,7 @@ impl Histogram { ) -> Result { let float_value = |value: &DataValue, prefix_len: usize| { let value = match value.logical_type() { - LogicalType::Varchar(_) => match value { + LogicalType::Varchar(_) | LogicalType::Char(_) => match value { DataValue::Utf8(value) => value.as_ref().map(|string| { if prefix_len > string.len() { return 0.0; diff --git a/src/planner/mod.rs b/src/planner/mod.rs index e90bf804..228c0c5c 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -104,6 +104,7 @@ impl LogicalPlan { Operator::Describe(_) => Arc::new(vec![ Arc::new(ColumnCatalog::new_dummy("FIELD".to_string())), Arc::new(ColumnCatalog::new_dummy("TYPE".to_string())), + Arc::new(ColumnCatalog::new_dummy("LEN".to_string())), Arc::new(ColumnCatalog::new_dummy("NULL".to_string())), Arc::new(ColumnCatalog::new_dummy("Key".to_string())), Arc::new(ColumnCatalog::new_dummy("DEFAULT".to_string())), diff --git a/src/storage/kip.rs b/src/storage/kip.rs index 38a7c213..323989ad 100644 --- a/src/storage/kip.rs +++ b/src/storage/kip.rs @@ -202,9 +202,10 @@ impl Transaction for KipTransaction { &mut self, table_name: &str, tuple: Tuple, + types: &[LogicalType], is_overwrite: bool, ) -> Result<(), DatabaseError> { - let (key, value) = TableCodec::encode_tuple(table_name, &tuple)?; + let (key, value) = TableCodec::encode_tuple(table_name, &tuple, types)?; if !is_overwrite && self.tx.get(&key)?.is_some() { return Err(DatabaseError::DuplicatePrimaryKey); @@ -611,6 +612,7 @@ mod test { Arc::new(DataValue::Boolean(Some(true))), ], }, + &[LogicalType::Integer, LogicalType::Boolean], false, )?; transaction.append( @@ -622,6 +624,7 @@ mod test { Arc::new(DataValue::Boolean(Some(false))), ], }, + &[LogicalType::Integer, LogicalType::Boolean], false, )?; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 3ba5d891..1cb9d938 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -9,7 +9,7 @@ use crate::storage::table_codec::TableCodec; use crate::types::index::{Index, IndexId, IndexMetaRef, IndexType}; use crate::types::tuple::{Tuple, TupleId}; use crate::types::value::{DataValue, ValueRef}; -use crate::types::ColumnId; +use crate::types::{ColumnId, LogicalType}; use kip_db::kernel::lsm::iterator::Iter as DBIter; use kip_db::kernel::lsm::mvcc; use std::collections::{Bound, VecDeque}; @@ -75,6 +75,7 @@ pub trait Transaction: Sync + Send + 'static { &mut self, table_name: &str, tuple: Tuple, + types: &[LogicalType], is_overwrite: bool, ) -> Result<(), DatabaseError>; diff --git a/src/storage/table_codec.rs b/src/storage/table_codec.rs index 977ecc29..1cc182ad 100644 --- a/src/storage/table_codec.rs +++ b/src/storage/table_codec.rs @@ -144,11 +144,15 @@ impl TableCodec { /// Key: {TableName}{TUPLE_TAG}{BOUND_MIN_TAG}{RowID}(Sorted) /// Value: Tuple - pub fn encode_tuple(table_name: &str, tuple: &Tuple) -> Result<(Bytes, Bytes), DatabaseError> { + pub fn encode_tuple( + table_name: &str, + tuple: &Tuple, + types: &[LogicalType], + ) -> Result<(Bytes, Bytes), DatabaseError> { let tuple_id = tuple.id.clone().ok_or(DatabaseError::PrimaryKeyNotFound)?; let key = Self::encode_tuple_key(table_name, &tuple_id)?; - Ok((Bytes::from(key), Bytes::from(tuple.serialize_to()))) + Ok((Bytes::from(key), Bytes::from(tuple.serialize_to(types)))) } pub fn encode_tuple_key( @@ -223,7 +227,7 @@ impl TableCodec { ) -> Result<(Bytes, Bytes), DatabaseError> { let key = TableCodec::encode_index_key(name, index, Some(tuple_id))?; - Ok((Bytes::from(key), Bytes::from(tuple_id.to_raw()))) + Ok((Bytes::from(key), Bytes::from(tuple_id.to_raw(None)))) } fn _encode_index_key(name: &str, index: &Index) -> Result, DatabaseError> { @@ -263,7 +267,7 @@ impl TableCodec { if let Some(tuple_id) = tuple_id { if matches!(index.ty, IndexType::Normal | IndexType::Composite) { - key_prefix.append(&mut tuple_id.to_raw()); + key_prefix.append(&mut tuple_id.to_raw(None)); } } Ok(key_prefix) @@ -381,7 +385,11 @@ mod tests { Arc::new(DataValue::Decimal(Some(Decimal::new(1, 0)))), ], }; - let (_, bytes) = TableCodec::encode_tuple(&table_catalog.name, &tuple)?; + let (_, bytes) = TableCodec::encode_tuple( + &table_catalog.name, + &tuple, + &[LogicalType::Integer, LogicalType::Decimal(None, None)], + )?; let schema = table_catalog.schema_ref(); assert_eq!( diff --git a/src/types/mod.rs b/src/types/mod.rs index 13e883ce..361a447c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -7,9 +7,10 @@ use chrono::{NaiveDate, NaiveDateTime}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::any::TypeId; +use std::cmp; use crate::errors::DatabaseError; -use sqlparser::ast::ExactNumberInfo; +use sqlparser::ast::{CharLengthUnits, CharacterLength, ExactNumberInfo}; use strum_macros::AsRefStr; pub type ColumnId = u32; @@ -33,6 +34,7 @@ pub enum LogicalType { UBigint, Float, Double, + Char(u32), Varchar(Option), Date, DateTime, @@ -80,7 +82,6 @@ impl LogicalType { pub fn raw_len(&self) -> Option { match self { - LogicalType::Invalid => Some(0), LogicalType::SqlNull => Some(0), LogicalType::Boolean => Some(1), LogicalType::Tinyint => Some(1), @@ -95,10 +96,11 @@ impl LogicalType { LogicalType::Double => Some(8), /// Note: The non-fixed length type's raw_len is None e.g. Varchar LogicalType::Varchar(_) => None, + LogicalType::Char(len) => Some(*len as usize), LogicalType::Decimal(_, _) => Some(16), LogicalType::Date => Some(4), LogicalType::DateTime => Some(8), - LogicalType::Tuple => unreachable!(), + LogicalType::Invalid | LogicalType::Tuple => unreachable!(), } } @@ -287,9 +289,16 @@ impl LogicalType { LogicalType::UBigint => matches!(to, LogicalType::Float | LogicalType::Double), LogicalType::Float => matches!(to, LogicalType::Double), LogicalType::Double => false, + LogicalType::Char(_) => false, LogicalType::Varchar(_) => false, - LogicalType::Date => matches!(to, LogicalType::DateTime | LogicalType::Varchar(_)), - LogicalType::DateTime => matches!(to, LogicalType::Date | LogicalType::Varchar(_)), + LogicalType::Date => matches!( + to, + LogicalType::DateTime | LogicalType::Varchar(_) | LogicalType::Char(_) + ), + LogicalType::DateTime => matches!( + to, + LogicalType::Date | LogicalType::Varchar(_) | LogicalType::Char(_) + ), LogicalType::Decimal(_, _) | LogicalType::Tuple => false, } } @@ -301,8 +310,34 @@ impl TryFrom for LogicalType { fn try_from(value: sqlparser::ast::DataType) -> Result { match value { - sqlparser::ast::DataType::Char(len) | sqlparser::ast::DataType::Varchar(len) => { - Ok(LogicalType::Varchar(len.map(|len| len.length as u32))) + sqlparser::ast::DataType::Char(char_len) + | sqlparser::ast::DataType::Character(char_len) => { + let mut len = 1; + if let Some(CharacterLength { length, unit }) = char_len { + if matches!(unit, Some(CharLengthUnits::Octets)) { + return Err(DatabaseError::UnsupportedStmt(format!( + "char unit: {:?}", + unit + ))); + } + len = cmp::max(len, length) + } + Ok(LogicalType::Char(len as u32)) + } + sqlparser::ast::DataType::CharVarying(varchar_len) + | sqlparser::ast::DataType::CharacterVarying(varchar_len) + | sqlparser::ast::DataType::Varchar(varchar_len) => { + let mut len = None; + if let Some(CharacterLength { length, unit }) = varchar_len { + if matches!(unit, Some(CharLengthUnits::Octets)) { + return Err(DatabaseError::UnsupportedStmt(format!( + "char unit: {:?}", + unit + ))); + } + len = Some(length as u32) + } + Ok(LogicalType::Varchar(len)) } sqlparser::ast::DataType::Float(_) => Ok(LogicalType::Float), sqlparser::ast::DataType::Double | sqlparser::ast::DataType::DoublePrecision => { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index b4acbd52..ff484a22 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -12,6 +12,10 @@ pub type TupleId = ValueRef; pub type Schema = Vec; pub type SchemaRef = Arc; +pub fn types(schema: &Schema) -> Vec { + schema.iter().map(|column| *column.datatype()).collect_vec() +} + #[derive(Clone, Debug, PartialEq)] pub struct Tuple { pub id: Option, @@ -94,7 +98,9 @@ impl Tuple { /// e.g.: bits(u8)..|data_0(len for utf8_1)|utf8_0|data_1| /// Tips: all len is u32 - pub fn serialize_to(&self) -> Vec { + pub fn serialize_to(&self, types: &[LogicalType]) -> Vec { + assert_eq!(self.values.len(), types.len()); + fn flip_bit(bits: u8, i: usize) -> u8 { bits | (1 << (7 - i)) } @@ -107,9 +113,10 @@ impl Tuple { if value.is_null() { bytes[i / BITS_MAX_INDEX] = flip_bit(bytes[i / BITS_MAX_INDEX], i % BITS_MAX_INDEX); } else { - let mut value_bytes = value.to_raw(); + let logical_type = types[i]; + let mut value_bytes = value.to_raw(Some(logical_type)); - if value.is_variable() { + if logical_type.raw_len().is_none() { bytes.append(&mut (value_bytes.len() as u32).encode_fixed_vec()); } bytes.append(&mut value_bytes); @@ -226,6 +233,11 @@ mod tests { false, ColumnDesc::new(LogicalType::Decimal(None, None), false, false, None), )), + Arc::new(ColumnCatalog::new( + "c14".to_string(), + false, + ColumnDesc::new(LogicalType::Char(1), false, false, None), + )), ]); let tuples = vec![ @@ -245,6 +257,7 @@ mod tests { Arc::new(DataValue::Date64(Some(0))), Arc::new(DataValue::Date32(Some(0))), Arc::new(DataValue::Decimal(Some(Decimal::new(0, 3)))), + Arc::new(DataValue::Utf8(Some("K".to_string()))), ], }, Tuple { @@ -263,6 +276,7 @@ mod tests { Arc::new(DataValue::Date64(None)), Arc::new(DataValue::Date32(None)), Arc::new(DataValue::Decimal(None)), + Arc::new(DataValue::Utf8(None)), ], }, ]; @@ -274,15 +288,15 @@ mod tests { let tuple_0 = Tuple::deserialize_from( &types, - &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], &columns, - &tuples[0].serialize_to(), + &tuples[0].serialize_to(&types), ); let tuple_1 = Tuple::deserialize_from( &types, - &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], &columns, - &tuples[1].serialize_to(), + &tuples[1].serialize_to(&types), ); assert_eq!(tuples[0], tuple_0); diff --git a/src/types/value.rs b/src/types/value.rs index fb09aea5..dc2e704f 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -310,10 +310,6 @@ impl DataValue { value.and_then(|v| Self::date_time_format(v).map(|fmt| format!("{}", fmt))) } - pub fn is_variable(&self) -> bool { - matches!(self, DataValue::Utf8(_)) - } - pub fn is_null(&self) -> bool { match self { DataValue::Null => true, @@ -351,7 +347,7 @@ impl DataValue { LogicalType::UBigint => DataValue::UInt64(None), LogicalType::Float => DataValue::Float32(None), LogicalType::Double => DataValue::Float64(None), - LogicalType::Varchar(_) => DataValue::Utf8(None), + LogicalType::Char(_) | LogicalType::Varchar(_) => DataValue::Utf8(None), LogicalType::Date => DataValue::Date32(None), LogicalType::DateTime => DataValue::Date64(None), LogicalType::Decimal(_, _) => DataValue::Decimal(None), @@ -374,7 +370,7 @@ impl DataValue { LogicalType::UBigint => DataValue::UInt64(Some(0)), LogicalType::Float => DataValue::Float32(Some(0.0)), LogicalType::Double => DataValue::Float64(Some(0.0)), - LogicalType::Varchar(_) => DataValue::Utf8(Some("".to_string())), + LogicalType::Char(_) | LogicalType::Varchar(_) => DataValue::Utf8(Some("".to_string())), LogicalType::Date => DataValue::Date32(Some(UNIX_DATETIME.num_days_from_ce())), LogicalType::DateTime => DataValue::Date64(Some(UNIX_DATETIME.timestamp())), LogicalType::Decimal(_, _) => DataValue::Decimal(Some(Decimal::new(0, 0))), @@ -382,7 +378,7 @@ impl DataValue { } } - pub fn to_raw(&self) -> Vec { + pub fn to_raw(&self, logical_type: Option) -> Vec { match self { DataValue::Null => None, DataValue::Boolean(v) => v.map(|v| vec![v as u8]), @@ -396,7 +392,12 @@ impl DataValue { DataValue::UInt16(v) => v.map(|v| v.encode_fixed_vec()), DataValue::UInt32(v) => v.map(|v| v.encode_fixed_vec()), DataValue::UInt64(v) => v.map(|v| v.encode_fixed_vec()), - DataValue::Utf8(v) => v.clone().map(|v| v.into_bytes()), + DataValue::Utf8(v) => v.clone().map(|mut v| { + if let Some(LogicalType::Char(len)) = logical_type { + v = format!("{:len$}", v, len = len as usize); + } + v.into_bytes() + }), DataValue::Date32(v) => v.map(|v| v.encode_fixed_vec()), DataValue::Date64(v) => v.map(|v| v.encode_fixed_vec()), DataValue::Decimal(v) => v.map(|v| v.serialize().to_vec()), @@ -444,6 +445,17 @@ impl DataValue { buf.copy_from_slice(bytes); f64::from_ne_bytes(buf) })), + LogicalType::Char(_) => { + // https://dev.mysql.com/doc/refman/8.0/en/char.html#:~:text=If%20a%20given%20value%20is%20stored%20into%20the%20CHAR(4)%20and%20VARCHAR(4)%20columns%2C%20the%20values%20retrieved%20from%20the%20columns%20are%20not%20always%20the%20same%20because%20trailing%20spaces%20are%20removed%20from%20CHAR%20columns%20upon%20retrieval.%20The%20following%20example%20illustrates%20this%20difference%3A + let value = (!bytes.is_empty()).then(|| { + let last_non_zero_index = match bytes.iter().rposition(|&x| x != b' ') { + Some(index) => index + 1, + None => 0, + }; + String::from_utf8(bytes[0..last_non_zero_index].to_owned()).unwrap() + }); + DataValue::Utf8(value) + } LogicalType::Varchar(_) => DataValue::Utf8( (!bytes.is_empty()).then(|| String::from_utf8(bytes.to_owned()).unwrap()), ), @@ -613,7 +625,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(None)), LogicalType::Float => Ok(DataValue::Float32(None)), LogicalType::Double => Ok(DataValue::Float64(None)), - LogicalType::Varchar(_) => Ok(DataValue::Utf8(None)), + LogicalType::Char(_) | LogicalType::Varchar(_) => Ok(DataValue::Utf8(None)), LogicalType::Date => Ok(DataValue::Date32(None)), LogicalType::DateTime => Ok(DataValue::Date64(None)), LogicalType::Decimal(_, _) => Ok(DataValue::Decimal(None)), @@ -632,6 +644,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v.into()))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), _ => Err(DatabaseError::CastFail), }, @@ -639,6 +652,7 @@ impl DataValue { LogicalType::SqlNull => Ok(DataValue::Null), LogicalType::Float => Ok(DataValue::Float32(value)), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal( value @@ -657,6 +671,7 @@ impl DataValue { LogicalType::SqlNull => Ok(DataValue::Null), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v as f32))), LogicalType::Double => Ok(DataValue::Float64(value)), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal( value @@ -689,6 +704,7 @@ impl DataValue { LogicalType::Bigint => Ok(DataValue::Int64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v.into()))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -716,6 +732,7 @@ impl DataValue { LogicalType::Bigint => Ok(DataValue::Int64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v.into()))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -742,6 +759,7 @@ impl DataValue { LogicalType::Bigint => Ok(DataValue::Int64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v as f32))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -767,6 +785,7 @@ impl DataValue { LogicalType::Bigint => Ok(DataValue::Int64(value)), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v as f32))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v as f64))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -788,6 +807,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v.into()))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -807,6 +827,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v.into()))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -824,6 +845,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(value.map(|v| v.into()))), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v as f32))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v.into()))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -839,6 +861,7 @@ impl DataValue { LogicalType::UBigint => Ok(DataValue::UInt64(value)), LogicalType::Float => Ok(DataValue::Float32(value.map(|v| v as f32))), LogicalType::Double => Ok(DataValue::Float64(value.map(|v| v as f64))), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Decimal(_, option) => Ok(DataValue::Decimal(value.map(|v| { let mut decimal = Decimal::from(v); @@ -885,6 +908,7 @@ impl DataValue { LogicalType::Double => Ok(DataValue::Float64( value.map(|v| f64::from_str(&v)).transpose()?, )), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), LogicalType::Date => { let option = value @@ -917,6 +941,7 @@ impl DataValue { }, DataValue::Date32(value) => match to { LogicalType::SqlNull => Ok(DataValue::Null), + LogicalType::Char(len) => varchar_cast!(Self::format_date(value), Some(len)), LogicalType::Varchar(len) => varchar_cast!(Self::format_date(value), len), LogicalType::Date => Ok(DataValue::Date32(value)), LogicalType::DateTime => { @@ -932,6 +957,9 @@ impl DataValue { }, DataValue::Date64(value) => match to { LogicalType::SqlNull => Ok(DataValue::Null), + LogicalType::Char(len) => { + varchar_cast!(Self::format_datetime(value), Some(len)) + } LogicalType::Varchar(len) => varchar_cast!(Self::format_datetime(value), len), LogicalType::Date => { let option = value.and_then(|v| { @@ -949,6 +977,7 @@ impl DataValue { LogicalType::Float => Ok(DataValue::Float32(value.and_then(|v| v.to_f32()))), LogicalType::Double => Ok(DataValue::Float64(value.and_then(|v| v.to_f64()))), LogicalType::Decimal(_, _) => Ok(DataValue::Decimal(value)), + LogicalType::Char(len) => varchar_cast!(value, Some(len)), LogicalType::Varchar(len) => varchar_cast!(value, len), _ => Err(DatabaseError::CastFail), }, diff --git a/tests/slt/describe.slt b/tests/slt/describe.slt index 9541eaa4..c5137ff3 100644 --- a/tests/slt/describe.slt +++ b/tests/slt/describe.slt @@ -4,9 +4,9 @@ create table t9 (c1 int primary key, c2 int default 0, c3 varchar unique); query TTTTI describe t9; ---- -c1 INTEGER false PRIMARY null -c2 INTEGER true EMPTY 0 -c3 VARCHAR true UNIQUE null +c1 INTEGER 4 false PRIMARY null +c2 INTEGER 4 true EMPTY 0 +c3 VARCHAR DYNAMIC true UNIQUE null statement ok drop table t9; \ No newline at end of file diff --git a/tests/slt/dummy.slt b/tests/slt/dummy.slt index febf308d..0a28fc47 100644 --- a/tests/slt/dummy.slt +++ b/tests/slt/dummy.slt @@ -36,6 +36,16 @@ SELECT (0, 1, 2) >= (0, 1) ---- true +query B +SELECT 1::CHAR = 1::VARCHAR +---- +true + +query B +SELECT 1::CHAR = 2::VARCHAR +---- +false + query B SELECT NULL=NULL ---- diff --git a/tests/slt/insert.slt b/tests/slt/insert.slt index 6a1d529c..b38e252e 100644 --- a/tests/slt/insert.slt +++ b/tests/slt/insert.slt @@ -44,6 +44,9 @@ select * from t 7 10 1 null 8 null null null +statement ok +drop table t; + statement ok create table t1(id int primary key, v1 bigint default 233) @@ -73,4 +76,28 @@ select * from t1 2 233 3 233 4 0 -5 233 \ No newline at end of file +5 233 + +statement ok +drop table t1; + +statement ok +create table t2(id int primary key, v1 char(10), v2 varchar); + +statement ok +insert into t2 (id, v1, v2) values (0, 'foo', 'foo'); + +query ITT +select * from t2; +---- +0 foo foo + +query B +select v1 = v2 from t2; +---- +true + +statement ok +drop table t2; + + diff --git a/tests/slt/sql_2016/E021_01.slt b/tests/slt/sql_2016/E021_01.slt index 2d0f908e..ffe33d48 100644 --- a/tests/slt/sql_2016/E021_01.slt +++ b/tests/slt/sql_2016/E021_01.slt @@ -1,31 +1,27 @@ # E021-01: CHARACTER data type (including all its spellings) -# TODO: CHAR - -# statement ok -# CREATE TABLE TABLE_E021_01_01_01 ( A CHAR ( 8 ) ) - -# statement ok -# CREATE TABLE TABLE_E021_01_01_02 ( A CHAR ( 8 CHARACTERS ) ) +statement ok +CREATE TABLE TABLE_E021_01_01_01 ( ID INT PRIMARY KEY, A CHAR ( 8 ) ) +statement ok +CREATE TABLE TABLE_E021_01_01_02 ( ID INT PRIMARY KEY, A CHAR ( 8 CHARACTERS ) ) +# TODO: char unit: OCTETS # statement ok -# CREATE TABLE TABLE_E021_01_01_03 ( A CHAR ( 8 OCTETS ) ) +# CREATE TABLE TABLE_E021_01_01_03 ( ID INT PRIMARY KEY, A CHAR ( 8 OCTETS ) ) +statement ok +CREATE TABLE TABLE_E021_01_01_04 ( ID INT PRIMARY KEY, A CHAR ) -# statement ok -# CREATE TABLE TABLE_E021_01_01_04 ( A CHAR ) +statement ok +CREATE TABLE TABLE_E021_01_01_05 ( ID INT PRIMARY KEY, A CHARACTER ( 8 ) ) -# statement ok -# CREATE TABLE TABLE_E021_01_01_05 ( A CHARACTER ( 8 ) ) +statement ok +CREATE TABLE TABLE_E021_01_01_06 ( ID INT PRIMARY KEY, A CHARACTER ( 8 CHARACTERS ) ) +# TODO: char unit: OCTETS # statement ok -# CREATE TABLE TABLE_E021_01_01_06 ( A CHARACTER ( 8 CHARACTERS ) ) +# CREATE TABLE TABLE_E021_01_01_07 ( ID INT PRIMARY KEY, A CHARACTER ( 8 OCTETS ) ) - -# statement ok -# CREATE TABLE TABLE_E021_01_01_07 ( A CHARACTER ( 8 OCTETS ) ) - - -# statement ok -# CREATE TABLE TABLE_E021_01_01_08 ( A CHARACTER ) +statement ok +CREATE TABLE TABLE_E021_01_01_08 ( ID INT PRIMARY KEY, A CHARACTER ) diff --git a/tests/slt/sql_2016/E021_02.slt b/tests/slt/sql_2016/E021_02.slt index 6d80b5dc..45045a94 100644 --- a/tests/slt/sql_2016/E021_02.slt +++ b/tests/slt/sql_2016/E021_02.slt @@ -1,47 +1,40 @@ # E021-02: CHARACTER VARYING data type (including all its spellings) -# TODO: CHAR VARYING - -# statement ok -# CREATE TABLE TABLE_E021_02_01_01 ( A CHAR VARING ( 8 ) ) - - -# statement ok -# CREATE TABLE TABLE_E021_02_01_02 ( A CHAR VARING ( 8 CHARACTERS ) ) - - -# statement ok -# CREATE TABLE TABLE_E021_02_01_03 ( A CHAR VARING ( 8 OCTETS ) ) +statement ok +CREATE TABLE TABLE_E021_02_01_01 ( ID INT PRIMARY KEY, A CHAR VARYING ( 8 ) ) +statement ok +CREATE TABLE TABLE_E021_02_01_02 ( ID INT PRIMARY KEY, A CHAR VARYING ( 8 CHARACTERS ) ) +# TODO: char unit: OCTETS # statement ok -# CREATE TABLE TABLE_E021_02_01_04 ( A CHAR VARING ) - +# CREATE TABLE TABLE_E021_02_01_03 ( ID INT PRIMARY KEY, A CHAR VARYING ( 8 OCTETS ) ) -# statement ok -# CREATE TABLE TABLE_E021_02_01_05 ( A CHARACTER VARYING ( 8 ) ) +statement ok +CREATE TABLE TABLE_E021_02_01_04 ( ID INT PRIMARY KEY, A CHAR VARYING ) -# statement ok -# CREATE TABLE TABLE_E021_02_01_06 ( A CHARACTER VARYING ( 8 CHARACTERS ) ) +statement ok +CREATE TABLE TABLE_E021_02_01_05 ( ID INT PRIMARY KEY, A CHARACTER VARYING ( 8 ) ) +statement ok +CREATE TABLE TABLE_E021_02_01_06 ( ID INT PRIMARY KEY, A CHARACTER VARYING ( 8 CHARACTERS ) ) +# TODO: char unit: OCTETS # statement ok -# CREATE TABLE TABLE_E021_02_01_07 ( A CHARACTER VARYING ( 8 OCTETS ) ) - +# CREATE TABLE TABLE_E021_02_01_07 ( ID INT PRIMARY KEY, A CHARACTER VARYING ( 8 OCTETS ) ) -# statement ok -# CREATE TABLE TABLE_E021_02_01_08 ( A CHARACTER VARYING ) +statement ok +CREATE TABLE TABLE_E021_02_01_08 ( ID INT PRIMARY KEY, A CHARACTER VARYING ) statement ok CREATE TABLE TABLE_E021_02_01_09 ( ID INT PRIMARY KEY, A VARCHAR ( 8 ) ) -# statement ok -# CREATE TABLE TABLE_E021_02_01_10 ( A VARCHAR ( 8 CHARACTERS ) ) - +statement ok +CREATE TABLE TABLE_E021_02_01_10 ( ID INT PRIMARY KEY, A VARCHAR ( 8 CHARACTERS ) ) +# TODO: char unit: OCTETS # statement ok -# CREATE TABLE TABLE_E021_02_01_11 ( A VARCHAR ( 8 OCTETS ) ) - +# CREATE TABLE TABLE_E021_02_01_11 ( ID INT PRIMARY KEY, A VARCHAR ( 8 OCTETS ) ) statement ok CREATE TABLE TABLE_E021_02_01_12 ( ID INT PRIMARY KEY, A VARCHAR ) diff --git a/tests/slt/sql_2016/E021_10.slt b/tests/slt/sql_2016/E021_10.slt index 7eb06679..a22291ca 100644 --- a/tests/slt/sql_2016/E021_10.slt +++ b/tests/slt/sql_2016/E021_10.slt @@ -1,12 +1,12 @@ # E021-10: Implicit casting among the fixed-length and variable-length character string types -# TODO: CHARACTER and CHARACTER VARYING +statement ok +CREATE TABLE TABLE_E021_10_01_01 ( ID INT PRIMARY KEY, A CHARACTER ( 10 ) , B CHARACTER VARYING ( 15 ) ); -# statement ok -# CREATE TABLE TABLE_E021_10_01_01 ( A CHARACTER ( 10 ) , B CHARACTER VARYING ( 15 ) ); +statement ok +INSERT INTO TABLE_E021_10_01_01 ( ID, A, B ) VALUES ( 0, 'foo' , 'bar' ); -# statement ok -# INSERT INTO TABLE_E021_10_01_01 ( A, B ) VALUES ( 'foo' , 'bar' ); - -# query B -# SELECT A = B FROM TABLE_E021_10_01_01 +query B +SELECT A = B FROM TABLE_E021_10_01_01 +---- +false