use std::borrow::Cow;
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub enum Value {
#[default]
Null,
Bool(bool),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F32(f32),
F64(f64),
Decimal(String),
String(String),
Bytes(Vec<u8>),
Uuid(String),
Date(String),
DateTime(String),
Time(String),
Json(String),
Array(Vec<Value>),
Object(std::collections::HashMap<String, Value>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub fn is_i64(&self) -> bool {
matches!(self, Value::I64(_))
}
pub fn is_f64(&self) -> bool {
matches!(self, Value::F64(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_bytes(&self) -> bool {
matches!(self, Value::Bytes(_))
}
pub fn is_object(&self) -> bool {
matches!(self, Value::Object(_))
}
pub fn from_map(map: std::collections::HashMap<String, Value>) -> Self {
Value::Object(map)
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
Value::Decimal(s) => Some(s),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::I8(v) => Some(*v as i64),
Value::I16(v) => Some(*v as i64),
Value::I32(v) => Some(*v as i64),
Value::I64(v) => Some(*v),
Value::U8(v) => Some(*v as i64),
Value::U16(v) => Some(*v as i64),
Value::U32(v) => Some(*v as i64),
Value::U64(v) => i64::try_from(*v).ok(),
Value::F32(v) => Some(*v as i64),
Value::F64(v) => Some(*v as i64),
Value::Bool(v) => Some(if *v { 1 } else { 0 }),
Value::String(s) => s.parse::<i64>().ok(),
Value::Decimal(s) => s.parse::<i64>().ok(),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::F32(v) => Some(*v as f64),
Value::F64(v) => Some(*v),
Value::I8(v) => Some(*v as f64),
Value::I16(v) => Some(*v as f64),
Value::I32(v) => Some(*v as f64),
Value::I64(v) => Some(*v as f64),
Value::U8(v) => Some(*v as f64),
Value::U16(v) => Some(*v as f64),
Value::U32(v) => Some(*v as f64),
Value::U64(v) => Some(*v as f64),
Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
Value::Decimal(s) => s.parse::<f64>().ok(),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(v) => Some(*v),
Value::I8(v) => Some(*v != 0),
Value::I16(v) => Some(*v != 0),
Value::I32(v) => Some(*v != 0),
Value::I64(v) => Some(*v != 0),
Value::U8(v) => Some(*v != 0),
Value::U16(v) => Some(*v != 0),
Value::U32(v) => Some(*v != 0),
Value::U64(v) => Some(*v != 0),
Value::F32(v) => Some(*v != 0.0),
Value::F64(v) => Some(*v != 0.0),
Value::String(s) => match s.to_lowercase().as_str() {
"1" | "true" | "yes" | "on" => Some(true),
"0" | "false" | "no" | "off" => Some(false),
_ => None,
},
Value::Null => Some(false),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Value::Bytes(v) => Some(v),
Value::String(s) => Some(s.as_bytes()),
_ => None,
}
}
pub fn to_param(&self) -> Cow<'_, str> {
match self {
Value::Null => Cow::Borrowed("NULL"),
Value::Bool(b) => Cow::Owned(if *b { "TRUE" } else { "FALSE" }.to_string()),
Value::I8(v) => Cow::Owned(v.to_string()),
Value::I16(v) => Cow::Owned(v.to_string()),
Value::I32(v) => Cow::Owned(v.to_string()),
Value::I64(v) => Cow::Owned(v.to_string()),
Value::U8(v) => Cow::Owned(v.to_string()),
Value::U16(v) => Cow::Owned(v.to_string()),
Value::U32(v) => Cow::Owned(v.to_string()),
Value::U64(v) => Cow::Owned(v.to_string()),
Value::F32(v) => Cow::Owned(v.to_string()),
Value::F64(v) => Cow::Owned(v.to_string()),
Value::Decimal(s) => Cow::Owned(s.clone()),
Value::String(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::Bytes(b) => Cow::Owned(format!("X'{}'", hex_encode(b))),
Value::Uuid(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::Date(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::DateTime(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::Time(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::Json(s) => Cow::Owned(format!("'{}'", escape_string(s))),
Value::Array(arr) => {
let params: Vec<String> = arr.iter().map(|v| v.to_param().into_owned()).collect();
Cow::Owned(format!("({})", params.join(", ")))
}
Value::Object(_) => Cow::Borrowed("NULL"),
}
}
pub fn to_param_with_dialect(&self, dialect: &dyn crate::dialect::Dialect) -> Cow<'_, str> {
match self {
Value::Null => Cow::Borrowed("NULL"),
Value::Bool(b) => Cow::Owned(if *b { "TRUE" } else { "FALSE" }.to_string()),
Value::I8(v) => Cow::Owned(v.to_string()),
Value::I16(v) => Cow::Owned(v.to_string()),
Value::I32(v) => Cow::Owned(v.to_string()),
Value::I64(v) => Cow::Owned(v.to_string()),
Value::U8(v) => Cow::Owned(v.to_string()),
Value::U16(v) => Cow::Owned(v.to_string()),
Value::U32(v) => Cow::Owned(v.to_string()),
Value::U64(v) => Cow::Owned(v.to_string()),
Value::F32(v) => Cow::Owned(v.to_string()),
Value::F64(v) => Cow::Owned(v.to_string()),
Value::Decimal(s) => Cow::Owned(s.clone()),
Value::String(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::Bytes(b) => Cow::Owned(format!("X'{}'", hex_encode(b))),
Value::Uuid(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::Date(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::DateTime(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::Time(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::Json(s) => Cow::Owned(format!("'{}'", dialect.escape_string(s))),
Value::Array(arr) => {
let params: Vec<String> = arr
.iter()
.map(|v| v.to_param_with_dialect(dialect).into_owned())
.collect();
Cow::Owned(format!("({})", params.join(", ")))
}
Value::Object(_) => Cow::Borrowed("NULL"),
}
}
pub fn from<T: Into<Value>>(v: T) -> Self {
v.into()
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "NULL"),
Value::Bool(b) => write!(f, "{}", b),
Value::I8(v) => write!(f, "{}", v),
Value::I16(v) => write!(f, "{}", v),
Value::I32(v) => write!(f, "{}", v),
Value::I64(v) => write!(f, "{}", v),
Value::U8(v) => write!(f, "{}", v),
Value::U16(v) => write!(f, "{}", v),
Value::U32(v) => write!(f, "{}", v),
Value::U64(v) => write!(f, "{}", v),
Value::F32(v) => write!(f, "{}", v),
Value::F64(v) => write!(f, "{}", v),
Value::Decimal(v) => write!(f, "{}", v),
Value::String(v) => write!(f, "'{}'", v),
Value::Bytes(v) => write!(f, "X'{}'", hex_encode(v)),
Value::Uuid(v) => write!(f, "'{}'", v),
Value::Date(v) => write!(f, "'{}'", v),
Value::DateTime(v) => write!(f, "'{}'", v),
Value::Time(v) => write!(f, "'{}'", v),
Value::Json(v) => write!(f, "'{}'", v),
Value::Array(v) => {
let items: Vec<String> = v.iter().map(|i| format!("{}", i)).collect();
write!(f, "({})", items.join(", "))
}
Value::Object(map) => {
let items: Vec<String> = map.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
write!(f, "{{{}}}", items.join(", "))
}
}
}
}
impl From<()> for Value {
fn from(_: ()) -> Self {
Value::Null
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Bool(v)
}
}
impl From<i8> for Value {
fn from(v: i8) -> Self {
Value::I8(v)
}
}
impl From<i16> for Value {
fn from(v: i16) -> Self {
Value::I16(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::I32(v)
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::I64(v)
}
}
impl From<u8> for Value {
fn from(v: u8) -> Self {
Value::U8(v)
}
}
impl From<u16> for Value {
fn from(v: u16) -> Self {
Value::U16(v)
}
}
impl From<u32> for Value {
fn from(v: u32) -> Self {
Value::U32(v)
}
}
impl From<u64> for Value {
fn from(v: u64) -> Self {
Value::U64(v)
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::F32(v)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::F64(v)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::String(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::String(v.to_string())
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value::Bytes(v)
}
}
impl From<&[u8]> for Value {
fn from(v: &[u8]) -> Self {
Value::Bytes(v.to_vec())
}
}
impl From<Vec<Value>> for Value {
fn from(v: Vec<Value>) -> Self {
Value::Array(v)
}
}
fn escape_string(s: &str) -> String {
let mut escaped = String::with_capacity(s.len() + s.chars().filter(|&c| c == '\'').count());
for c in s.chars() {
if c == '\'' {
escaped.push_str("''");
} else {
escaped.push(c);
}
}
escaped
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ColType {
Bool,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
Decimal,
String,
Bytes,
Date,
DateTime,
Time,
Json,
Uuid,
Unknown,
}
impl ColType {
pub fn from_type_name(type_name: &str) -> Self {
match type_name {
"BOOLEAN" | "BOOL" => Self::Bool,
"TINYINT" => Self::I8,
"SMALLINT" | "INT2" => Self::I16,
"INT" | "INT4" | "OID" | "MEDIUMINT" | "INTEGER" => Self::I32,
"BIGINT" | "INT8" => Self::I64,
"TINYINT UNSIGNED" => Self::U8,
"SMALLINT UNSIGNED" => Self::U16,
"INT UNSIGNED" | "MEDIUMINT UNSIGNED" => Self::U32,
"BIGINT UNSIGNED" => Self::U64,
"FLOAT" | "FLOAT4" | "REAL" => Self::F32,
"DOUBLE" | "FLOAT8" => Self::F64,
"DECIMAL" | "NUMERIC" | "NEWDECIMAL" | "MONEY" => Self::Decimal,
"TEXT" | "VARCHAR" | "CHAR" | "NAME" => Self::String,
"BLOB" | "BYTEA" => Self::Bytes,
"DATE" => Self::Date,
"DATETIME" | "TIMESTAMP" => Self::DateTime,
"TIME" => Self::Time,
"JSON" => Self::Json,
"UUID" => Self::Uuid,
_ => Self::Unknown,
}
}
pub fn parse_sqlite(type_name: &str) -> Self {
if type_name.is_empty() {
return Self::Unknown;
}
match type_name.to_uppercase().as_str() {
"INTEGER" | "INT" | "BIGINT" | "INT8" | "INT4" | "INT2" | "TINYINT" | "SMALLINT"
| "MEDIUMINT" => Self::I64,
"BOOLEAN" | "BOOL" => Self::Bool,
"REAL" | "FLOAT" | "DOUBLE" | "FLOAT8" | "DOUBLE PRECISION" => Self::F64,
"DECIMAL" | "NUMERIC" => Self::Decimal,
"TEXT" | "CLOB" | "VARCHAR" | "CHAR" | "NAME" => Self::String,
"BLOB" => Self::Bytes,
"DATE" => Self::Date,
"DATETIME" | "TIMESTAMP" => Self::DateTime,
"TIME" => Self::Time,
"JSON" => Self::Json,
_ => Self::Unknown,
}
}
pub fn parse_mysql(type_name: &str) -> Self {
match type_name.to_uppercase().as_str() {
"TINYINT" => Self::I8,
"SMALLINT" => Self::I16,
"INT" | "INTEGER" | "MEDIUMINT" => Self::I32,
"BIGINT" => Self::I64,
"TINYINT UNSIGNED" => Self::U8,
"SMALLINT UNSIGNED" => Self::U16,
"INT UNSIGNED" | "MEDIUMINT UNSIGNED" => Self::U32,
"BIGINT UNSIGNED" => Self::U64,
"FLOAT" => Self::F32,
"DOUBLE" => Self::F64,
"DECIMAL" | "NUMERIC" | "NEWDECIMAL" => Self::Decimal,
"VARCHAR" | "CHAR" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM"
| "SET" => Self::String,
"BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB" | "BINARY" | "VARBINARY" => Self::Bytes,
"DATE" => Self::Date,
"DATETIME" | "TIMESTAMP" => Self::DateTime,
"TIME" => Self::Time,
"YEAR" => Self::I16,
"JSON" => Self::Json,
"BOOLEAN" | "BOOL" => Self::Bool,
_ => Self::from_type_name(type_name),
}
}
pub fn parse_postgres(type_name: &str) -> Self {
match type_name.to_uppercase().as_str() {
"BOOL" => Self::Bool,
"INT2" | "SMALLINT" => Self::I16,
"INT4" | "INTEGER" | "INT" => Self::I32,
"INT8" | "BIGINT" => Self::I64,
"FLOAT4" | "REAL" => Self::F32,
"FLOAT8" | "DOUBLE PRECISION" => Self::F64,
"NUMERIC" | "DECIMAL" | "MONEY" => Self::Decimal,
"TEXT" | "VARCHAR" | "CHAR" | "BPCHAR" | "NAME" | "CITEXT" => Self::String,
"BYTEA" => Self::Bytes,
"DATE" => Self::Date,
"TIMESTAMP" | "TIMESTAMPTZ" => Self::DateTime,
"TIME" | "TIMETZ" => Self::Time,
"JSON" | "JSONB" => Self::Json,
"UUID" => Self::Uuid,
"OID" => Self::I32,
_ => Self::from_type_name(type_name),
}
}
}
pub type QueryValues = (Vec<String>, Vec<Vec<Value>>);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_is_null() {
assert!(Value::Null.is_null());
assert!(!Value::I64(0).is_null());
}
#[test]
fn test_col_type_from_type_name() {
assert_eq!(ColType::from_type_name("BOOLEAN"), ColType::Bool);
assert_eq!(ColType::from_type_name("TINYINT"), ColType::I8);
assert_eq!(ColType::from_type_name("SMALLINT"), ColType::I16);
assert_eq!(ColType::from_type_name("INT"), ColType::I32);
assert_eq!(ColType::from_type_name("BIGINT"), ColType::I64);
assert_eq!(ColType::from_type_name("INT UNSIGNED"), ColType::U32);
assert_eq!(ColType::from_type_name("FLOAT"), ColType::F32);
assert_eq!(ColType::from_type_name("DOUBLE"), ColType::F64);
assert_eq!(ColType::from_type_name("TEXT"), ColType::String);
assert_eq!(ColType::from_type_name("BLOB"), ColType::Bytes);
assert_eq!(ColType::from_type_name("DATE"), ColType::Date);
assert_eq!(ColType::from_type_name("TIMESTAMP"), ColType::DateTime);
assert_eq!(ColType::from_type_name("JSON"), ColType::Json);
assert_eq!(ColType::from_type_name("INT2"), ColType::I16);
assert_eq!(ColType::from_type_name("INT4"), ColType::I32);
assert_eq!(ColType::from_type_name("INT8"), ColType::I64);
assert_eq!(ColType::from_type_name("FLOAT4"), ColType::F32);
assert_eq!(ColType::from_type_name("FLOAT8"), ColType::F64);
assert_eq!(ColType::from_type_name("BYTEA"), ColType::Bytes);
assert_eq!(ColType::from_type_name("UNKNOWN_TYPE"), ColType::Unknown);
assert_eq!(ColType::from_type_name(""), ColType::Unknown);
}
#[test]
fn test_value_as_i64() {
assert_eq!(Value::I64(42).as_i64(), Some(42));
assert_eq!(Value::I32(42).as_i64(), Some(42));
assert_eq!(Value::Bool(true).as_i64(), Some(1));
assert!(Value::String("test".to_string()).as_i64().is_none());
}
#[test]
fn test_value_as_f64() {
assert_eq!(Value::F64(2.5).as_f64(), Some(2.5));
assert_eq!(Value::I64(42).as_f64(), Some(42.0));
}
#[test]
fn test_value_as_str() {
assert_eq!(Value::String("hello".to_string()).as_str(), Some("hello"));
}
#[test]
fn test_value_to_param() {
assert_eq!(Value::Null.to_param(), "NULL");
assert_eq!(Value::Bool(true).to_param(), "TRUE");
assert_eq!(Value::I64(42).to_param(), "42");
assert_eq!(Value::String("test".to_string()).to_param(), "'test'");
assert_eq!(Value::String("it's".to_string()).to_param(), "'it''s'");
}
#[test]
fn test_value_into() {
let v: Value = 42i64.into();
assert_eq!(v, Value::I64(42));
let v: Value = "hello".into();
assert_eq!(v, Value::String("hello".to_string()));
let arr: Vec<Value> = vec![Value::I64(1), Value::I64(2)];
let v: Value = arr.into();
assert_eq!(v, Value::Array(vec![Value::I64(1), Value::I64(2)]));
}
#[test]
fn test_value_display() {
assert_eq!(format!("{}", Value::Null), "NULL");
assert_eq!(format!("{}", Value::Bool(true)), "true");
assert_eq!(format!("{}", Value::I64(42)), "42");
assert_eq!(format!("{}", Value::String("test".to_string())), "'test'");
}
}