gitql_core/values/
text.rs

1use std::any::Any;
2use std::cmp::Ordering;
3
4use gitql_ast::operator::GroupComparisonOperator;
5use regex::Regex;
6use regex::RegexBuilder;
7
8use gitql_ast::types::text::TextType;
9use gitql_ast::types::DataType;
10
11use super::base::Value;
12use super::boolean::BoolValue;
13use super::converters::string_literal_to_boolean;
14use super::converters::string_literal_to_date;
15use super::converters::string_literal_to_date_time;
16use super::converters::string_literal_to_time;
17
18#[derive(Clone)]
19pub struct TextValue {
20    pub value: String,
21}
22
23impl TextValue {
24    pub fn new(value: String) -> Self {
25        TextValue { value }
26    }
27
28    pub fn empty() -> Self {
29        TextValue {
30            value: String::default(),
31        }
32    }
33}
34
35impl Value for TextValue {
36    fn literal(&self) -> String {
37        self.value.to_string()
38    }
39
40    fn equals(&self, other: &Box<dyn Value>) -> bool {
41        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
42            return self.value == other_text.value;
43        }
44        false
45    }
46
47    fn compare(&self, other: &Box<dyn Value>) -> Option<Ordering> {
48        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
49            return self.value.partial_cmp(&other_text.value);
50        }
51        None
52    }
53
54    fn data_type(&self) -> Box<dyn DataType> {
55        Box::new(TextType)
56    }
57
58    fn as_any(&self) -> &dyn Any {
59        self
60    }
61
62    fn eq_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
63        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
64            return Ok(Box::new(BoolValue::new(self.value == other_text.value)));
65        }
66        Err("Unexpected type to perform `=` with".to_string())
67    }
68
69    fn group_eq_op(
70        &self,
71        other: &Box<dyn Value>,
72        group_op: &GroupComparisonOperator,
73    ) -> Result<Box<dyn Value>, String> {
74        if other.is_array_of(|element_type| element_type.is_text()) {
75            let elements = &other.as_array().unwrap();
76            let mut matches_count = 0;
77            for element in elements.iter() {
78                if self.value == element.as_text().unwrap() {
79                    matches_count += 1;
80                    if GroupComparisonOperator::Any.eq(group_op) {
81                        break;
82                    }
83                }
84            }
85
86            let result = match group_op {
87                GroupComparisonOperator::All => matches_count == elements.len(),
88                GroupComparisonOperator::Any => matches_count > 0,
89            };
90
91            return Ok(Box::new(BoolValue::new(result)));
92        }
93        Err("Unexpected type to perform `=` with".to_string())
94    }
95
96    fn bang_eq_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
97        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
98            return Ok(Box::new(BoolValue::new(self.value != other_text.value)));
99        }
100        Err("Unexpected type to perform `!=` with".to_string())
101    }
102
103    fn group_bang_eq_op(
104        &self,
105        other: &Box<dyn Value>,
106        group_op: &GroupComparisonOperator,
107    ) -> Result<Box<dyn Value>, String> {
108        if other.is_array_of(|element_type| element_type.is_text()) {
109            let elements = &other.as_array().unwrap();
110            let mut matches_count = 0;
111            for element in elements.iter() {
112                if self.value != element.as_text().unwrap() {
113                    matches_count += 1;
114                    if GroupComparisonOperator::Any.eq(group_op) {
115                        break;
116                    }
117                }
118            }
119
120            let result = match group_op {
121                GroupComparisonOperator::All => matches_count == elements.len(),
122                GroupComparisonOperator::Any => matches_count > 0,
123            };
124
125            return Ok(Box::new(BoolValue::new(result)));
126        }
127        Err("Unexpected type to perform `!=` with".to_string())
128    }
129
130    fn gt_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
131        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
132            return Ok(Box::new(BoolValue::new(self.value > other_text.value)));
133        }
134        Err("Unexpected type to perform `>` with".to_string())
135    }
136
137    fn group_gt_op(
138        &self,
139        other: &Box<dyn Value>,
140        group_op: &GroupComparisonOperator,
141    ) -> Result<Box<dyn Value>, String> {
142        if other.is_array_of(|element_type| element_type.is_text()) {
143            let elements = &other.as_array().unwrap();
144            let mut matches_count = 0;
145            for element in elements.iter() {
146                if self.value > element.as_text().unwrap() {
147                    matches_count += 1;
148                    if GroupComparisonOperator::Any.eq(group_op) {
149                        break;
150                    }
151                }
152            }
153
154            let result = match group_op {
155                GroupComparisonOperator::All => matches_count == elements.len(),
156                GroupComparisonOperator::Any => matches_count > 0,
157            };
158
159            return Ok(Box::new(BoolValue::new(result)));
160        }
161        Err("Unexpected type to perform `>` with".to_string())
162    }
163
164    fn gte_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
165        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
166            return Ok(Box::new(BoolValue::new(self.value >= other_text.value)));
167        }
168        Err("Unexpected type to perform `>=` with".to_string())
169    }
170
171    fn group_gte_op(
172        &self,
173        other: &Box<dyn Value>,
174        group_op: &GroupComparisonOperator,
175    ) -> Result<Box<dyn Value>, String> {
176        if other.is_array_of(|element_type| element_type.is_text()) {
177            let elements = &other.as_array().unwrap();
178            let mut matches_count = 0;
179            for element in elements.iter() {
180                if self.value >= element.as_text().unwrap() {
181                    matches_count += 1;
182                    if GroupComparisonOperator::Any.eq(group_op) {
183                        break;
184                    }
185                }
186            }
187
188            let result = match group_op {
189                GroupComparisonOperator::All => matches_count == elements.len(),
190                GroupComparisonOperator::Any => matches_count > 0,
191            };
192
193            return Ok(Box::new(BoolValue::new(result)));
194        }
195        Err("Unexpected type to perform `>=` with".to_string())
196    }
197
198    fn lt_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
199        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
200            return Ok(Box::new(BoolValue::new(self.value < other_text.value)));
201        }
202        Err("Unexpected type to perform `<` with".to_string())
203    }
204
205    fn group_lt_op(
206        &self,
207        other: &Box<dyn Value>,
208        group_op: &GroupComparisonOperator,
209    ) -> Result<Box<dyn Value>, String> {
210        if other.is_array_of(|element_type| element_type.is_text()) {
211            let elements = &other.as_array().unwrap();
212            let mut matches_count = 0;
213            for element in elements.iter() {
214                if self.value < element.as_text().unwrap() {
215                    matches_count += 1;
216                    if GroupComparisonOperator::Any.eq(group_op) {
217                        break;
218                    }
219                }
220            }
221
222            let result = match group_op {
223                GroupComparisonOperator::All => matches_count == elements.len(),
224                GroupComparisonOperator::Any => matches_count > 0,
225            };
226
227            return Ok(Box::new(BoolValue::new(result)));
228        }
229        Err("Unexpected type to perform `<` with".to_string())
230    }
231
232    fn lte_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
233        if let Some(other_text) = other.as_any().downcast_ref::<TextValue>() {
234            return Ok(Box::new(BoolValue::new(self.value <= other_text.value)));
235        }
236        Err("Unexpected type to perform `<=` with".to_string())
237    }
238
239    fn group_lte_op(
240        &self,
241        other: &Box<dyn Value>,
242        group_op: &GroupComparisonOperator,
243    ) -> Result<Box<dyn Value>, String> {
244        if other.is_array_of(|element_type| element_type.is_text()) {
245            let elements = &other.as_array().unwrap();
246            let mut matches_count = 0;
247            for element in elements.iter() {
248                if self.value <= element.as_text().unwrap() {
249                    matches_count += 1;
250                    if GroupComparisonOperator::Any.eq(group_op) {
251                        break;
252                    }
253                }
254            }
255
256            let result = match group_op {
257                GroupComparisonOperator::All => matches_count == elements.len(),
258                GroupComparisonOperator::Any => matches_count > 0,
259            };
260
261            return Ok(Box::new(BoolValue::new(result)));
262        }
263        Err("Unexpected type to perform `<=` with".to_string())
264    }
265
266    fn like_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
267        let pattern_text = other.as_text().unwrap();
268        let pattern = &format!(
269            "^{}$",
270            pattern_text
271                .to_lowercase()
272                .replace('%', ".*")
273                .replace('_', ".")
274        );
275
276        let regex_builder = RegexBuilder::new(pattern)
277            .multi_line(true)
278            .unicode(true)
279            .build();
280
281        match regex_builder {
282            Ok(regex) => {
283                let is_match = regex.is_match(&self.value.to_lowercase());
284                Ok(Box::new(BoolValue { value: is_match }))
285            }
286            Err(error_message) => Err(error_message.to_string()),
287        }
288    }
289
290    fn glob_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
291        let pattern_text = other.as_text().unwrap();
292        let pattern = &format!(
293            "^{}$",
294            pattern_text
295                .replace('.', "\\.")
296                .replace('*', ".*")
297                .replace('?', ".")
298        );
299
300        match Regex::new(pattern) {
301            Ok(regex) => {
302                let is_match = regex.is_match(&self.value);
303                Ok(Box::new(BoolValue { value: is_match }))
304            }
305            Err(error_message) => Err(error_message.to_string()),
306        }
307    }
308
309    fn regexp_op(&self, other: &Box<dyn Value>) -> Result<Box<dyn Value>, String> {
310        let pattern_text = other.as_text().unwrap();
311        let pattern = &format!(
312            "^{}$",
313            pattern_text
314                .to_lowercase()
315                .replace('%', ".*")
316                .replace('_', ".")
317        );
318
319        let regex_builder = RegexBuilder::new(pattern)
320            .multi_line(true)
321            .unicode(true)
322            .build();
323
324        match regex_builder {
325            Ok(regex) => {
326                let is_match = regex.is_match(&self.value.to_lowercase());
327                Ok(Box::new(BoolValue { value: is_match }))
328            }
329            Err(error_message) => Err(error_message.to_string()),
330        }
331    }
332
333    fn cast_op(&self, target_type: &Box<dyn DataType>) -> Result<Box<dyn Value>, String> {
334        if target_type.is_bool() {
335            return Ok(string_literal_to_boolean(&self.value));
336        }
337
338        if target_type.is_time() {
339            return Ok(string_literal_to_time(&self.value));
340        }
341
342        if target_type.is_date() {
343            return Ok(string_literal_to_date(&self.value));
344        }
345
346        if target_type.is_date_time() {
347            return Ok(string_literal_to_date_time(&self.value));
348        }
349
350        Err("Unexpected value to perform `CAST` with".to_string())
351    }
352}