1use worker::wasm_bindgen::JsValue;
2
3pub enum Order {
4 Asc,
5 Desc,
6}
7
8impl Order {
9 fn as_sql(&self) -> &'static str {
10 match self { Order::Asc => "ASC", Order::Desc => "DESC" }
11 }
12}
13
14enum ConditionKind {
15 Eq(String),
16 Ne(String),
17 Gt(String),
18 Gte(String),
19 Lt(String),
20 Lte(String),
21 IsNull(String),
22 IsNotNull(String),
23 FilterOptional(String),
24 FilterOptionalGte(String),
25 FilterOptionalLte(String),
26}
27
28struct Condition {
29 kind: ConditionKind,
30 value: Option<JsValue>,
31}
32
33pub struct Query {
34 conditions: Vec<Condition>,
35 order: Option<(String, Order)>,
36 limit: Option<u64>,
37 offset: Option<u64>,
38}
39
40impl Query {
41 pub fn new() -> Self {
42 Self { conditions: vec![], order: None, limit: None, offset: None }
43 }
44
45 pub fn eq(mut self, col: &str, val: impl Into<JsValue>) -> Self {
46 self.conditions.push(Condition { kind: ConditionKind::Eq(col.to_string()), value: Some(val.into()) });
47 self
48 }
49
50 pub fn ne(mut self, col: &str, val: impl Into<JsValue>) -> Self {
51 self.conditions.push(Condition { kind: ConditionKind::Ne(col.to_string()), value: Some(val.into()) });
52 self
53 }
54
55 pub fn gt(mut self, col: &str, val: impl Into<JsValue>) -> Self {
56 self.conditions.push(Condition { kind: ConditionKind::Gt(col.to_string()), value: Some(val.into()) });
57 self
58 }
59
60 pub fn gte(mut self, col: &str, val: impl Into<JsValue>) -> Self {
61 self.conditions.push(Condition { kind: ConditionKind::Gte(col.to_string()), value: Some(val.into()) });
62 self
63 }
64
65 pub fn lt(mut self, col: &str, val: impl Into<JsValue>) -> Self {
66 self.conditions.push(Condition { kind: ConditionKind::Lt(col.to_string()), value: Some(val.into()) });
67 self
68 }
69
70 pub fn lte(mut self, col: &str, val: impl Into<JsValue>) -> Self {
71 self.conditions.push(Condition { kind: ConditionKind::Lte(col.to_string()), value: Some(val.into()) });
72 self
73 }
74
75 pub fn is_null(mut self, col: &str) -> Self {
76 self.conditions.push(Condition { kind: ConditionKind::IsNull(col.to_string()), value: None });
77 self
78 }
79
80 pub fn is_not_null(mut self, col: &str) -> Self {
81 self.conditions.push(Condition { kind: ConditionKind::IsNotNull(col.to_string()), value: None });
82 self
83 }
84
85 pub fn filter_optional(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
88 let js = val.map(Into::into).unwrap_or(JsValue::NULL);
89 self.conditions.push(Condition { kind: ConditionKind::FilterOptional(col.to_string()), value: Some(js) });
90 self
91 }
92
93 pub fn filter_optional_gte(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
95 let js = val.map(Into::into).unwrap_or(JsValue::NULL);
96 self.conditions.push(Condition { kind: ConditionKind::FilterOptionalGte(col.to_string()), value: Some(js) });
97 self
98 }
99
100 pub fn filter_optional_lte(mut self, col: &str, val: Option<impl Into<JsValue>>) -> Self {
102 let js = val.map(Into::into).unwrap_or(JsValue::NULL);
103 self.conditions.push(Condition { kind: ConditionKind::FilterOptionalLte(col.to_string()), value: Some(js) });
104 self
105 }
106
107 pub fn order_by(mut self, col: &str, dir: Order) -> Self {
108 self.order = Some((col.to_string(), dir));
109 self
110 }
111
112 pub fn limit(mut self, n: u64) -> Self { self.limit = Some(n); self }
113 pub fn offset(mut self, n: u64) -> Self { self.offset = Some(n); self }
114
115 pub(crate) fn build_conditions(&self, param_start: usize) -> (String, Vec<JsValue>) {
116 let mut parts: Vec<String> = vec![];
117 let mut values: Vec<JsValue> = vec![];
118 let mut n = param_start;
119
120 for cond in &self.conditions {
121 match &cond.kind {
122 ConditionKind::Eq(col) => {
123 parts.push(format!("{} = ?{}", col, n));
124 values.push(cond.value.clone().unwrap());
125 n += 1;
126 }
127 ConditionKind::Ne(col) => {
128 parts.push(format!("{} != ?{}", col, n));
129 values.push(cond.value.clone().unwrap());
130 n += 1;
131 }
132 ConditionKind::Gt(col) => {
133 parts.push(format!("{} > ?{}", col, n));
134 values.push(cond.value.clone().unwrap());
135 n += 1;
136 }
137 ConditionKind::Gte(col) => {
138 parts.push(format!("{} >= ?{}", col, n));
139 values.push(cond.value.clone().unwrap());
140 n += 1;
141 }
142 ConditionKind::Lt(col) => {
143 parts.push(format!("{} < ?{}", col, n));
144 values.push(cond.value.clone().unwrap());
145 n += 1;
146 }
147 ConditionKind::Lte(col) => {
148 parts.push(format!("{} <= ?{}", col, n));
149 values.push(cond.value.clone().unwrap());
150 n += 1;
151 }
152 ConditionKind::IsNull(col) => {
153 parts.push(format!("{} IS NULL", col));
154 }
155 ConditionKind::IsNotNull(col) => {
156 parts.push(format!("{} IS NOT NULL", col));
157 }
158 ConditionKind::FilterOptional(col) => {
159 parts.push(format!("(?{0} IS NULL OR {1} = ?{0})", n, col));
160 values.push(cond.value.clone().unwrap());
161 n += 1;
162 }
163 ConditionKind::FilterOptionalGte(col) => {
164 parts.push(format!("(?{0} IS NULL OR {1} >= ?{0})", n, col));
165 values.push(cond.value.clone().unwrap());
166 n += 1;
167 }
168 ConditionKind::FilterOptionalLte(col) => {
169 parts.push(format!("(?{0} IS NULL OR {1} <= ?{0})", n, col));
170 values.push(cond.value.clone().unwrap());
171 n += 1;
172 }
173 }
174 }
175
176 (parts.join(" AND "), values)
177 }
178
179 pub(crate) fn build_tail(&self) -> String {
180 let mut sql = String::new();
181 if let Some((col, dir)) = &self.order {
182 sql.push_str(&format!(" ORDER BY {} {}", col, dir.as_sql()));
183 }
184 if let Some(l) = self.limit {
185 sql.push_str(&format!(" LIMIT {}", l));
186 }
187 if let Some(o) = self.offset {
188 sql.push_str(&format!(" OFFSET {}", o));
189 }
190 sql
191 }
192}
193
194impl Default for Query {
195 fn default() -> Self { Self::new() }
196}