1use std::fmt;
2
3use facet_value::Value as FacetValue;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Statement {
7 Create(CreateStatement),
8 Select(SelectStatement),
9 Update(UpdateStatement),
10 Delete(DeleteStatement),
11 Assign(AssignStatement),
12 Close(CloseStatement),
13 Comment(CommentStatement),
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum CreateStatement {
18 User {
19 username: String,
20 email: Option<String>,
21 name: Option<String>,
22 },
23 Project {
24 project_id: String,
25 name: Option<String>,
26 description: Option<String>,
27 owner: Option<String>,
28 },
29 Issue {
30 project: String,
31 title: String,
32 description: Option<String>,
33 priority: Option<Priority>,
34 assignee: Option<String>,
35 labels: Vec<String>,
36 },
37 Comment {
38 issue_id: IssueId,
39 content: String,
40 author: Option<String>,
41 },
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub struct SelectStatement {
46 pub columns: Columns,
47 pub from: EntityType,
48 pub filter: Option<FilterExpression>,
49 pub order_by: Option<OrderBy>,
50 pub limit: Option<u32>,
51 pub offset: Option<u32>,
52}
53
54#[derive(Debug, Clone, PartialEq)]
55pub enum Columns {
56 All,
57 Named(Vec<String>),
58}
59
60impl Columns {
61 pub fn len(&self) -> usize {
62 match self {
63 Columns::All => usize::MAX,
64 Columns::Named(cols) => cols.len(),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
70pub enum EntityType {
71 Users,
72 Projects,
73 Issues,
74 Comments,
75}
76
77impl fmt::Display for EntityType {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 EntityType::Users => write!(f, "users"),
81 EntityType::Projects => write!(f, "projects"),
82 EntityType::Issues => write!(f, "issues"),
83 EntityType::Comments => write!(f, "comments"),
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub enum FilterExpression {
90 Comparison {
91 field: String,
92 op: ComparisonOp,
93 value: Value,
94 },
95 And(Box<FilterExpression>, Box<FilterExpression>),
96 Or(Box<FilterExpression>, Box<FilterExpression>),
97 Not(Box<FilterExpression>),
98 In {
99 field: String,
100 values: Vec<Value>,
101 },
102 IsNull(String),
103 IsNotNull(String),
104}
105
106impl FilterExpression {
107 pub fn matches(&self, value: &FacetValue) -> bool {
108 match self {
109 FilterExpression::Comparison {
110 field,
111 op,
112 value: filter_value,
113 } => {
114 let obj = match value.as_object() {
115 Some(obj) => obj,
116 None => return false,
117 };
118
119 let field_value = match obj.get(field) {
120 Some(v) => v,
121 None => return false,
122 };
123
124 Self::compare_values(field_value, op, filter_value)
125 }
126 FilterExpression::And(left, right) => left.matches(value) && right.matches(value),
127 FilterExpression::Or(left, right) => left.matches(value) || right.matches(value),
128 FilterExpression::Not(expr) => !expr.matches(value),
129 FilterExpression::In { field, values } => {
130 let obj = match value.as_object() {
131 Some(obj) => obj,
132 None => return false,
133 };
134
135 let field_value = match obj.get(field) {
136 Some(v) => v,
137 None => return false,
138 };
139
140 values.iter().any(|filter_val| {
141 Self::compare_values(field_value, &ComparisonOp::Equal, filter_val)
142 })
143 }
144 FilterExpression::IsNull(field) => {
145 let obj = match value.as_object() {
146 Some(obj) => obj,
147 None => return false,
148 };
149
150 match obj.get(field) {
151 None => true,
152 Some(v) => v.is_null(),
153 }
154 }
155 FilterExpression::IsNotNull(field) => {
156 let obj = match value.as_object() {
157 Some(obj) => obj,
158 None => return false,
159 };
160
161 match obj.get(field) {
162 None => false,
163 Some(v) => !v.is_null(),
164 }
165 }
166 }
167 }
168
169 fn compare_values(field_value: &FacetValue, op: &ComparisonOp, filter_value: &Value) -> bool {
170 match op {
171 ComparisonOp::Equal => {
172 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
173 field_value == &converted
174 } else {
175 false
176 }
177 }
178 ComparisonOp::NotEqual => {
179 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
180 field_value != &converted
181 } else {
182 true
183 }
184 }
185 ComparisonOp::GreaterThan => {
186 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
187 field_value.partial_cmp(&converted) == Some(std::cmp::Ordering::Greater)
188 } else {
189 false
190 }
191 }
192 ComparisonOp::LessThan => {
193 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
194 field_value.partial_cmp(&converted) == Some(std::cmp::Ordering::Less)
195 } else {
196 false
197 }
198 }
199 ComparisonOp::GreaterThanOrEqual => {
200 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
201 matches!(
202 field_value.partial_cmp(&converted),
203 Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
204 )
205 } else {
206 false
207 }
208 }
209 ComparisonOp::LessThanOrEqual => {
210 if let Some(converted) = Self::convert_iql_value_to_facet(filter_value) {
211 matches!(
212 field_value.partial_cmp(&converted),
213 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
214 )
215 } else {
216 false
217 }
218 }
219 ComparisonOp::Like => {
220 let field_str = field_value.as_string().map(|s| s.as_str()).unwrap_or("");
221 if let Value::String(pattern) = filter_value {
222 let pattern = pattern.replace("%", ".*");
223 if let Ok(regex) = regex::Regex::new(&format!("^{}$", pattern)) {
224 regex.is_match(field_str)
225 } else {
226 false
227 }
228 } else {
229 false
230 }
231 }
232 }
233 }
234
235 fn convert_iql_value_to_facet(iql_value: &Value) -> Option<FacetValue> {
236 match iql_value {
237 Value::String(s) => Some(facet_value::VString::new(s).into_value()),
238 Value::Number(n) => Some(facet_value::VNumber::from_u64(*n as u64).into_value()),
239 Value::Float(f) => Some(facet_value::VNumber::from_f64(*f as f64)?.into_value()),
240 Value::Boolean(b) => Some(if *b {
241 facet_value::Value::TRUE
242 } else {
243 facet_value::Value::FALSE
244 }),
245 Value::Null => Some(facet_value::Value::NULL),
246 Value::Priority(p) => Some(facet_value::VString::new(&p.to_string()).into_value()),
247 Value::Identifier(id) => Some(facet_value::VString::new(id).into_value()),
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq)]
253pub enum ComparisonOp {
254 Equal,
255 NotEqual,
256 GreaterThan,
257 LessThan,
258 GreaterThanOrEqual,
259 LessThanOrEqual,
260 Like,
261}
262
263#[derive(Debug, Clone, PartialEq)]
264pub struct OrderBy {
265 pub field: String,
266 pub direction: OrderDirection,
267}
268
269#[derive(Debug, Clone, PartialEq)]
270pub enum OrderDirection {
271 Asc,
272 Desc,
273}
274
275#[derive(Debug, Clone, PartialEq)]
276pub struct UpdateStatement {
277 pub entity: UpdateTarget,
278 pub updates: Vec<FieldUpdate>,
279}
280
281#[derive(Debug, Clone, PartialEq)]
282pub enum UpdateTarget {
283 User(String),
284 Project(String),
285 Issue(IssueId),
286 Comment(u64),
287}
288
289#[derive(Debug, Clone, PartialEq)]
290pub struct FieldUpdate {
291 pub field: String,
292 pub value: Value,
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub struct DeleteStatement {
297 pub entity: DeleteTarget,
298}
299
300#[derive(Debug, Clone, PartialEq)]
301pub enum DeleteTarget {
302 User(String),
303 Project(String),
304 Issue(IssueId),
305 Comment(u64),
306}
307
308#[derive(Debug, Clone, PartialEq)]
309pub struct AssignStatement {
310 pub issue_id: IssueId,
311 pub assignee: String,
312}
313
314#[derive(Debug, Clone, PartialEq)]
315pub struct CloseStatement {
316 pub issue_id: IssueId,
317 pub reason: Option<String>,
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct CommentStatement {
322 pub issue_id: IssueId,
323 pub content: String,
324}
325
326#[derive(Debug, Clone, PartialEq)]
327pub struct IssueId {
328 pub project: String,
329 pub number: u64,
330}
331
332impl fmt::Display for IssueId {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 write!(f, "{}#{}", self.project, self.number)
335 }
336}
337
338#[derive(Debug, Clone, PartialEq)]
339pub enum Priority {
340 Critical,
341 High,
342 Medium,
343 Low,
344}
345
346impl fmt::Display for Priority {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 match self {
349 Priority::Critical => write!(f, "critical"),
350 Priority::High => write!(f, "high"),
351 Priority::Medium => write!(f, "medium"),
352 Priority::Low => write!(f, "low"),
353 }
354 }
355}
356
357#[derive(Debug, Clone, PartialEq)]
358pub enum Value {
359 String(String),
360 Number(i64),
361 Float(f64),
362 Boolean(bool),
363 Null,
364 Priority(Priority),
365 Identifier(String),
366}
367
368impl fmt::Display for Value {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 match self {
371 Value::String(s) => write!(f, "'{}'", s),
372 Value::Number(n) => write!(f, "{}", n),
373 Value::Float(fl) => write!(f, "{}", fl),
374 Value::Boolean(b) => write!(f, "{}", b),
375 Value::Null => write!(f, "NULL"),
376 Value::Priority(p) => write!(f, "{}", p),
377 Value::Identifier(id) => write!(f, "{}", id),
378 }
379 }
380}