1+ use crate :: query:: { Expression , BinaryOperator , LogicalOperator , LogicalPlan , Statement } ;
2+ use serde_json:: Value ;
3+ use sqlparser:: dialect:: GenericDialect ;
4+ use sqlparser:: parser:: Parser ;
5+ use sqlparser:: ast:: { self , SetExpr , TableFactor , BinaryOperator as SqlBinaryOperator , Expr , LimitClause } ;
6+ use nom:: {
7+ bytes:: complete:: { tag_no_case, take_while1} ,
8+ character:: complete:: { multispace0, multispace1, char} ,
9+ sequence:: tuple,
10+ multi:: separated_list1,
11+ IResult ,
12+ } ;
13+
14+ pub fn parse ( sql : & str ) -> Result < Statement , String > {
15+ let trimmed = sql. trim ( ) ;
16+ if trimmed. to_uppercase ( ) . starts_with ( "INSERT" ) {
17+ parse_insert ( trimmed) . map_err ( |e| format ! ( "Insert parse error: {}" , e) )
18+ } else {
19+ parse_select ( trimmed)
20+ }
21+ }
22+
23+ // --- INSERT Parsing (Custom using Nom) ---
24+
25+ fn parse_insert ( input : & str ) -> Result < Statement , String > {
26+ match insert_statement ( input) {
27+ Ok ( ( _, stmt) ) => Ok ( stmt) ,
28+ Err ( e) => Err ( format ! ( "{}" , e) ) ,
29+ }
30+ }
31+
32+ fn insert_statement ( input : & str ) -> IResult < & str , Statement > {
33+ // INSERT INTO <collection> RECORDS <json>...
34+ let ( input, _) = tag_no_case ( "INSERT" ) ( input) ?;
35+ let ( input, _) = multispace1 ( input) ?;
36+ let ( input, _) = tag_no_case ( "INTO" ) ( input) ?;
37+ let ( input, _) = multispace1 ( input) ?;
38+
39+ let ( input, collection) = identifier ( input) ?;
40+
41+ let ( input, _) = multispace1 ( input) ?;
42+ let ( input, _) = tag_no_case ( "RECORDS" ) ( input) ?;
43+ let ( input, _) = multispace0 ( input) ?;
44+
45+ let ( input, documents) = separated_list1 (
46+ tuple ( ( multispace0, char ( ',' ) , multispace0) ) ,
47+ json_object
48+ ) ( input) ?;
49+
50+ Ok ( ( input, Statement :: Insert {
51+ collection : collection. to_string ( ) ,
52+ documents,
53+ } ) )
54+ }
55+
56+ fn identifier ( input : & str ) -> IResult < & str , & str > {
57+ take_while1 ( |c : char | c. is_alphanumeric ( ) || c == '_' ) ( input)
58+ }
59+
60+ fn json_object ( input : & str ) -> IResult < & str , Value > {
61+ let mut depth = 0 ;
62+ let mut len = 0 ;
63+ let mut found_start = false ;
64+
65+ // Skip leading whitespace
66+ let leading_ws = input. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) . count ( ) ;
67+ let trimmed_input = & input[ leading_ws..] ;
68+
69+ if !trimmed_input. starts_with ( '{' ) {
70+ return Err ( nom:: Err :: Error ( nom:: error:: Error :: new ( input, nom:: error:: ErrorKind :: Tag ) ) ) ;
71+ }
72+
73+ // Iterate to find the matching brace
74+ for ( i, c) in trimmed_input. char_indices ( ) {
75+ if c == '{' {
76+ depth += 1 ;
77+ found_start = true ;
78+ } else if c == '}' {
79+ depth -= 1 ;
80+ }
81+
82+ if found_start && depth == 0 {
83+ len = i + 1 ;
84+ break ;
85+ }
86+ }
87+
88+ if depth != 0 {
89+ return Err ( nom:: Err :: Error ( nom:: error:: Error :: new ( input, nom:: error:: ErrorKind :: Complete ) ) ) ;
90+ }
91+
92+ let json_str = & trimmed_input[ 0 ..len] ;
93+ let value: Value = serde_json:: from_str ( json_str) . map_err ( |_| {
94+ nom:: Err :: Error ( nom:: error:: Error :: new ( input, nom:: error:: ErrorKind :: MapRes ) )
95+ } ) ?;
96+
97+ Ok ( ( & trimmed_input[ len..] , value) )
98+ }
99+
100+
101+ // --- SELECT Parsing (sqlparser) ---
102+
103+ fn parse_select ( sql : & str ) -> Result < Statement , String > {
104+ let dialect = GenericDialect { } ;
105+ let ast = Parser :: parse_sql ( & dialect, sql) . map_err ( |e| e. to_string ( ) ) ?;
106+
107+ if ast. len ( ) != 1 {
108+ return Err ( "Expected exactly one statement" . to_string ( ) ) ;
109+ }
110+
111+ match & ast[ 0 ] {
112+ ast:: Statement :: Query ( query) => {
113+ let logical_plan = convert_query ( query) ?;
114+ Ok ( Statement :: Select ( logical_plan) )
115+ }
116+ _ => Err ( "Only SELECT statements are supported" . to_string ( ) ) ,
117+ }
118+ }
119+
120+ fn convert_query ( query : & ast:: Query ) -> Result < LogicalPlan , String > {
121+ let mut limit_val = None ;
122+ let mut offset_val = None ;
123+
124+ if let Some ( limit_clause) = & query. limit_clause {
125+ match limit_clause {
126+ LimitClause :: Limit { limit, offset } => {
127+ limit_val = Some ( parse_limit_expr ( limit) ?) ;
128+ if let Some ( off) = offset {
129+ offset_val = Some ( parse_limit_expr ( off) ?) ;
130+ }
131+ }
132+ LimitClause :: LimitOffset { limit, offset } => {
133+ limit_val = Some ( parse_limit_expr ( limit) ?) ;
134+ offset_val = Some ( parse_limit_expr ( offset) ?) ;
135+ }
136+ }
137+ }
138+
139+ // Body (SetExpr)
140+ let plan = match & * query. body {
141+ SetExpr :: Select ( select) => convert_select ( select) ?,
142+ _ => return Err ( "Only SELECT queries are supported (no UNION, etc.)" . to_string ( ) ) ,
143+ } ;
144+
145+ // Wrap with Limit/Offset
146+ let plan = if let Some ( o) = offset_val {
147+ LogicalPlan :: Offset { input : Box :: new ( plan) , offset : o }
148+ } else {
149+ plan
150+ } ;
151+
152+ let plan = if let Some ( l) = limit_val {
153+ LogicalPlan :: Limit { input : Box :: new ( plan) , limit : l }
154+ } else {
155+ plan
156+ } ;
157+
158+ Ok ( plan)
159+ }
160+
161+ fn parse_limit_expr ( expr : & Expr ) -> Result < usize , String > {
162+ match expr {
163+ Expr :: Value ( val_span) => match & val_span. value {
164+ ast:: Value :: Number ( n, _) => n. parse :: < usize > ( ) . map_err ( |_| "Invalid number" . to_string ( ) ) ,
165+ _ => Err ( "Expected number" . to_string ( ) ) ,
166+ } ,
167+ _ => Err ( "Expected value for limit/offset" . to_string ( ) ) ,
168+ }
169+ }
170+
171+ fn convert_select ( select : & ast:: Select ) -> Result < LogicalPlan , String > {
172+ // 1. FROM (Scan)
173+ if select. from . len ( ) != 1 {
174+ return Err ( "FROM clause must have exactly one table" . to_string ( ) ) ;
175+ }
176+ let table = & select. from [ 0 ] ;
177+ let collection = match & table. relation {
178+ TableFactor :: Table { name, .. } => name. to_string ( ) ,
179+ _ => return Err ( "Unsupported FROM clause" . to_string ( ) ) ,
180+ } ;
181+
182+ let mut plan = LogicalPlan :: Scan { collection } ;
183+
184+ // 2. WHERE (Filter)
185+ if let Some ( selection) = & select. selection {
186+ let predicate = convert_expr ( selection) ?;
187+ plan = LogicalPlan :: Filter {
188+ input : Box :: new ( plan) ,
189+ predicate,
190+ } ;
191+ }
192+
193+ // 3. SELECT (Project)
194+ let mut projections = Vec :: new ( ) ;
195+ for item in & select. projection {
196+ match item {
197+ ast:: SelectItem :: UnnamedExpr ( expr) => {
198+ projections. push ( convert_expr ( expr) ?) ;
199+ }
200+ ast:: SelectItem :: ExprWithAlias { expr, alias : _ } => {
201+ // Ignore alias for now as LogicalPlan doesn't support renaming explicitly yet
202+ projections. push ( convert_expr ( expr) ?) ;
203+ }
204+ ast:: SelectItem :: Wildcard ( _) => {
205+ return Err ( "Wildcard * not supported yet" . to_string ( ) ) ;
206+ }
207+ _ => return Err ( "Unsupported projection item" . to_string ( ) ) ,
208+ }
209+ }
210+
211+ plan = LogicalPlan :: Project {
212+ input : Box :: new ( plan) ,
213+ projections,
214+ } ;
215+
216+ Ok ( plan)
217+ }
218+
219+ fn convert_expr ( expr : & Expr ) -> Result < Expression , String > {
220+ match expr {
221+ Expr :: Identifier ( ident) => Ok ( Expression :: FieldReference ( ident. value . clone ( ) ) ) ,
222+ Expr :: CompoundIdentifier ( idents) => {
223+ let path = idents. iter ( ) . map ( |i| i. value . clone ( ) ) . collect :: < Vec < _ > > ( ) . join ( "." ) ;
224+ Ok ( Expression :: FieldReference ( path) )
225+ }
226+ Expr :: Value ( val_span) => match & val_span. value {
227+ ast:: Value :: Number ( n, _) => {
228+ // Try parse as i64 or f64
229+ if let Ok ( i) = n. parse :: < i64 > ( ) {
230+ Ok ( Expression :: Literal ( serde_json:: Value :: Number ( i. into ( ) ) ) )
231+ } else if let Ok ( f) = n. parse :: < f64 > ( ) {
232+ Ok ( Expression :: Literal ( serde_json:: Value :: Number ( serde_json:: Number :: from_f64 ( f) . ok_or ( "Invalid float" ) ?) ) )
233+ } else {
234+ Err ( "Invalid number" . to_string ( ) )
235+ }
236+ }
237+ ast:: Value :: SingleQuotedString ( s) => Ok ( Expression :: Literal ( Value :: String ( s. clone ( ) ) ) ) ,
238+ ast:: Value :: Boolean ( b) => Ok ( Expression :: Literal ( Value :: Bool ( * b) ) ) ,
239+ ast:: Value :: Null => Ok ( Expression :: Literal ( Value :: Null ) ) ,
240+ _ => Err ( format ! ( "Unsupported literal: {:?}" , val_span. value) ) ,
241+ } ,
242+ Expr :: BinaryOp { left, op, right } => {
243+ let left_expr = Box :: new ( convert_expr ( left) ?) ;
244+ let right_expr = Box :: new ( convert_expr ( right) ?) ;
245+
246+ let ( is_logical, b_op, l_op) = match op {
247+ SqlBinaryOperator :: Eq => ( false , Some ( BinaryOperator :: Eq ) , None ) ,
248+ SqlBinaryOperator :: NotEq => ( false , Some ( BinaryOperator :: Neq ) , None ) ,
249+ SqlBinaryOperator :: Lt => ( false , Some ( BinaryOperator :: Lt ) , None ) ,
250+ SqlBinaryOperator :: LtEq => ( false , Some ( BinaryOperator :: Lte ) , None ) ,
251+ SqlBinaryOperator :: Gt => ( false , Some ( BinaryOperator :: Gt ) , None ) ,
252+ SqlBinaryOperator :: GtEq => ( false , Some ( BinaryOperator :: Gte ) , None ) ,
253+ SqlBinaryOperator :: And => ( true , None , Some ( LogicalOperator :: And ) ) ,
254+ SqlBinaryOperator :: Or => ( true , None , Some ( LogicalOperator :: Or ) ) ,
255+ _ => return Err ( format ! ( "Unsupported binary operator: {:?}" , op) ) ,
256+ } ;
257+
258+ if is_logical {
259+ Ok ( Expression :: Logical {
260+ left : left_expr,
261+ op : l_op. unwrap ( ) ,
262+ right : right_expr,
263+ } )
264+ } else {
265+ Ok ( Expression :: Binary {
266+ left : left_expr,
267+ op : b_op. unwrap ( ) ,
268+ right : right_expr,
269+ } )
270+ }
271+ } ,
272+ Expr :: JsonAccess { .. } => Err ( "JsonAccess not implemented" . to_string ( ) ) ,
273+ _ => Err ( format ! ( "Unsupported expression: {:?}" , expr) ) ,
274+ }
275+ }
276+
277+ #[ cfg( test) ]
278+ mod tests {
279+ use super :: * ;
280+
281+ #[ test]
282+ fn test_parse_insert ( ) {
283+ let sql = r#"INSERT INTO users RECORDS {"name": "Alice", "age": 30}, {"name": "Bob"}"# ;
284+ let stmt = parse ( sql) . unwrap ( ) ;
285+ match stmt {
286+ Statement :: Insert { collection, documents } => {
287+ assert_eq ! ( collection, "users" ) ;
288+ assert_eq ! ( documents. len( ) , 2 ) ;
289+ assert_eq ! ( documents[ 0 ] [ "name" ] , "Alice" ) ;
290+ assert_eq ! ( documents[ 1 ] [ "name" ] , "Bob" ) ;
291+ }
292+ _ => panic ! ( "Expected Insert" ) ,
293+ }
294+ }
295+
296+ #[ test]
297+ fn test_parse_select ( ) {
298+ let sql = "SELECT name, age FROM users WHERE age > 18 AND active = true LIMIT 10 OFFSET 5" ;
299+ let stmt = parse ( sql) . unwrap ( ) ;
300+ match stmt {
301+ Statement :: Select ( plan) => {
302+ // Verify structure: Limit(Offset(Project(Filter(Scan))))
303+ match plan {
304+ LogicalPlan :: Limit { input, limit } => {
305+ assert_eq ! ( limit, 10 ) ;
306+ match * input {
307+ LogicalPlan :: Offset { input, offset } => {
308+ assert_eq ! ( offset, 5 ) ;
309+ match * input {
310+ LogicalPlan :: Project { input, projections } => {
311+ assert_eq ! ( projections. len( ) , 2 ) ;
312+ match * input {
313+ LogicalPlan :: Filter { input, predicate : _ } => {
314+ match * input {
315+ LogicalPlan :: Scan { collection } => {
316+ assert_eq ! ( collection, "users" ) ;
317+ }
318+ _ => panic ! ( "Expected Scan" ) ,
319+ }
320+ }
321+ _ => panic ! ( "Expected Filter" ) ,
322+ }
323+ }
324+ _ => panic ! ( "Expected Project" ) ,
325+ }
326+ }
327+ _ => panic ! ( "Expected Offset" ) ,
328+ }
329+ }
330+ _ => panic ! ( "Expected Limit" ) ,
331+ }
332+ }
333+ _ => panic ! ( "Expected Select" ) ,
334+ }
335+ }
336+ }
0 commit comments