filter_param 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +138 -0
- data/lib/filter_param/ast/attribute.rb +15 -0
- data/lib/filter_param/ast/expressions.rb +37 -0
- data/lib/filter_param/ast/group.rb +13 -0
- data/lib/filter_param/ast/literal.rb +26 -0
- data/lib/filter_param/ast/literals/boolean.rb +38 -0
- data/lib/filter_param/ast/literals/date.rb +33 -0
- data/lib/filter_param/ast/literals/date_time.rb +36 -0
- data/lib/filter_param/ast/literals/decimal.rb +29 -0
- data/lib/filter_param/ast/literals/integer.rb +39 -0
- data/lib/filter_param/ast/literals/null.rb +19 -0
- data/lib/filter_param/ast/literals/string.rb +43 -0
- data/lib/filter_param/ast/node.rb +15 -0
- data/lib/filter_param/ast/scope.rb +12 -0
- data/lib/filter_param/definition.rb +157 -0
- data/lib/filter_param/field.rb +45 -0
- data/lib/filter_param/operator.rb +46 -0
- data/lib/filter_param/operators/and.rb +13 -0
- data/lib/filter_param/operators/case_insensitive_equal.rb +16 -0
- data/lib/filter_param/operators/contains.rb +18 -0
- data/lib/filter_param/operators/ends_with.rb +18 -0
- data/lib/filter_param/operators/equal.rb +21 -0
- data/lib/filter_param/operators/field_filter_operator.rb +40 -0
- data/lib/filter_param/operators/greater_than.rb +16 -0
- data/lib/filter_param/operators/greater_than_equal.rb +16 -0
- data/lib/filter_param/operators/group.rb +17 -0
- data/lib/filter_param/operators/less_than.rb +16 -0
- data/lib/filter_param/operators/less_than_equal.rb +16 -0
- data/lib/filter_param/operators/not.rb +16 -0
- data/lib/filter_param/operators/not_equal.rb +19 -0
- data/lib/filter_param/operators/or.rb +13 -0
- data/lib/filter_param/operators/present.rb +21 -0
- data/lib/filter_param/operators/starts_with.rb +18 -0
- data/lib/filter_param/parser.rb +144 -0
- data/lib/filter_param/scope.rb +24 -0
- data/lib/filter_param/transformer.rb +37 -0
- data/lib/filter_param/transpiler.rb +114 -0
- data/lib/filter_param/version.rb +5 -0
- data/lib/filter_param.rb +70 -0
- 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,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,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
|