use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum PartiQLStatement {
Select(SelectStatement),
Insert(InsertStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectStatement {
pub table_name: String,
pub index_name: Option<String>,
pub select_list: SelectList,
pub where_clause: Option<WhereClause>,
pub order_by: Option<OrderBy>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectList {
All,
Attributes(Vec<String>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhereClause {
pub conditions: Vec<Condition>,
}
impl WhereClause {
pub fn get_condition(&self, attr_name: &str) -> Option<&Condition> {
self.conditions.iter().find(|c| c.attribute == attr_name)
}
pub fn has_condition(&self, attr_name: &str) -> bool {
self.get_condition(attr_name).is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Condition {
pub attribute: String,
pub operator: CompareOp,
pub value: SqlValue,
}
impl Condition {
pub fn is_key_attribute(&self) -> bool {
self.attribute == "pk" || self.attribute == "sk"
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CompareOp {
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
In,
Between,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SqlValue {
Number(String),
String(String),
Boolean(bool),
Null,
List(Vec<SqlValue>),
Map(HashMap<String, SqlValue>),
}
impl SqlValue {
pub fn to_kstone_value(&self) -> crate::Value {
match self {
SqlValue::Number(s) => crate::Value::N(s.clone()),
SqlValue::String(s) => crate::Value::S(s.clone()),
SqlValue::Boolean(b) => crate::Value::Bool(*b),
SqlValue::Null => crate::Value::Null,
SqlValue::List(items) => {
let values: Vec<crate::Value> = items.iter()
.map(|item| item.to_kstone_value())
.collect();
crate::Value::L(values)
}
SqlValue::Map(map) => {
let mut kv_map = std::collections::HashMap::new();
for (k, v) in map {
kv_map.insert(k.clone(), v.to_kstone_value());
}
crate::Value::M(kv_map)
}
}
}
pub fn from_kstone_value(value: &crate::Value) -> Self {
match value {
crate::Value::N(s) => SqlValue::Number(s.clone()),
crate::Value::S(s) => SqlValue::String(s.clone()),
crate::Value::Bool(b) => SqlValue::Boolean(*b),
crate::Value::Null => SqlValue::Null,
crate::Value::L(items) => {
let sql_values: Vec<SqlValue> = items.iter()
.map(SqlValue::from_kstone_value)
.collect();
SqlValue::List(sql_values)
}
crate::Value::M(map) => {
let mut sql_map = HashMap::new();
for (k, v) in map {
sql_map.insert(k.clone(), SqlValue::from_kstone_value(v));
}
SqlValue::Map(sql_map)
}
crate::Value::B(bytes) => {
SqlValue::String(base64_encode(bytes))
}
crate::Value::VecF32(vec) => {
let numbers: Vec<SqlValue> = vec.iter()
.map(|f| SqlValue::Number(f.to_string()))
.collect();
SqlValue::List(numbers)
}
crate::Value::Ts(ts) => SqlValue::Number(ts.to_string()),
}
}
}
fn base64_encode(bytes: &bytes::Bytes) -> String {
use std::io::Write;
let mut buf = Vec::new();
{
let mut encoder = base64::write::EncoderWriter::new(&mut buf, &base64::engine::general_purpose::STANDARD);
encoder.write_all(bytes).unwrap();
encoder.finish().unwrap();
}
String::from_utf8(buf).unwrap()
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderBy {
pub attribute: String,
pub ascending: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct InsertStatement {
pub table_name: String,
pub value: SqlValue,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateStatement {
pub table_name: String,
pub where_clause: WhereClause,
pub set_assignments: Vec<SetAssignment>,
pub remove_attributes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SetAssignment {
pub attribute: String,
pub value: SetValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SetValue {
Literal(SqlValue),
Add {
attribute: String,
value: SqlValue,
},
Subtract {
attribute: String,
value: SqlValue,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteStatement {
pub table_name: String,
pub where_clause: WhereClause,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_where_clause_get_condition() {
let where_clause = WhereClause {
conditions: vec![
Condition {
attribute: "pk".to_string(),
operator: CompareOp::Equal,
value: SqlValue::String("user#123".to_string()),
},
Condition {
attribute: "age".to_string(),
operator: CompareOp::GreaterThan,
value: SqlValue::Number("18".to_string()),
},
],
};
assert!(where_clause.get_condition("pk").is_some());
assert!(where_clause.get_condition("age").is_some());
assert!(where_clause.get_condition("name").is_none());
}
#[test]
fn test_condition_is_key_attribute() {
let pk_cond = Condition {
attribute: "pk".to_string(),
operator: CompareOp::Equal,
value: SqlValue::String("user#123".to_string()),
};
let sk_cond = Condition {
attribute: "sk".to_string(),
operator: CompareOp::Equal,
value: SqlValue::String("profile".to_string()),
};
let data_cond = Condition {
attribute: "age".to_string(),
operator: CompareOp::GreaterThan,
value: SqlValue::Number("18".to_string()),
};
assert!(pk_cond.is_key_attribute());
assert!(sk_cond.is_key_attribute());
assert!(!data_cond.is_key_attribute());
}
#[test]
fn test_sql_value_to_kstone_value() {
let sql_num = SqlValue::Number("42".to_string());
assert!(matches!(sql_num.to_kstone_value(), crate::Value::N(s) if s == "42"));
let sql_str = SqlValue::String("hello".to_string());
assert!(matches!(sql_str.to_kstone_value(), crate::Value::S(s) if s == "hello"));
let sql_bool = SqlValue::Boolean(true);
assert!(matches!(sql_bool.to_kstone_value(), crate::Value::Bool(true)));
let sql_null = SqlValue::Null;
assert!(matches!(sql_null.to_kstone_value(), crate::Value::Null));
}
#[test]
fn test_sql_value_from_kstone_value() {
let kv_num = crate::Value::N("42".to_string());
assert_eq!(SqlValue::from_kstone_value(&kv_num), SqlValue::Number("42".to_string()));
let kv_str = crate::Value::S("hello".to_string());
assert_eq!(SqlValue::from_kstone_value(&kv_str), SqlValue::String("hello".to_string()));
let kv_bool = crate::Value::Bool(true);
assert_eq!(SqlValue::from_kstone_value(&kv_bool), SqlValue::Boolean(true));
}
}