appfuel 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|