filter_param 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE +21 -0
  4. data/README.md +138 -0
  5. data/lib/filter_param/ast/attribute.rb +15 -0
  6. data/lib/filter_param/ast/expressions.rb +37 -0
  7. data/lib/filter_param/ast/group.rb +13 -0
  8. data/lib/filter_param/ast/literal.rb +26 -0
  9. data/lib/filter_param/ast/literals/boolean.rb +38 -0
  10. data/lib/filter_param/ast/literals/date.rb +33 -0
  11. data/lib/filter_param/ast/literals/date_time.rb +36 -0
  12. data/lib/filter_param/ast/literals/decimal.rb +29 -0
  13. data/lib/filter_param/ast/literals/integer.rb +39 -0
  14. data/lib/filter_param/ast/literals/null.rb +19 -0
  15. data/lib/filter_param/ast/literals/string.rb +43 -0
  16. data/lib/filter_param/ast/node.rb +15 -0
  17. data/lib/filter_param/ast/scope.rb +12 -0
  18. data/lib/filter_param/definition.rb +157 -0
  19. data/lib/filter_param/field.rb +45 -0
  20. data/lib/filter_param/operator.rb +46 -0
  21. data/lib/filter_param/operators/and.rb +13 -0
  22. data/lib/filter_param/operators/case_insensitive_equal.rb +16 -0
  23. data/lib/filter_param/operators/contains.rb +18 -0
  24. data/lib/filter_param/operators/ends_with.rb +18 -0
  25. data/lib/filter_param/operators/equal.rb +21 -0
  26. data/lib/filter_param/operators/field_filter_operator.rb +40 -0
  27. data/lib/filter_param/operators/greater_than.rb +16 -0
  28. data/lib/filter_param/operators/greater_than_equal.rb +16 -0
  29. data/lib/filter_param/operators/group.rb +17 -0
  30. data/lib/filter_param/operators/less_than.rb +16 -0
  31. data/lib/filter_param/operators/less_than_equal.rb +16 -0
  32. data/lib/filter_param/operators/not.rb +16 -0
  33. data/lib/filter_param/operators/not_equal.rb +19 -0
  34. data/lib/filter_param/operators/or.rb +13 -0
  35. data/lib/filter_param/operators/present.rb +21 -0
  36. data/lib/filter_param/operators/starts_with.rb +18 -0
  37. data/lib/filter_param/parser.rb +144 -0
  38. data/lib/filter_param/scope.rb +24 -0
  39. data/lib/filter_param/transformer.rb +37 -0
  40. data/lib/filter_param/transpiler.rb +114 -0
  41. data/lib/filter_param/version.rb +5 -0
  42. data/lib/filter_param.rb +70 -0
  43. metadata +143 -0
