use worker::wasm_bindgen::JsValue;
pub enum Order {
Asc,
Desc,
}
impl Order {
fn as_sql(&self) -> &'static str {
match self { Order::Asc => "ASC", Order::Desc => "DESC" }
}
}
enum ConditionKind {
Eq(String),
Ne(String),
Gt(String),
Gte(String),
Lt(String),
Lte(String),
IsNull(String),
IsNotNull(String),
FilterOptional(String),
FilterOptionalGte(String),
FilterOptionalLte(String),
}
struct Condition {
kind: ConditionKind,
value: Option<JsValue>,
}
pub struct Query {
conditions: Vec<Condition>,
order: Option<(String, Order)>,
limit: Option<u64>,
offset: Option<u64>,
}
impl Query {
pub fn new() -> Self {
Self { conditions: vec![], order: None, limit: None, offset: None }
}
pub fn eq(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Eq(col.to_string()), value: Some(val.into()) });
self
}
pub fn ne(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Ne(col.to_string()), value: Some(val.into()) });
self
}
pub fn gt(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Gt(col.to_string()), value: Some(val.into()) });
self
}
pub fn gte(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Gte(col.to_string()), value: Some(val.into()) });
self
}
pub fn lt(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Lt(col.to_string()), value: Some(val.into()) });
self
}
pub fn lte(mut self, col: &str, val: impl Into<JsValue>) -> Self {
self.conditions.push(Condition { kind: ConditionKind::Lte(col.to_string()), value: Some(val.into()) });
self
}
pub fn is_null(mut self, col: &str) -> Self {
self.conditions.push(Condition { kind: ConditionKind::IsNull(col.to_string()), value: None });
self
}
pub fn is_not_null(mut self, col: &str) -> Self {
self.conditions.push(Condition { kind: ConditionKind::IsNotNull(col.to_string()), value: None });
self
}
pub fn filter_optional(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
let js = val.map(Into::into).unwrap_or(JsValue::NULL);
self.conditions.push(Condition { kind: ConditionKind::FilterOptional(col.to_string()), value: Some(js) });
self
}
pub fn filter_optional_gte(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
let js = val.map(Into::into).unwrap_or(JsValue::NULL);
self.conditions.push(Condition { kind: ConditionKind::FilterOptionalGte(col.to_string()), value: Some(js) });
self
}
pub fn filter_optional_lte(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
let js = val.map(Into::into).unwrap_or(JsValue::NULL);
self.conditions.push(Condition { kind: ConditionKind::FilterOptionalLte(col.to_string()), value: Some(js) });
self
}
pub fn order_by(mut self, col: &str, dir: Order) -> Self {
self.order = Some((col.to_string(), dir));
self
}
pub fn limit(mut self, n: u64) -> Self { self.limit = Some(n); self }
pub fn offset(mut self, n: u64) -> Self { self.offset = Some(n); self }
pub(crate) fn build_conditions(&self, param_start: usize) -> (String, Vec<JsValue>) {
let mut parts: Vec<String> = vec![];
let mut values: Vec<JsValue> = vec![];
let mut n = param_start;
for cond in &self.conditions {
match &cond.kind {
ConditionKind::Eq(col) => {
parts.push(format!("{} = ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::Ne(col) => {
parts.push(format!("{} != ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::Gt(col) => {
parts.push(format!("{} > ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::Gte(col) => {
parts.push(format!("{} >= ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::Lt(col) => {
parts.push(format!("{} < ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::Lte(col) => {
parts.push(format!("{} <= ?{}", col, n));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::IsNull(col) => {
parts.push(format!("{} IS NULL", col));
}
ConditionKind::IsNotNull(col) => {
parts.push(format!("{} IS NOT NULL", col));
}
ConditionKind::FilterOptional(col) => {
parts.push(format!("(?{0} IS NULL OR {1} = ?{0})", n, col));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::FilterOptionalGte(col) => {
parts.push(format!("(?{0} IS NULL OR {1} >= ?{0})", n, col));
values.push(cond.value.clone().unwrap());
n += 1;
}
ConditionKind::FilterOptionalLte(col) => {
parts.push(format!("(?{0} IS NULL OR {1} <= ?{0})", n, col));
values.push(cond.value.clone().unwrap());
n += 1;
}
}
}
(parts.join(" AND "), values)
}
pub(crate) fn build_tail(&self) -> String {
let mut sql = String::new();
if let Some((col, dir)) = &self.order {
sql.push_str(&format!(" ORDER BY {} {}", col, dir.as_sql()));
}
if let Some(l) = self.limit {
sql.push_str(&format!(" LIMIT {}", l));
}
if let Some(o) = self.offset {
sql.push_str(&format!(" OFFSET {}", o));
}
sql
}
}
impl Default for Query {
fn default() -> Self { Self::new() }
}