appfuel 0.2.3 → 0.2.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +78 -1
- data/appfuel.gemspec +1 -0
- data/docs/images/appfuel_basic_flow.png +0 -0
- data/lib/appfuel/application/app_container.rb +24 -1
- data/lib/appfuel/application/container_class_registration.rb +29 -4
- data/lib/appfuel/application/root.rb +1 -0
- data/lib/appfuel/application.rb +0 -1
- data/lib/appfuel/domain/base_criteria.rb +171 -0
- data/lib/appfuel/domain/criteria_builder.rb +248 -0
- data/lib/appfuel/domain/criteria_settings.rb +156 -0
- data/lib/appfuel/domain/dsl.rb +5 -1
- data/lib/appfuel/domain/entity.rb +1 -2
- data/lib/appfuel/domain/exists_criteria.rb +57 -0
- data/lib/appfuel/domain/expr.rb +66 -97
- data/lib/appfuel/domain/expr_conjunction.rb +27 -0
- data/lib/appfuel/domain/expr_parser.rb +199 -0
- data/lib/appfuel/domain/expr_transform.rb +68 -0
- data/lib/appfuel/domain/search_criteria.rb +137 -0
- data/lib/appfuel/domain.rb +6 -1
- data/lib/appfuel/feature/initializer.rb +5 -0
- data/lib/appfuel/handler/action.rb +3 -0
- data/lib/appfuel/handler/base.rb +11 -1
- data/lib/appfuel/handler/command.rb +4 -0
- data/lib/appfuel/repository/base.rb +16 -2
- data/lib/appfuel/repository/mapper.rb +41 -7
- data/lib/appfuel/repository/mapping_dsl.rb +4 -4
- data/lib/appfuel/repository/mapping_entry.rb +2 -2
- data/lib/appfuel/repository.rb +0 -1
- data/lib/appfuel/storage/db/active_record_model.rb +32 -28
- data/lib/appfuel/storage/db/mapper.rb +38 -125
- data/lib/appfuel/storage/db/repository.rb +6 -10
- data/lib/appfuel/storage/memory/repository.rb +4 -0
- data/lib/appfuel/types.rb +0 -1
- data/lib/appfuel/version.rb +1 -1
- data/lib/appfuel.rb +6 -10
- metadata +26 -7
- data/lib/appfuel/application/container_key.rb +0 -201
- data/lib/appfuel/application/qualify_container_key.rb +0 -76
- data/lib/appfuel/db_model.rb +0 -16
- data/lib/appfuel/domain/criteria.rb +0 -436
- data/lib/appfuel/repository/mapping_registry.rb +0 -121
@@ -0,0 +1,156 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Domain
|
3
|
+
|
4
|
+
# The Criteria represents the interface between the repositories and actions
|
5
|
+
# or commands. The allow you to find entities in the application storage (
|
6
|
+
# a database) without knowledge of that storage system. The criteria will
|
7
|
+
# always refer to its queries in the domain language for which the repo is
|
8
|
+
# responsible for mapping that query to its persistence layer.
|
9
|
+
#
|
10
|
+
# settings:
|
11
|
+
# page: 1
|
12
|
+
# per_page: 2
|
13
|
+
# disable_pagination
|
14
|
+
# first
|
15
|
+
# last
|
16
|
+
# all
|
17
|
+
# error_on_empty
|
18
|
+
# parser
|
19
|
+
# transform
|
20
|
+
# search_name
|
21
|
+
#
|
22
|
+
class CriteriaSettings
|
23
|
+
DEFAULT_PAGE = 1
|
24
|
+
DEFAULT_PER_PAGE = 20
|
25
|
+
|
26
|
+
attr_reader :parser, :transform
|
27
|
+
|
28
|
+
# @param domain [String] fully qualified domain name
|
29
|
+
# @param opts [Hash] options for initializing criteria
|
30
|
+
# @return [Criteria]
|
31
|
+
def initialize(settings = {})
|
32
|
+
@parser = settings[:expr_parser] || ExprParser.new
|
33
|
+
@transform = settings[:expr_transform] || ExprTransform.new
|
34
|
+
|
35
|
+
|
36
|
+
empty_dataset_is_valid!
|
37
|
+
enable_pagination
|
38
|
+
disable_all
|
39
|
+
disable_first
|
40
|
+
disable_last
|
41
|
+
|
42
|
+
if settings[:error_on_empty] == true
|
43
|
+
error_on_empty_dataset!
|
44
|
+
end
|
45
|
+
|
46
|
+
if settings[:disable_pagination] == true
|
47
|
+
disable_pagination
|
48
|
+
end
|
49
|
+
|
50
|
+
if settings[:first] == true
|
51
|
+
first
|
52
|
+
elsif settings[:last] == true
|
53
|
+
last
|
54
|
+
elsif settings[:all]
|
55
|
+
all
|
56
|
+
end
|
57
|
+
|
58
|
+
page(settings[:page] || DEFAULT_PAGE)
|
59
|
+
per_page(settings[:per_page] || DEFAULT_PER_PAGE)
|
60
|
+
end
|
61
|
+
|
62
|
+
def disable_pagination?
|
63
|
+
@disable_pagination
|
64
|
+
end
|
65
|
+
|
66
|
+
def enable_pagination
|
67
|
+
@disable_pagination = false
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def disable_pagination
|
72
|
+
@disable_pagination = true
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def page(value = nil)
|
77
|
+
return @page if value.nil?
|
78
|
+
@page = Integer(value)
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def per_page(value = nil)
|
83
|
+
return @per_page if value.nil?
|
84
|
+
@per_page = Integer(value)
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def all?
|
89
|
+
@all
|
90
|
+
end
|
91
|
+
|
92
|
+
def all
|
93
|
+
@all = true
|
94
|
+
disable_first
|
95
|
+
disable_last
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def first?
|
100
|
+
@first
|
101
|
+
end
|
102
|
+
|
103
|
+
def first
|
104
|
+
@first = true
|
105
|
+
disable_last
|
106
|
+
disable_all
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def last?
|
111
|
+
@last
|
112
|
+
end
|
113
|
+
|
114
|
+
def last
|
115
|
+
@last = true
|
116
|
+
disable_first
|
117
|
+
disable_all
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
# Tells the repo to return an error when entity is not found
|
122
|
+
#
|
123
|
+
# @return SearchSettings
|
124
|
+
def error_on_empty_dataset!
|
125
|
+
@error_on_empty = true
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Tells the repo to return and empty collection, or nil if single is
|
130
|
+
# invoked, if the entity is not found
|
131
|
+
#
|
132
|
+
# @return SearchSettings
|
133
|
+
def empty_dataset_is_valid!
|
134
|
+
@error_on_empty = false
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
def error_on_empty_dataset?
|
139
|
+
@error_on_empty
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def disable_all
|
144
|
+
@all = false
|
145
|
+
end
|
146
|
+
|
147
|
+
def disable_first
|
148
|
+
@first = false
|
149
|
+
end
|
150
|
+
|
151
|
+
def disable_last
|
152
|
+
@last = false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/appfuel/domain/dsl.rb
CHANGED
@@ -27,10 +27,14 @@ module Appfuel
|
|
27
27
|
klass.equalizer = Dry::Equalizer.new(*schema.keys)
|
28
28
|
klass.send(:include, klass.equalizer)
|
29
29
|
|
30
|
-
|
30
|
+
stage_class_for_registration(klass)
|
31
31
|
Types.register_domain(klass)
|
32
32
|
end
|
33
33
|
|
34
|
+
def container_class_type
|
35
|
+
'domains'
|
36
|
+
end
|
37
|
+
|
34
38
|
def default?
|
35
39
|
false
|
36
40
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Domain
|
3
|
+
|
4
|
+
# The Criteria represents the interface between the repositories and actions
|
5
|
+
# or commands. The allow you to find entities in the application storage (
|
6
|
+
# a database) without knowledge of that storage system. The criteria will
|
7
|
+
# always refer to its queries in the domain language for which the repo is
|
8
|
+
# responsible for mapping that query to its persistence layer.
|
9
|
+
#
|
10
|
+
# global.user
|
11
|
+
# memberships.user
|
12
|
+
#
|
13
|
+
# exist: 'foo.bar exists id = 6'
|
14
|
+
#
|
15
|
+
# exits:
|
16
|
+
# domain: 'foo.bar'
|
17
|
+
# expr: 'id = 6'
|
18
|
+
#
|
19
|
+
# settings:
|
20
|
+
# error_on_empty
|
21
|
+
# parser
|
22
|
+
# transform
|
23
|
+
#
|
24
|
+
#
|
25
|
+
class ExistsCriteria < BaseCriteria
|
26
|
+
#
|
27
|
+
# @param domain [String] fully qualified domain name
|
28
|
+
# @param opts [Hash] options for initializing criteria
|
29
|
+
# @return [Criteria]
|
30
|
+
def initialize(domain_name, data = {})
|
31
|
+
super
|
32
|
+
expr(data[:expr]) if data[:expr]
|
33
|
+
end
|
34
|
+
|
35
|
+
def filter(str)
|
36
|
+
domain_expr = parse_expr(str)
|
37
|
+
if filters?
|
38
|
+
fail "A filter expression has already been assigned"
|
39
|
+
end
|
40
|
+
|
41
|
+
if domain_expr.conjunction?
|
42
|
+
fail "Only simple domain expressions are allowed for exists criteria"
|
43
|
+
end
|
44
|
+
|
45
|
+
if domain_expr.qualified?
|
46
|
+
fail "Only allows relative domain attributes"
|
47
|
+
end
|
48
|
+
|
49
|
+
@filters = qualify_expr(domain_expr)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/appfuel/domain/expr.rb
CHANGED
@@ -1,126 +1,95 @@
|
|
1
1
|
module Appfuel
|
2
2
|
module Domain
|
3
3
|
# Domain expressions are used mostly by the criteria to describe filter
|
4
|
-
# conditions. The class represents a basic expression like "id
|
5
|
-
# problem with this expression is that
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
4
|
+
# conditions. The class represents a basic expression like "id = 6", the
|
5
|
+
# problem with this expression is that "id" is relative to the domain
|
6
|
+
# represented by the criteria. In order to convert that expression to a
|
7
|
+
# storage expression for a db, that expression must be fully qualified in
|
8
|
+
# the form of "features.feature_name.domain.id = 6" so that the mapper can
|
9
|
+
# correctly map to database attributes. This class provides the necessary
|
10
|
+
# interfaces to allow a criteria to qualify all of its relative expressions.
|
11
|
+
# It also allows fully qualifed expressions to be used.
|
9
12
|
class Expr
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# @example
|
29
|
-
# feature domain
|
30
|
-
# Expr.new('foo.bar', 'id', eq: 6)
|
31
|
-
#
|
32
|
-
# or
|
33
|
-
# global domain
|
34
|
-
# Expr.new('bar', 'name', like: '%Bob%')
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# @param domain [String] fully qualified domain name
|
38
|
-
# @param domain_attr [String, Symbol] attribute name
|
39
|
-
# @param data [Hash] holds operator and value
|
40
|
-
# @option data [Symbol] the key is the operator and value is the value
|
41
|
-
#
|
42
|
-
# @return [Expr]
|
43
|
-
def initialize(domain, domain_attr, data)
|
44
|
-
fail "operator value pair must exist in a hash" unless data.is_a?(Hash)
|
45
|
-
@feature, @domain_basename, @domain_name = parse_domain_name(domain)
|
46
|
-
|
47
|
-
operator, value = data.first
|
48
|
-
@domain_attr = domain_attr.to_s
|
49
|
-
self.op = operator
|
50
|
-
self.value = value
|
51
|
-
|
52
|
-
fail "domain name can not be empty" if @domain_name.empty?
|
53
|
-
fail "domain attribute can not be empty" if @domain_attr.empty?
|
13
|
+
attr_reader :feature, :domain_basename, :domain_attr, :attr_list, :op, :value
|
14
|
+
|
15
|
+
def initialize(domain_attr, op, value)
|
16
|
+
@attr_list = parse_domain_attr(domain_attr)
|
17
|
+
@op = op.to_s.strip
|
18
|
+
@value = value
|
19
|
+
|
20
|
+
fail "op can not be empty" if @op.empty?
|
21
|
+
fail "attr_list can not be empty" if @attr_list.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def qualify_feature(feature, domain)
|
25
|
+
fail "this expr is already qualified" if qualified?
|
26
|
+
|
27
|
+
attr_list.unshift(domain)
|
28
|
+
attr_list.unshift(feature)
|
29
|
+
attr_list.unshift('features')
|
30
|
+
self
|
54
31
|
end
|
55
32
|
|
56
|
-
def
|
57
|
-
|
33
|
+
def qualify_global(domain)
|
34
|
+
fail "this expr is already qualified" if qualified?
|
35
|
+
attr_list.unshift(domain)
|
36
|
+
attr_list.unshift('global')
|
37
|
+
self
|
58
38
|
end
|
59
39
|
|
60
40
|
def global?
|
61
|
-
|
41
|
+
attr_list[0] == 'global'
|
62
42
|
end
|
63
43
|
|
64
|
-
|
65
|
-
|
66
|
-
@negated
|
44
|
+
def conjunction?
|
45
|
+
false
|
67
46
|
end
|
68
47
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
operator = data[1]
|
73
|
-
rvalue = data[2]
|
48
|
+
def qualified?
|
49
|
+
attr_list[0] == 'global' || attr_list[0] == 'features'
|
50
|
+
end
|
74
51
|
|
75
|
-
|
76
|
-
|
52
|
+
def feature
|
53
|
+
index = global? ? 0 : 1
|
54
|
+
attr_list[index]
|
77
55
|
end
|
78
56
|
|
79
|
-
def
|
80
|
-
|
57
|
+
def domain_basename
|
58
|
+
index = global? ? 1 : 2
|
59
|
+
attr_list[index]
|
81
60
|
end
|
82
61
|
|
83
|
-
def
|
84
|
-
|
62
|
+
def domain_name
|
63
|
+
"#{feature}.#{domain_basename}"
|
85
64
|
end
|
86
65
|
|
87
|
-
|
66
|
+
def domain_attr
|
67
|
+
start_range = global? ? 2 : 3
|
68
|
+
end_range = -1
|
69
|
+
attr_list.slice(start_range .. end_range).join('.')
|
70
|
+
end
|
88
71
|
|
89
|
-
def
|
90
|
-
|
91
|
-
@negated = false
|
92
|
-
if negated == 'not'
|
93
|
-
@negated = true
|
94
|
-
else
|
95
|
-
value = negated
|
96
|
-
end
|
97
|
-
value = value.to_sym
|
98
|
-
unless supported_op?(value)
|
99
|
-
fail "op has to be one of [#{OPS.keys.join(',')}]"
|
100
|
-
end
|
101
|
-
@op = value
|
72
|
+
def to_s
|
73
|
+
"#{attr_list.join('.')} #{op} #{value}"
|
102
74
|
end
|
103
75
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
unless data.is_a?(Array)
|
108
|
-
fail ":in operator must have an array as a value"
|
109
|
-
end
|
110
|
-
when :range
|
111
|
-
unless data.is_a?(Range)
|
112
|
-
fail ":range operator must have a range as a value"
|
113
|
-
end
|
114
|
-
when :gt, :gteq, :lt, :lteq
|
115
|
-
unless data.is_a?(Numeric)
|
116
|
-
fail ":gt, :gteq, :lt, :lteq operators expect a numeric value"
|
117
|
-
end
|
76
|
+
def validate_as_fully_qualified
|
77
|
+
unless qualified?
|
78
|
+
fail "expr (#{to_s}) is not fully qualified, mapping will not work"
|
118
79
|
end
|
119
|
-
|
80
|
+
true
|
120
81
|
end
|
121
82
|
|
122
|
-
|
123
|
-
|
83
|
+
private
|
84
|
+
|
85
|
+
def parse_domain_attr(list)
|
86
|
+
list = list.split('.') if list.is_a?(String)
|
87
|
+
|
88
|
+
unless list.is_a?(Array)
|
89
|
+
fail "Domain attribute must be a string in the form of " +
|
90
|
+
"(foo.bar.id) or an array ['foo', 'bar', 'id']"
|
91
|
+
end
|
92
|
+
list
|
124
93
|
end
|
125
94
|
end
|
126
95
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Domain
|
3
|
+
class ExprConjunction
|
4
|
+
attr_reader :op, :left, :right
|
5
|
+
|
6
|
+
def initialize(type, left, right)
|
7
|
+
@op = type.to_s.downcase
|
8
|
+
@left = left
|
9
|
+
@right = right
|
10
|
+
end
|
11
|
+
|
12
|
+
def conjunction?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def qualify_feature(feature, domain)
|
17
|
+
left.qualify_feature(feature, domain)
|
18
|
+
right.qualify_feature(feature, domain)
|
19
|
+
end
|
20
|
+
|
21
|
+
def qualify_global(domain)
|
22
|
+
left.qualify_global(domain)
|
23
|
+
right.qualify_global(domain)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require 'parslet/convenience'
|
3
|
+
require_relative 'expr_transform'
|
4
|
+
|
5
|
+
module Appfuel
|
6
|
+
module Domain
|
7
|
+
# A PEG (Parser Expression Grammer) parser for our domain language. This
|
8
|
+
# gives us the ablity to describe the filtering we would like to do when
|
9
|
+
# searching on a given domain entity. The search string is parsed and
|
10
|
+
# transformed into an interface that repositories can use to determine how
|
11
|
+
# to search a given storage interface. The language always represent the
|
12
|
+
# business domain entities and not a storage system.
|
13
|
+
#
|
14
|
+
class ExprParser < Parslet::Parser
|
15
|
+
rule(:space) { match('\s').repeat(1) }
|
16
|
+
rule(:space?) { space.maybe }
|
17
|
+
|
18
|
+
rule(:comma) { space? >> str(',') >> space? }
|
19
|
+
rule(:digit) { match['0-9'] }
|
20
|
+
|
21
|
+
rule(:lparen) { str('(') >> space? }
|
22
|
+
rule(:rparen) { str(')') >> space? }
|
23
|
+
|
24
|
+
rule(:filter_identifier) { stri('filter') }
|
25
|
+
rule(:order_identifier) { stri('order') }
|
26
|
+
rule(:limit_identifier) { stri('limit') }
|
27
|
+
|
28
|
+
rule(:integer) do
|
29
|
+
(str('-').maybe >> digit >> digit.repeat).as(:integer)
|
30
|
+
end
|
31
|
+
|
32
|
+
rule(:float) do
|
33
|
+
(
|
34
|
+
str('-').maybe >> digit.repeat(1) >> str('.') >> digit.repeat(1)
|
35
|
+
).as(:float)
|
36
|
+
end
|
37
|
+
|
38
|
+
rule(:number) { integer | float }
|
39
|
+
|
40
|
+
rule(:boolean) do
|
41
|
+
(stri("true") | stri('false')).as(:boolean)
|
42
|
+
end
|
43
|
+
|
44
|
+
rule(:string_special) { match['\0\t\n\r"\\\\'] }
|
45
|
+
|
46
|
+
rule(:escaped_special) { str('\\') >> match['0tnr"\\\\'] }
|
47
|
+
|
48
|
+
rule(:string) do
|
49
|
+
str('"') >>
|
50
|
+
((escaped_special | string_special.absent? >> any).repeat).as(:string) >>
|
51
|
+
str('"')
|
52
|
+
end
|
53
|
+
|
54
|
+
rule(:date) do
|
55
|
+
(
|
56
|
+
digit.repeat(4) >> str('-') >>
|
57
|
+
digit.repeat(2) >> str('-') >>
|
58
|
+
digit.repeat(2)
|
59
|
+
).as(:date)
|
60
|
+
end
|
61
|
+
|
62
|
+
# 1979-05-27T07:32:00Z
|
63
|
+
rule(:datetime) do
|
64
|
+
(
|
65
|
+
digit.repeat(4) >> str('-') >>
|
66
|
+
digit.repeat(2) >> str('-') >>
|
67
|
+
digit.repeat(2) >> str("T") >>
|
68
|
+
digit.repeat(2) >> str(":") >>
|
69
|
+
digit.repeat(2) >> str(":") >>
|
70
|
+
digit.repeat(2) >> str("Z")
|
71
|
+
).as(:datetime)
|
72
|
+
end
|
73
|
+
|
74
|
+
rule(:value) do
|
75
|
+
string | number | boolean | datetime | date
|
76
|
+
end
|
77
|
+
|
78
|
+
rule(:attr_label) do
|
79
|
+
match['a-z0-9_'].repeat(1).as(:attr_label)
|
80
|
+
end
|
81
|
+
|
82
|
+
rule(:domain_attr) do
|
83
|
+
(attr_label >> (str('.') >> attr_label).repeat).maybe.as(:domain_attr)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
rule(:and_op) { stri('and') >> space? }
|
89
|
+
rule(:or_op) { stri('or') >> space? }
|
90
|
+
rule(:in_op) { stri('in') >> space? }
|
91
|
+
rule(:like_op) { stri('like') >> space? }
|
92
|
+
rule(:between_op) { stri('between') >> space? }
|
93
|
+
|
94
|
+
rule(:eq_op) { str('=') >> space? }
|
95
|
+
rule(:gt_op) { str('>') >> space? }
|
96
|
+
rule(:gteq_op) { str('>=') >> space? }
|
97
|
+
rule(:lt_op) { str('<') >> space? }
|
98
|
+
rule(:lteq_op) { str('<=') >> space? }
|
99
|
+
|
100
|
+
rule(:comparison_value) do
|
101
|
+
number | date | datetime
|
102
|
+
end
|
103
|
+
|
104
|
+
rule(:eq_expr) do
|
105
|
+
domain_attr >> space? >> eq_op.as(:op) >> value.as(:value)
|
106
|
+
end
|
107
|
+
|
108
|
+
rule(:gt_expr) do
|
109
|
+
domain_attr >> space? >>
|
110
|
+
gt_op.as(:op) >> space? >>
|
111
|
+
comparison_value.as(:value)
|
112
|
+
end
|
113
|
+
|
114
|
+
rule(:gteq_expr) do
|
115
|
+
domain_attr >> space? >>
|
116
|
+
gteq_op.as(:op) >> space? >>
|
117
|
+
comparison_value.as(:value)
|
118
|
+
end
|
119
|
+
|
120
|
+
rule(:lt_expr) do
|
121
|
+
domain_attr >> space? >>
|
122
|
+
lt_op.as(:op) >> space? >>
|
123
|
+
comparison_value.as(:value)
|
124
|
+
end
|
125
|
+
|
126
|
+
rule(:lteq_expr) do
|
127
|
+
domain_attr >> space? >>
|
128
|
+
lteq_op.as(:op) >> space? >>
|
129
|
+
comparison_value.as(:value)
|
130
|
+
end
|
131
|
+
|
132
|
+
rule(:relational_expr) do
|
133
|
+
eq_expr | gt_expr | gteq_expr | lt_expr | lteq_expr
|
134
|
+
end
|
135
|
+
|
136
|
+
rule(:in_expr) do
|
137
|
+
domain_attr >> space >>
|
138
|
+
in_op.as(:op) >>
|
139
|
+
str('(') >> space? >>
|
140
|
+
(value >> (comma >> value).repeat).maybe.as(:value) >> space? >>
|
141
|
+
str(')')
|
142
|
+
end
|
143
|
+
|
144
|
+
rule(:like_expr) do
|
145
|
+
domain_attr >> space >>
|
146
|
+
like_op.as(:op) >> space? >>
|
147
|
+
string.as(:value)
|
148
|
+
end
|
149
|
+
|
150
|
+
rule(:between_expr) do
|
151
|
+
domain_attr >> space? >>
|
152
|
+
between_op.as(:op) >> space? >>
|
153
|
+
(
|
154
|
+
comparison_value.as(:lvalue) >> space? >>
|
155
|
+
and_op >> space? >>
|
156
|
+
comparison_value.as(:rvalue)
|
157
|
+
).as(:value)
|
158
|
+
end
|
159
|
+
|
160
|
+
rule(:domain_expr) do
|
161
|
+
(
|
162
|
+
relational_expr |
|
163
|
+
like_expr |
|
164
|
+
between_expr |
|
165
|
+
in_expr
|
166
|
+
).as(:domain_expr)
|
167
|
+
end
|
168
|
+
|
169
|
+
rule(:primary) do
|
170
|
+
lparen >> or_operation >> rparen | domain_expr >> space?
|
171
|
+
end
|
172
|
+
|
173
|
+
rule(:and_operation) do
|
174
|
+
(
|
175
|
+
primary.as(:left) >> space? >>
|
176
|
+
and_op >>
|
177
|
+
and_operation.as(:right)
|
178
|
+
).as(:and) | primary
|
179
|
+
end
|
180
|
+
|
181
|
+
rule(:or_operation) do
|
182
|
+
(
|
183
|
+
and_operation.as(:left) >> space? >>
|
184
|
+
or_op >>
|
185
|
+
or_operation.as(:right)
|
186
|
+
).as(:or) | and_operation
|
187
|
+
end
|
188
|
+
|
189
|
+
root(:or_operation)
|
190
|
+
|
191
|
+
def stri(str)
|
192
|
+
key_chars = str.split(//)
|
193
|
+
key_chars.collect! {|char|
|
194
|
+
match["#{char.upcase}#{char.downcase}"]
|
195
|
+
}.reduce(:>>)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|