1#[derive(Debug, Clone)]
48pub enum SochQuery {
49 Select(SelectQuery),
51 Insert(InsertQuery),
53 CreateTable(CreateTableQuery),
55 DropTable { table: String },
57}
58
59#[derive(Debug, Clone)]
61pub struct SelectQuery {
62 pub columns: Vec<String>,
64 pub table: String,
66 pub where_clause: Option<WhereClause>,
68 pub order_by: Option<OrderBy>,
70 pub limit: Option<usize>,
72 pub offset: Option<usize>,
74}
75
76#[derive(Debug, Clone)]
78pub struct InsertQuery {
79 pub table: String,
81 pub columns: Vec<String>,
83 pub rows: Vec<Vec<SochValue>>,
85}
86
87#[derive(Debug, Clone)]
89pub struct CreateTableQuery {
90 pub table: String,
92 pub columns: Vec<ColumnDef>,
94 pub primary_key: Option<String>,
96}
97
98#[derive(Debug, Clone)]
100pub struct ColumnDef {
101 pub name: String,
103 pub col_type: ColumnType,
105 pub not_null: bool,
107 pub unique: bool,
109 pub default: Option<SochValue>,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum ColumnType {
116 Bool,
117 Int64,
118 UInt64,
119 Float64,
120 Text,
121 Binary,
122 Timestamp,
123}
124
125#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
127pub enum SochValue {
128 Null,
129 Bool(bool),
130 Int(i64),
131 UInt(u64),
132 Float(f64),
133 Text(String),
134 Binary(Vec<u8>),
135 Array(Vec<SochValue>),
136}
137
138impl SochValue {
139 pub fn to_soch_string(&self) -> String {
141 match self {
142 SochValue::Null => "null".to_string(),
143 SochValue::Bool(b) => b.to_string(),
144 SochValue::Int(i) => i.to_string(),
145 SochValue::UInt(u) => u.to_string(),
146 SochValue::Float(f) => format!("{:.2}", f),
147 SochValue::Text(s) => s.clone(),
148 SochValue::Binary(b) => {
149 let hex_str: String = b.iter().map(|byte| format!("{:02x}", byte)).collect();
150 format!("0x{}", hex_str)
151 }
152 SochValue::Array(arr) => {
153 let items: Vec<String> = arr.iter().map(|v| v.to_soch_string()).collect();
154 format!("[{}]", items.join(","))
155 }
156 }
157 }
158}
159
160#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
162pub struct WhereClause {
163 pub conditions: Vec<Condition>,
164 pub operator: LogicalOp,
165}
166
167#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
169pub enum LogicalOp {
170 And,
171 Or,
172}
173
174#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
176pub struct Condition {
177 pub column: String,
178 pub operator: ComparisonOp,
179 pub value: SochValue,
180}
181
182#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
184pub enum ComparisonOp {
185 Eq,
186 Ne,
187 Lt,
188 Le,
189 Gt,
190 Ge,
191 Like,
192 In,
193 SimilarTo,
196}
197
198#[derive(Debug, Clone)]
200pub struct OrderBy {
201 pub column: String,
202 pub direction: SortDirection,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum SortDirection {
208 Asc,
209 Desc,
210}
211
212#[derive(Debug, Clone)]
214pub struct SochResult {
215 pub table: String,
217 pub columns: Vec<String>,
219 pub rows: Vec<Vec<SochValue>>,
221}
222
223impl SochResult {
224 pub fn to_soch_string(&self) -> String {
233 let mut result = String::new();
234
235 result.push_str(&format!(
237 "{}[{}]{{{}}}:\n",
238 self.table,
239 self.rows.len(),
240 self.columns.join(",")
241 ));
242
243 for row in &self.rows {
245 let values: Vec<String> = row.iter().map(|v| v.to_soch_string()).collect();
246 result.push_str(&values.join(","));
247 result.push('\n');
248 }
249
250 result
251 }
252
253 pub fn row_count(&self) -> usize {
255 self.rows.len()
256 }
257
258 pub fn column_count(&self) -> usize {
260 self.columns.len()
261 }
262
263 pub fn get(&self, row: usize, col: usize) -> Option<&SochValue> {
265 self.rows.get(row)?.get(col)
266 }
267}
268
269pub struct SochQlParser;
271
272impl SochQlParser {
273 pub fn parse(query: &str) -> Result<SochQuery, ParseError> {
275 let query = query.trim();
276
277 if query.to_uppercase().starts_with("SELECT") {
278 Self::parse_select(query)
279 } else if query.to_uppercase().starts_with("INSERT") {
280 Self::parse_insert(query)
281 } else if query.to_uppercase().starts_with("CREATE TABLE") {
282 Self::parse_create_table(query)
283 } else if query.to_uppercase().starts_with("DROP TABLE") {
284 Self::parse_drop_table(query)
285 } else {
286 Err(ParseError::UnknownStatement)
287 }
288 }
289
290 fn parse_select(query: &str) -> Result<SochQuery, ParseError> {
291 let query_upper = query.to_uppercase();
294
295 let from_idx = query_upper.find("FROM").ok_or(ParseError::MissingFrom)?;
297 let columns_str = &query[6..from_idx].trim();
298 let columns: Vec<String> = if columns_str == &"*" {
299 vec!["*".to_string()]
300 } else {
301 columns_str
302 .split(',')
303 .map(|s| s.trim().to_string())
304 .collect()
305 };
306
307 let after_from = &query[from_idx + 4..].trim();
309 let table_end = after_from
310 .find(|c: char| c.is_whitespace())
311 .unwrap_or(after_from.len());
312 let table = after_from[..table_end].to_string();
313
314 let where_clause = Self::parse_where_clause(&query_upper, query)?;
316
317 let order_by = None;
318 let limit = None;
319 let offset = None;
320
321 Ok(SochQuery::Select(SelectQuery {
322 columns,
323 table,
324 where_clause,
325 order_by,
326 limit,
327 offset,
328 }))
329 }
330
331 fn parse_where_clause(
333 query_upper: &str,
334 original: &str,
335 ) -> Result<Option<WhereClause>, ParseError> {
336 let where_idx = match query_upper.find("WHERE") {
337 Some(idx) => idx,
338 None => return Ok(None),
339 };
340
341 let after_where = &original[where_idx + 5..].trim();
343 let clause_end = after_where
344 .to_uppercase()
345 .find("ORDER BY")
346 .or_else(|| after_where.to_uppercase().find("LIMIT"))
347 .unwrap_or(after_where.len());
348
349 let condition_str = after_where[..clause_end].trim();
350
351 let (column, operator, value) = Self::parse_condition(condition_str)?;
354
355 Ok(Some(WhereClause {
356 conditions: vec![Condition {
357 column,
358 operator,
359 value,
360 }],
361 operator: LogicalOp::And, }))
363 }
364
365 fn parse_condition(condition: &str) -> Result<(String, ComparisonOp, SochValue), ParseError> {
367 let condition_upper = condition.to_uppercase();
368
369 if let Some(in_idx) = condition_upper.find(" IN ") {
371 let field = condition[..in_idx].trim().to_string();
372 let values_str = condition[in_idx + 4..].trim();
373 let values = Self::parse_in_values(values_str)?;
375 return Ok((field, ComparisonOp::In, values));
376 }
377
378 if let Some(like_idx) = condition_upper.find(" LIKE ") {
380 let field = condition[..like_idx].trim().to_string();
381 let pattern = condition[like_idx + 6..].trim();
382 let value = Self::parse_value(pattern)?;
383 return Ok((field, ComparisonOp::Like, value));
384 }
385
386 let operators = [
388 ("!=", ComparisonOp::Ne),
389 ("<=", ComparisonOp::Le),
390 (">=", ComparisonOp::Ge),
391 ("<>", ComparisonOp::Ne),
392 ("=", ComparisonOp::Eq),
393 ("<", ComparisonOp::Lt),
394 (">", ComparisonOp::Gt),
395 ];
396
397 for (op_str, op) in operators {
398 if let Some(op_idx) = condition.find(op_str) {
399 let field = condition[..op_idx].trim().to_string();
400 let value_str = condition[op_idx + op_str.len()..].trim();
401 let value = Self::parse_value(value_str)?;
402 return Ok((field, op, value));
403 }
404 }
405
406 Err(ParseError::InvalidSyntax)
407 }
408
409 fn parse_in_values(values_str: &str) -> Result<SochValue, ParseError> {
411 let trimmed = values_str.trim();
412 if !trimmed.starts_with('(') || !trimmed.ends_with(')') {
413 return Err(ParseError::InvalidSyntax);
414 }
415
416 let inner = &trimmed[1..trimmed.len() - 1];
417 let values: Result<Vec<SochValue>, ParseError> = inner
418 .split(',')
419 .map(|v| Self::parse_value(v.trim()))
420 .collect();
421
422 Ok(SochValue::Array(values?))
424 }
425
426 fn parse_value(value_str: &str) -> Result<SochValue, ParseError> {
428 let trimmed = value_str.trim();
429
430 if (trimmed.starts_with('\'') && trimmed.ends_with('\''))
432 || (trimmed.starts_with('"') && trimmed.ends_with('"'))
433 {
434 let inner = &trimmed[1..trimmed.len() - 1];
435 return Ok(SochValue::Text(inner.to_string()));
436 }
437
438 if trimmed.eq_ignore_ascii_case("true") {
440 return Ok(SochValue::Bool(true));
441 }
442 if trimmed.eq_ignore_ascii_case("false") {
443 return Ok(SochValue::Bool(false));
444 }
445
446 if trimmed.eq_ignore_ascii_case("null") {
448 return Ok(SochValue::Null);
449 }
450
451 if trimmed.contains('.')
453 && let Ok(f) = trimmed.parse::<f64>()
454 {
455 return Ok(SochValue::Float(f));
456 }
457
458 if let Ok(i) = trimmed.parse::<i64>() {
460 return Ok(SochValue::Int(i));
461 }
462
463 if let Ok(u) = trimmed.parse::<u64>() {
465 return Ok(SochValue::UInt(u));
466 }
467
468 Ok(SochValue::Text(trimmed.to_string()))
470 }
471
472 fn parse_insert(query: &str) -> Result<SochQuery, ParseError> {
473 let after_insert = query[6..].trim();
477
478 let table_end = after_insert
480 .find([':', '['])
481 .ok_or(ParseError::InvalidSyntax)?;
482 let table = after_insert[..table_end].trim().to_string();
483
484 Ok(SochQuery::Insert(InsertQuery {
486 table,
487 columns: Vec::new(),
488 rows: Vec::new(),
489 }))
490 }
491
492 fn parse_create_table(query: &str) -> Result<SochQuery, ParseError> {
493 let after_create = &query[12..].trim();
495 let brace_idx = after_create.find('{').ok_or(ParseError::InvalidSyntax)?;
496 let table = after_create[..brace_idx].trim().to_string();
497
498 Ok(SochQuery::CreateTable(CreateTableQuery {
499 table,
500 columns: Vec::new(),
501 primary_key: None,
502 }))
503 }
504
505 fn parse_drop_table(query: &str) -> Result<SochQuery, ParseError> {
506 let table = query[10..].trim().to_string();
507 Ok(SochQuery::DropTable { table })
508 }
509}
510
511#[derive(Debug, Clone)]
513pub enum ParseError {
514 UnknownStatement,
515 MissingFrom,
516 InvalidSyntax,
517 InvalidValue(String),
518}
519
520impl std::fmt::Display for ParseError {
521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522 match self {
523 ParseError::UnknownStatement => write!(f, "Unknown statement"),
524 ParseError::MissingFrom => write!(f, "Missing FROM clause"),
525 ParseError::InvalidSyntax => write!(f, "Invalid syntax"),
526 ParseError::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
527 }
528 }
529}
530
531impl std::error::Error for ParseError {}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_parse_select() {
539 let query = "SELECT id, name FROM users";
540 let result = SochQlParser::parse(query).unwrap();
541
542 match result {
543 SochQuery::Select(select) => {
544 assert_eq!(select.table, "users");
545 assert_eq!(select.columns, vec!["id", "name"]);
546 }
547 _ => panic!("Expected SELECT query"),
548 }
549 }
550
551 #[test]
552 fn test_parse_select_star() {
553 let query = "SELECT * FROM users";
554 let result = SochQlParser::parse(query).unwrap();
555
556 match result {
557 SochQuery::Select(select) => {
558 assert_eq!(select.table, "users");
559 assert_eq!(select.columns, vec!["*"]);
560 }
561 _ => panic!("Expected SELECT query"),
562 }
563 }
564
565 #[test]
566 fn test_parse_create_table() {
567 let query = "CREATE TABLE users { id: u64, name: text }";
568 let result = SochQlParser::parse(query).unwrap();
569
570 match result {
571 SochQuery::CreateTable(ct) => {
572 assert_eq!(ct.table, "users");
573 }
574 _ => panic!("Expected CREATE TABLE query"),
575 }
576 }
577
578 #[test]
579 fn test_parse_drop_table() {
580 let query = "DROP TABLE users";
581 let result = SochQlParser::parse(query).unwrap();
582
583 match result {
584 SochQuery::DropTable { table } => {
585 assert_eq!(table, "users");
586 }
587 _ => panic!("Expected DROP TABLE query"),
588 }
589 }
590
591 #[test]
592 fn test_soch_result_format() {
593 let result = SochResult {
594 table: "users".to_string(),
595 columns: vec!["id".to_string(), "name".to_string()],
596 rows: vec![
597 vec![SochValue::UInt(1), SochValue::Text("Alice".to_string())],
598 vec![SochValue::UInt(2), SochValue::Text("Bob".to_string())],
599 ],
600 };
601
602 let formatted = result.to_soch_string();
603 assert!(formatted.contains("users[2]{id,name}:"));
604 assert!(formatted.contains("1,Alice"));
605 assert!(formatted.contains("2,Bob"));
606 }
607
608 #[test]
609 #[allow(clippy::approx_constant)]
610 fn test_soch_value_format() {
611 assert_eq!(SochValue::Null.to_soch_string(), "null");
612 assert_eq!(SochValue::Bool(true).to_soch_string(), "true");
613 assert_eq!(SochValue::Int(-42).to_soch_string(), "-42");
614 assert_eq!(SochValue::UInt(100).to_soch_string(), "100");
615 assert_eq!(SochValue::Float(3.14).to_soch_string(), "3.14");
616 assert_eq!(
617 SochValue::Text("hello".to_string()).to_soch_string(),
618 "hello"
619 );
620 }
621}