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