@@ -4,6 +4,7 @@ defmodule Electric.Shapes.Querying do
44 alias Electric.Shapes.Shape
55 alias Electric.Shapes.Shape.SubqueryMoves
66 alias Electric.Replication.Eval.Decomposer
7+ alias Electric.Replication.Eval.Parser . { Const , Ref , Func , Array }
78 alias Electric.Telemetry.OpenTelemetry
89
910 @ value_prefix SubqueryMoves . value_prefix ( )
@@ -333,11 +334,102 @@ defmodule Electric.Shapes.Querying do
333334 end
334335
335336 # Generate SQL for a non-subquery condition
336- # This is complex as we'd need to convert the AST back to SQL
337- # For now, return true (since the row is in the result, the condition is satisfied)
338- defp generate_non_subquery_condition_sql ( _subexpr ) do
339- # Non-subquery conditions in the WHERE clause are already satisfied for returned rows
340- # This is a simplification - for full accuracy we'd need AST-to-SQL conversion
337+ # Convert the AST back to SQL for accurate evaluation of OR conditions
338+ defp generate_non_subquery_condition_sql ( subexpr ) do
339+ ast_to_sql ( subexpr . ast )
340+ end
341+
342+ # Convert AST to SQL - handles common comparison operators
343+ # Note: Operator names are stored with surrounding quotes (e.g., "\"=\"" for equals)
344+ defp ast_to_sql ( % Func { name: "\" =\" " , args: [ left , right ] } ) do
345+ "(#{ ast_to_sql ( left ) } = #{ ast_to_sql ( right ) } )"
346+ end
347+
348+ defp ast_to_sql ( % Func { name: "\" <>\" " , args: [ left , right ] } ) do
349+ "(#{ ast_to_sql ( left ) } <> #{ ast_to_sql ( right ) } )"
350+ end
351+
352+ defp ast_to_sql ( % Func { name: "\" <\" " , args: [ left , right ] } ) do
353+ "(#{ ast_to_sql ( left ) } < #{ ast_to_sql ( right ) } )"
354+ end
355+
356+ defp ast_to_sql ( % Func { name: "\" >\" " , args: [ left , right ] } ) do
357+ "(#{ ast_to_sql ( left ) } > #{ ast_to_sql ( right ) } )"
358+ end
359+
360+ defp ast_to_sql ( % Func { name: "\" <=\" " , args: [ left , right ] } ) do
361+ "(#{ ast_to_sql ( left ) } <= #{ ast_to_sql ( right ) } )"
362+ end
363+
364+ defp ast_to_sql ( % Func { name: "\" >=\" " , args: [ left , right ] } ) do
365+ "(#{ ast_to_sql ( left ) } >= #{ ast_to_sql ( right ) } )"
366+ end
367+
368+ defp ast_to_sql ( % Func { name: "\" ~~\" " , args: [ left , right ] } ) do
369+ "(#{ ast_to_sql ( left ) } LIKE #{ ast_to_sql ( right ) } )"
370+ end
371+
372+ defp ast_to_sql ( % Func { name: "\" ~~*\" " , args: [ left , right ] } ) do
373+ "(#{ ast_to_sql ( left ) } ILIKE #{ ast_to_sql ( right ) } )"
374+ end
375+
376+ defp ast_to_sql ( % Func { name: "is null" , args: [ arg ] } ) do
377+ "(#{ ast_to_sql ( arg ) } IS NULL)"
378+ end
379+
380+ defp ast_to_sql ( % Func { name: "is not null" , args: [ arg ] } ) do
381+ "(#{ ast_to_sql ( arg ) } IS NOT NULL)"
382+ end
383+
384+ defp ast_to_sql ( % Func { name: "in" , args: [ left , % Array { elements: elements } ] } ) do
385+ values = Enum . map ( elements , & ast_to_sql / 1 ) |> Enum . join ( ", " )
386+ "(#{ ast_to_sql ( left ) } IN (#{ values } ))"
387+ end
388+
389+ defp ast_to_sql ( % Func { name: "not" , args: [ inner ] } ) do
390+ "(NOT #{ ast_to_sql ( inner ) } )"
391+ end
392+
393+ defp ast_to_sql ( % Func { name: "and" , args: args } ) do
394+ conditions = Enum . map ( args , & ast_to_sql / 1 ) |> Enum . join ( " AND " )
395+ "(#{ conditions } )"
396+ end
397+
398+ defp ast_to_sql ( % Func { name: "or" , args: args } ) do
399+ conditions = Enum . map ( args , & ast_to_sql / 1 ) |> Enum . join ( " OR " )
400+ "(#{ conditions } )"
401+ end
402+
403+ defp ast_to_sql ( % Ref { path: path } ) do
404+ # Quote the column name
405+ ~s| "#{ Enum . join ( path , "\" .\" " ) } "|
406+ end
407+
408+ defp ast_to_sql ( % Const { value: nil } ) do
409+ "NULL"
410+ end
411+
412+ defp ast_to_sql ( % Const { value: value } ) when is_binary ( value ) do
413+ # Escape single quotes in the value
414+ escaped = String . replace ( value , "'" , "''" )
415+ "'#{ escaped } '"
416+ end
417+
418+ defp ast_to_sql ( % Const { value: value } ) when is_integer ( value ) or is_float ( value ) do
419+ "#{ value } "
420+ end
421+
422+ defp ast_to_sql ( % Const { value: true } ) do
423+ "true"
424+ end
425+
426+ defp ast_to_sql ( % Const { value: false } ) do
427+ "false"
428+ end
429+
430+ defp ast_to_sql ( _other ) do
431+ # Fallback for unsupported AST nodes - return true to avoid breaking
432+ # This is conservative: if we can't convert, assume the condition could be satisfied
341433 "true"
342434 end
343435
0 commit comments