@@ -0,0 +1,45 @@
1
+ module FilterParam
2
+ class Field
3
+ TYPES = %i[boolean string integer decimal date datetime].freeze
4
+
5
+ attr_reader :type, :name
6
+
7
+ def initialize(name, type, options = {})
8
+ @name = name
9
+ @type = field_type(type)
10
+ @rename = field_rename(options[:rename])
11
+ @value_transformer = options[:value]
12
+ end
13
+
14
+ def transform_value(value)
15
+ return value if value_transformer.blank?
16
+
17
+ value_transformer.call(value)
18
+ end
19
+
20
+ def actual_name
21
+ rename.presence || name
22
+ end
23
+
24
+ def allow_operator?(operator)
25
+ true
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :value_transformer, :rename
31
+
32
+ def field_rename(rename)
33
+ return rename.call(name) if rename.is_a?(Proc)
34
+
35
+ rename
36
+ end
37
+
38
+ def field_type(type)
39
+ type = (type.presence || :string).to_sym
40
+ return type if type.in?(TYPES)
41
+
42
+ raise UnknownType.new("Unknown type '#{type}' for field '#{name}'. Allowed types: #{TYPES}.")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ module FilterParam
2
+ class Operator
3
+ class << self
4
+ def type
5
+ return :unary if method(:sql).arity == 1
6
+
7
+ :binary
8
+ end
9
+
10
+ def operator_tag(operator_tag)
11
+ @operator_tag ||= operator_tag
12
+ end
13
+
14
+ def register(operator_clazz)
15
+ operator_tag = operator_clazz.tag
16
+ registry[operator_tag] = operator_clazz
17
+ end
18
+
19
+ def tag
20
+ @operator_tag
21
+ end
22
+
23
+ def for(operator_tag)
24
+ registry[operator_tag]
25
+ end
26
+
27
+ def binaries
28
+ with_type(:binary).map(&:tag)
29
+ end
30
+
31
+ def unaries
32
+ with_type(:unary).map(&:tag)
33
+ end
34
+
35
+ private
36
+
37
+ def with_type(type)
38
+ registry.values.select { |op| op < self && op.type == type }
39
+ end
40
+
41
+ def registry
42
+ @@registry ||= {}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module FilterParam
2
+ module Operators
3
+ class And < Operator
4
+ operator_tag :and
5
+
6
+ def self.sql(left, right)
7
+ "#{left} AND #{right}"
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ FilterParam::Operator.register(FilterParam::Operators::And)
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class CaseInsensitiveEqual < FieldFilterOperator
4
+ operator_tag :eq_ci
5
+ operand_data_type :string
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ "lower(#{field.actual_name}) = #{sql_quote(literal.value.downcase)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::CaseInsensitiveEqual)
@@ -0,0 +1,18 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Contains < FieldFilterOperator
4
+ operator_tag :co
5
+ operand_data_type :string
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ pattern = "%#{literal.value}%"
11
+
12
+ "#{field.actual_name} LIKE #{sql_quote(pattern)}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ FilterParam::Operator.register(FilterParam::Operators::Contains)
@@ -0,0 +1,18 @@
1
+ module FilterParam
2
+ module Operators
3
+ class EndsWith < FieldFilterOperator
4
+ operator_tag :ew
5
+ operand_data_type :string
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ pattern = "%#{literal.value}"
11
+
12
+ "#{field.actual_name} LIKE #{sql_quote(pattern)}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ FilterParam::Operator.register(FilterParam::Operators::EndsWith)
@@ -0,0 +1,21 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Equal < FieldFilterOperator
4
+ operator_tag :eq
5
+
6
+ def self.sql(field, literal)
7
+ super
8
+
9
+ return "#{field.actual_name} IS NULL" if literal.value.nil?
10
+
11
+ "#{field.actual_name} = #{sql_quote(literal.value)}"
12
+ end
13
+
14
+ def self.negated_sql(field, literal)
15
+ Operators::NotEqual.sql(field, literal)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ FilterParam::Operator.register(FilterParam::Operators::Equal)
@@ -0,0 +1,40 @@
1
+ module FilterParam
2
+ module Operators
3
+ class FieldFilterOperator < Operator
4
+ class << self
5
+ attr_reader :operand_data_types
6
+
7
+ def operand_data_type(*data_types)
8
+ @operand_data_types ||= Set.new
9
+ @operand_data_types.merge(data_types)
10
+ @operand_data_types
11
+ end
12
+
13
+ def sql(field, literal)
14
+ validate_field!(field)
15
+ validate_literal!(literal)
16
+ end
17
+
18
+ private
19
+
20
+ def validate_field!(field)
21
+ field.allow_operator?(tag)
22
+ end
23
+
24
+ def validate_literal!(literal)
25
+ return if literal.nil?
26
+ return if operand_data_types.nil?
27
+ return if literal.data_type.in?(operand_data_types)
28
+
29
+ raise FilterParam::InvalidLiteral.new(
30
+ "Unexpected #{literal.data_type} operand for operator '#{tag}'."
31
+ )
32
+ end
33
+
34
+ def sql_quote(value)
35
+ ActiveRecord::Base.connection.quote(value)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class GreaterThan < FieldFilterOperator
4
+ operator_tag :gt
5
+ operand_data_type :string, :integer, :decimal, :date, :datetime
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ "#{field.actual_name} > #{sql_quote(literal.value)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::GreaterThan)
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class GreaterThanEqual < FieldFilterOperator
4
+ operator_tag :ge
5
+ operand_data_type :string, :integer, :decimal, :date, :datetime
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ "#{field.actual_name} >= #{sql_quote(literal.value)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::GreaterThanEqual)
@@ -0,0 +1,17 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Group < Operator
4
+ operator_tag :group
5
+
6
+ def self.sql(expression)
7
+ "(#{expression})"
8
+ end
9
+
10
+ def self.negated_sql(expression)
11
+ "NOT (#{expression})"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ FilterParam::Operator.register(FilterParam::Operators::Group)
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class LessThan < FieldFilterOperator
4
+ operator_tag :lt
5
+ operand_data_type :string, :integer, :decimal, :date, :datetime
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ "#{field.actual_name} < #{sql_quote(literal.value)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::LessThan)
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class LessThanEqual < FieldFilterOperator
4
+ operator_tag :le
5
+ operand_data_type :string, :integer, :decimal, :date, :datetime
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ "#{field.actual_name} <= #{sql_quote(literal.value)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::LessThanEqual)
@@ -0,0 +1,16 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Not < Operator
4
+ operator_tag :not
5
+
6
+ def self.sql(expression_operator, *expression_operands)
7
+ operator = Operator.for(expression_operator)
8
+ return operator.negated_sql(*expression_operands) if operator.respond_to?(:negated_sql)
9
+
10
+ "NOT #{operator.sql(*expression_operands)}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ FilterParam::Operator.register(FilterParam::Operators::Not)
@@ -0,0 +1,19 @@
1
+ module FilterParam
2
+ module Operators
3
+ class NotEqual < FieldFilterOperator
4
+ operator_tag :neq
5
+
6
+ def self.sql(field, literal)
7
+ return "#{field.actual_name} IS NOT NULL" if literal.value.nil?
8
+
9
+ "#{field.actual_name} != #{sql_quote(literal.value)}"
10
+ end
11
+
12
+ def self.negated_sql(field, literal)
13
+ Operators::Equal.sql(field, literal)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ FilterParam::Operator.register(FilterParam::Operators::NotEqual)
@@ -0,0 +1,13 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Or < Operator
4
+ operator_tag :or
5
+
6
+ def self.sql(left, right)
7
+ "#{left} OR #{right}"
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ FilterParam::Operator.register(FilterParam::Operators::Or)
@@ -0,0 +1,21 @@
1
+ module FilterParam
2
+ module Operators
3
+ class Present < FieldFilterOperator
4
+ operator_tag :pr
5
+
6
+ def self.sql(field)
7
+ return "#{field.actual_name} IS NOT NULL" unless field.type == :string
8
+
9
+ "(#{field.actual_name} IS NOT NULL AND TRIM(#{field.actual_name}) != '')"
10
+ end
11
+
12
+ def self.negated_sql(field)
13
+ return "#{field.actual_name} IS NULL" unless field.type == :string
14
+
15
+ "(#{field.actual_name} IS NULL OR TRIM(#{field.actual_name}) = '')"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ FilterParam::Operator.register(FilterParam::Operators::Present)
@@ -0,0 +1,18 @@
1
+ module FilterParam
2
+ module Operators
3
+ class StartsWith < FieldFilterOperator
4
+ operator_tag :sw
5
+ operand_data_type :string
6
+
7
+ def self.sql(field, literal)
8
+ super
9
+
10
+ pattern = "#{literal.value}%"
11
+
12
+ "#{field.actual_name} LIKE #{sql_quote(pattern)}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ FilterParam::Operator.register(FilterParam::Operators::StartsWith)
@@ -0,0 +1,144 @@
1
+ module FilterParam
2
+ class Parser < Parslet::Parser
3
+ rule(:space) { match("\s").repeat(1).ignore }
4
+ rule(:space?) { space.maybe }
5
+ rule(:dot) { str(".") }
6
+ rule(:lparen) { str("(") }
7
+ rule(:rparen) { str(")") }
8
+ rule(:escape_seq) { str('\\').present? >> str('\\') >> any }
9
+ rule(:double_quote) { str('"') }
10
+ rule(:single_quote) { str("'") }
11
+ rule(:digit) { match("[0-9]") }
12
+ rule(:zero) { str("0") }
13
+ rule(:non_zero_digit) { match("[1-9]") }
14
+ rule(:sig_number) { non_zero_digit >> digit.repeat(1).maybe }
15
+ rule(:zero_nonsig) { zero.repeat(0).maybe.ignore }
16
+ rule(:hyphen) { str("-") }
17
+ rule(:identifier) { match("[a-zA-Z_]") >> digit.maybe }
18
+ rule(:table) { identifier.repeat(1) >> dot }
19
+ rule(:attribute) { (table.maybe >> identifier.repeat(1)).as(:attribute) }
20
+ rule(:scope_name) { identifier.repeat(1) }
21
+
22
+ # Literals / types
23
+ rule(:null) { str("null").as(:null) }
24
+ rule(:boolean) { (str("true") | str("false")).as(:boolean) }
25
+
26
+ rule(:integer) do
27
+ (
28
+ (hyphen.maybe >> zero_nonsig >> sig_number) |
29
+ (hyphen.maybe.ignore >> zero >> zero_nonsig)
30
+ ).as(:integer)
31
+ end
32
+
33
+ rule(:decimal) do
34
+ (
35
+ (hyphen.maybe >> zero_nonsig >> sig_number >> dot >> digit.repeat(1)) |
36
+ (hyphen.maybe >> zero >> zero_nonsig >> dot >> digit.repeat(1))
37
+ ).as(:decimal)
38
+ end
39
+
40
+ rule(:string) do
41
+ (single_quote >> (escape_seq | match("[^\']")).repeat.as(:string) >> single_quote) |
42
+ (double_quote >> (escape_seq | match("[^\"]")).repeat.as(:string) >> double_quote)
43
+ end
44
+ rule(:date_yyyy) { digit.repeat(4) }
45
+ rule(:date_mm) { (zero >> non_zero_digit) | (str("1") >> match("[0-2]")) }
46
+ rule(:date_md) { (zero >> non_zero_digit) | (match("[1-2]") >> digit) | (str("3") >> match("[0-1]")) }
47
+ rule(:date_iso8601) { date_yyyy >> hyphen >> date_mm >> hyphen >> date_md }
48
+ rule(:date) { quoted date_iso8601.as(:date) }
49
+ rule(:time_hh_mi) do
50
+ (((zero | str("1")) >> digit) | (str("2") >> match("[0-3]"))) >>
51
+ str(":").maybe >> ((zero >> digit) | (match("[1-5]") >> digit))
52
+ end
53
+ rule(:time_hh_mi_ss) { time_hh_mi >> str(":") >> ((zero >> digit) | (match("[1-5]") >> digit)) }
54
+ rule(:time_hh_mi_ss_sss) { time_hh_mi_ss >> dot >> digit.repeat(3, 6) }
55
+ rule(:time_tz) { str("Z") | (match("[\+\-]") >> time_hh_mi) }
56
+ rule(:datetime_iso8601) { date_iso8601 >> str("T") >> (time_hh_mi_ss_sss | time_hh_mi_ss) >> time_tz }
57
+ rule(:datetime) { quoted datetime_iso8601.as(:datetime) }
58
+
59
+ # Operations
60
+ rule(:op_attr_binary) do
61
+ binary_attr_operators.as(:operator)
62
+ end
63
+ rule(:op_attr_unary) { (unary_attr_operators).as(:operator) }
64
+ rule(:op_logic_binary) { (str("and") | str("or")).as(:operator) }
65
+ rule(:op_logic_unary) { str("not").as(:operator) }
66
+
67
+ # Expressions
68
+ rule(:literal) { (null | boolean | decimal | integer | datetime | date | string) }
69
+ rule(:literal_paren) { lparen >> space? >> (literal | literal_paren) >> space? >> rparen }
70
+ rule(:attr_value) { literal_paren | (space >> (literal | literal_paren)) }
71
+ rule(:attr_exp) { (attribute >> space >> (op_attr_unary | (op_attr_binary >> attr_value.as(:val)))).as(:exp) }
72
+ rule(:group) do
73
+ empty_group.ignore |
74
+ (lparen >> space? >> (binary_exp | unary_exp | attr_exp) >> space? >> rparen).as(:group) |
75
+ (lparen >> space? >> group >> space? >> rparen)
76
+ end
77
+ rule(:empty_group) do
78
+ (lparen >> space? >> empty_group >> space? >> rparen) | (lparen >> space? >> rparen)
79
+ end
80
+ rule(:empty_exp) { (space | str("")).ignore }
81
+ rule(:scope_args) { literal >> space? >> (str(",") >> space? >> literal).repeat(0) }
82
+ rule(:scope) { scope_name.as(:scope) >> lparen >> space? >> scope_args.maybe.as(:args) >> space? >> rparen }
83
+ rule(:primary) { group | attr_exp | scope }
84
+
85
+ rule(:unary_exp) do
86
+ (op_logic_unary >> (space | lparen.present?) >> primary.as(:right)).as(:exp) | primary
87
+ end
88
+ rule(:binary_exp) do
89
+ (
90
+ unary_exp.as(:left) >> space >> op_logic_binary >> ((space | lparen.present?) >> exp).as(:right)
91
+ ).as(:exp) | unary_exp
92
+ end
93
+
94
+ rule(:exp) { (space? >> binary_exp >> space?) }
95
+ rule(:exp_root) { exp | empty_exp }
96
+ root(:exp_root)
97
+
98
+ def parse(expression, options = {})
99
+ super(expression, options)
100
+ rescue Parslet::ParseFailed => e
101
+ parse_cause = e.parse_failure_cause.children.last
102
+
103
+ raise_parse_error!(parse_cause)
104
+ end
105
+
106
+ private
107
+
108
+ def quoted(atom_or_seq)
109
+ (single_quote >> atom_or_seq >> single_quote) | (double_quote >> atom_or_seq >> double_quote)
110
+ end
111
+
112
+ def binary_attr_operators
113
+ @@binary_attr_ops = operators_to_atoms(Operators::FieldFilterOperator.binaries.map(&:to_s))
114
+ end
115
+
116
+ def unary_attr_operators
117
+ @@unary_attr_ops = operators_to_atoms(Operators::FieldFilterOperator.unaries.map(&:to_s))
118
+ end
119
+
120
+ def operators_to_atoms(operators)
121
+ operators.sort_by(&:length)
122
+ .reverse
123
+ .map { |tag| str(tag) }
124
+ .reduce(:|)
125
+ end
126
+
127
+ def raise_parse_error!(parse_cause)
128
+ parse_cause = parse_cause.to_s
129
+ invalid_expression = "Filter expression syntax error."
130
+
131
+ if parse_cause.start_with?("Expected ")
132
+ parse_cause = invalid_expression
133
+ else
134
+ unexpected_token = "Unexpected token"
135
+
136
+ parse_cause.sub!("Don't know what to do with", unexpected_token)
137
+ parse_cause.sub!(/(Failed to match).*.(at line 1)/, "#{unexpected_token} at")
138
+ parse_cause.sub!(/(at line 1)/, "at")
139
+ end
140
+
141
+ raise ParseError.new(parse_cause)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,24 @@
1
+ module FilterParam
2
+ class Scope
3
+ attr_reader :name
4
+
5
+ def initialize(name, options = {})
6
+ @name = name
7
+ @rename = scope_rename(options[:rename])
8
+ end
9
+
10
+ def actual_name
11
+ rename.presence || name
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :rename
17
+
18
+ def scope_rename(rename)
19
+ return rename.call(name) if rename.is_a?(Proc)
20
+
21
+ rename
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module FilterParam
2
+ class Transformer < Parslet::Transform
3
+ include AST
4
+
5
+ rule(null: simple(:null)) { Literals::Null.instance }
6
+ rule(string: simple(:value)) { Literals::String.new(value) }
7
+ rule(boolean: simple(:value)) { value == "true" ? Literals::Boolean::TRUE : Literals::Boolean::FALSE }
8
+ rule(integer: simple(:value)) { Literals::Integer.new(value) }
9
+ rule(decimal: simple(:value)) { Literals::Decimal.new(value) }
10
+ rule(date: simple(:value)) { Literals::Date.new(value) }
11
+ rule(datetime: simple(:value)) { Literals::DateTime.new(value) }
12
+ rule(exp: simple(:exp)) { exp }
13
+ rule(group: simple(:exp)) { Group.new(exp) }
14
+ rule(scope: simple(:name), args: simple(:scope_arg)) do
15
+ scope_args = scope_arg.nil? ? [] : [scope_arg]
16
+
17
+ AST::Scope.new(name, scope_args)
18
+ end
19
+ rule(scope: simple(:name), args: sequence(:scope_args)) { AST::Scope.new(name, scope_args) }
20
+ rule(operator: simple(:operator), right: simple(:operand)) do
21
+ Expressions::UnaryExpression.new(operator, operand)
22
+ end
23
+ rule(attribute: simple(:attribute_name), operator: simple(:operator)) do
24
+ attribute = Attribute.new(attribute_name)
25
+
26
+ Expressions::UnaryExpression.new(operator, attribute)
27
+ end
28
+ rule(left: simple(:left), operator: simple(:operator), right: simple(:right)) do
29
+ Expressions::BinaryExpression.new(operator, left, right)
30
+ end
31
+ rule(attribute: simple(:attribute_name), operator: simple(:operator), val: simple(:literal)) do
32
+ attribute = Attribute.new(attribute_name)
33
+
34
+ Expressions::BinaryExpression.new(operator, attribute, literal)
35
+ end
36
+ end
37
+ end