rasti-db 1.1.1 → 1.2.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +8 -0
- data/lib/rasti/db.rb +9 -18
- data/lib/rasti/db/collection.rb +1 -1
- data/lib/rasti/db/nql/invalid_expression_error.rb +19 -0
- data/lib/rasti/db/nql/nodes/binary_node.rb +25 -0
- data/lib/rasti/db/nql/nodes/comparisons/base.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/include.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/like.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +17 -0
- data/lib/rasti/db/nql/nodes/conjunction.rb +15 -0
- data/lib/rasti/db/nql/nodes/constants/false.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/float.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/integer.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/literal_string.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/string.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/time.rb +23 -0
- data/lib/rasti/db/nql/nodes/constants/true.rb +17 -0
- data/lib/rasti/db/nql/nodes/disjunction.rb +15 -0
- data/lib/rasti/db/nql/nodes/field.rb +23 -0
- data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +19 -0
- data/lib/rasti/db/nql/nodes/sentence.rb +19 -0
- data/lib/rasti/db/nql/syntax.rb +2266 -0
- data/lib/rasti/db/nql/syntax.treetop +168 -0
- data/lib/rasti/db/query.rb +15 -0
- data/lib/rasti/db/relations/graph_builder.rb +3 -3
- data/lib/rasti/db/version.rb +1 -1
- data/rasti-db.gemspec +3 -1
- data/spec/coverage_helper.rb +5 -1
- data/spec/nql/dependency_tables_spec.rb +35 -0
- data/spec/nql/filter_condition_spec.rb +146 -0
- data/spec/nql/syntax_parser_spec.rb +209 -0
- data/spec/query_spec.rb +53 -0
- metadata +68 -2
@@ -0,0 +1,168 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
grammar Syntax
|
5
|
+
|
6
|
+
rule sentence
|
7
|
+
space* proposition:proposition space* <Nodes::Sentence>
|
8
|
+
end
|
9
|
+
|
10
|
+
rule proposition
|
11
|
+
disjunction /
|
12
|
+
conjunction /
|
13
|
+
statement
|
14
|
+
end
|
15
|
+
|
16
|
+
rule disjunction
|
17
|
+
left:(conjunction / statement) space* '|' space* right:proposition <Nodes::Disjunction>
|
18
|
+
end
|
19
|
+
|
20
|
+
rule conjunction
|
21
|
+
left:statement space* '&' space* right:(conjunction / statement) <Nodes::Conjunction>
|
22
|
+
end
|
23
|
+
|
24
|
+
rule statement
|
25
|
+
parenthesis_sentence /
|
26
|
+
comparison
|
27
|
+
end
|
28
|
+
|
29
|
+
rule parenthesis_sentence
|
30
|
+
'(' sentence ')' <Nodes::ParenthesisSentence>
|
31
|
+
end
|
32
|
+
|
33
|
+
rule comparison
|
34
|
+
comparison_not_include /
|
35
|
+
comparison_include /
|
36
|
+
comparison_like /
|
37
|
+
comparison_greater_than_or_equal /
|
38
|
+
comparison_less_than_or_equal /
|
39
|
+
comparison_greater_than /
|
40
|
+
comparison_less_than /
|
41
|
+
comparison_not_equal /
|
42
|
+
comparison_equal
|
43
|
+
end
|
44
|
+
|
45
|
+
rule field
|
46
|
+
_tables:(table:field_name '.')* _column:field_name <Nodes::Field>
|
47
|
+
end
|
48
|
+
|
49
|
+
rule comparison_include
|
50
|
+
field:field space* comparator:':' space* argument:basic <Nodes::Comparisons::Include>
|
51
|
+
end
|
52
|
+
|
53
|
+
rule comparison_not_include
|
54
|
+
field:field space* comparator:'!:' space* argument:basic <Nodes::Comparisons::NotInclude>
|
55
|
+
end
|
56
|
+
|
57
|
+
rule comparison_like
|
58
|
+
field:field space* comparator:'~' space* argument:basic <Nodes::Comparisons::Like>
|
59
|
+
end
|
60
|
+
|
61
|
+
rule comparison_greater_than
|
62
|
+
field:field space* comparator:'>' space* argument:basic <Nodes::Comparisons::GreaterThan>
|
63
|
+
end
|
64
|
+
|
65
|
+
rule comparison_greater_than_or_equal
|
66
|
+
field:field space* comparator:'>=' space* argument:basic <Nodes::Comparisons::GreaterThanOrEqual>
|
67
|
+
end
|
68
|
+
|
69
|
+
rule comparison_less_than
|
70
|
+
field:field space* comparator:'<' space* argument:basic <Nodes::Comparisons::LessThan>
|
71
|
+
end
|
72
|
+
|
73
|
+
rule comparison_less_than_or_equal
|
74
|
+
field:field space* comparator:'<=' space* argument:basic <Nodes::Comparisons::LessThanOrEqual>
|
75
|
+
end
|
76
|
+
|
77
|
+
rule comparison_not_equal
|
78
|
+
field:field space* comparator:'!=' space* argument:basic <Nodes::Comparisons::NotEqual>
|
79
|
+
end
|
80
|
+
|
81
|
+
rule comparison_equal
|
82
|
+
field:field space* comparator:'=' space* argument:basic <Nodes::Comparisons::Equal>
|
83
|
+
end
|
84
|
+
|
85
|
+
rule basic
|
86
|
+
boolean /
|
87
|
+
time /
|
88
|
+
float /
|
89
|
+
integer /
|
90
|
+
literal_string /
|
91
|
+
string
|
92
|
+
end
|
93
|
+
|
94
|
+
rule space
|
95
|
+
[\s\t\n]
|
96
|
+
end
|
97
|
+
|
98
|
+
rule field_name
|
99
|
+
[a-z_]+
|
100
|
+
end
|
101
|
+
|
102
|
+
rule time
|
103
|
+
date:(value:date 'T')? hour:(digit digit) ':' minutes:(digit digit) seconds:(':' value:(digit digit))? timezone:(value:timezone)? <Nodes::Constants::Time>
|
104
|
+
end
|
105
|
+
|
106
|
+
rule date
|
107
|
+
year:(digit digit digit digit) '-' month:(digit digit) '-' day:(digit digit)
|
108
|
+
end
|
109
|
+
|
110
|
+
rule timezone
|
111
|
+
sign:sign hour:(digit digit) ':' minutes:(digit digit)
|
112
|
+
end
|
113
|
+
|
114
|
+
rule sign
|
115
|
+
'+' /
|
116
|
+
'-'
|
117
|
+
end
|
118
|
+
|
119
|
+
rule literal_string
|
120
|
+
'"' string:any_character+ '"' <Nodes::Constants::LiteralString>
|
121
|
+
end
|
122
|
+
|
123
|
+
rule string
|
124
|
+
valid_character+ <Nodes::Constants::String>
|
125
|
+
end
|
126
|
+
|
127
|
+
rule any_character
|
128
|
+
valid_character /
|
129
|
+
reserved_character
|
130
|
+
end
|
131
|
+
|
132
|
+
rule valid_character
|
133
|
+
[0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+-_'?!$*/\s]
|
134
|
+
end
|
135
|
+
|
136
|
+
rule boolean
|
137
|
+
true /
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
rule true
|
142
|
+
'true' <Nodes::Constants::True>
|
143
|
+
end
|
144
|
+
|
145
|
+
rule false
|
146
|
+
'false' <Nodes::Constants::False>
|
147
|
+
end
|
148
|
+
|
149
|
+
rule float
|
150
|
+
digit+ '.' digit+ <Nodes::Constants::Float>
|
151
|
+
end
|
152
|
+
|
153
|
+
rule integer
|
154
|
+
digit+ <Nodes::Constants::Integer>
|
155
|
+
end
|
156
|
+
|
157
|
+
rule digit
|
158
|
+
[0-9]
|
159
|
+
end
|
160
|
+
|
161
|
+
rule reserved_character
|
162
|
+
[&|.():!=<>~]
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/rasti/db/query.rb
CHANGED
@@ -92,6 +92,17 @@ module Rasti
|
|
92
92
|
end
|
93
93
|
alias_method :inspect, :to_s
|
94
94
|
|
95
|
+
def nql(filter_expression)
|
96
|
+
sentence = nql_parser.parse filter_expression
|
97
|
+
|
98
|
+
raise NQL::InvalidExpressionError.new(filter_expression) if sentence.nil?
|
99
|
+
|
100
|
+
dependency_tables = sentence.dependency_tables
|
101
|
+
query = dependency_tables.empty? ? self : join(*dependency_tables)
|
102
|
+
|
103
|
+
query.where sentence.filter_condition
|
104
|
+
end
|
105
|
+
|
95
106
|
private
|
96
107
|
|
97
108
|
def chainable(&block)
|
@@ -122,6 +133,10 @@ module Rasti
|
|
122
133
|
collection_class.queries.key?(method) || super
|
123
134
|
end
|
124
135
|
|
136
|
+
def nql_parser
|
137
|
+
NQL::SyntaxParser.new
|
138
|
+
end
|
139
|
+
|
125
140
|
attr_reader :collection_class, :dataset, :relations, :schema
|
126
141
|
|
127
142
|
end
|
@@ -14,7 +14,7 @@ module Rasti
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def joins_to(dataset, relations, collection_class, schema=nil)
|
17
|
-
ds =
|
17
|
+
ds = recursive_joins dataset, recursive_parse(relations), collection_class, schema
|
18
18
|
qualified_collection_name = schema ? Sequel[schema][collection_class.collection_name] : Sequel[collection_class.collection_name]
|
19
19
|
ds.distinct.select_all(qualified_collection_name)
|
20
20
|
end
|
@@ -41,13 +41,13 @@ module Rasti
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def recursive_joins(dataset, joins, collection_class, schema, prefix=nil)
|
45
45
|
joins.each do |relation_name, nested_joins|
|
46
46
|
relation = get_relation collection_class, relation_name
|
47
47
|
|
48
48
|
dataset = relation.join_to dataset, schema, prefix
|
49
49
|
|
50
|
-
dataset =
|
50
|
+
dataset = recursive_joins dataset, nested_joins, relation.target_collection_class, schema, relation.join_relation_name(prefix) unless nested_joins.empty?
|
51
51
|
end
|
52
52
|
|
53
53
|
dataset
|
data/lib/rasti/db/version.rb
CHANGED
data/rasti-db.gemspec
CHANGED
@@ -19,13 +19,15 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'sequel', '~> 5.0'
|
22
|
+
spec.add_runtime_dependency 'treetop', '~> 1.4.8'
|
22
23
|
spec.add_runtime_dependency 'consty', '~> 1.0', '>= 1.0.3'
|
23
24
|
spec.add_runtime_dependency 'timing', '~> 0.1', '>= 0.1.3'
|
24
25
|
spec.add_runtime_dependency 'class_config', '~> 0.0', '>= 0.0.2'
|
26
|
+
spec.add_runtime_dependency 'multi_require', '~> 1.0'
|
25
27
|
|
26
28
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
27
29
|
spec.add_development_dependency 'rake', '~> 11.0'
|
28
|
-
spec.add_development_dependency 'minitest', '~> 5.0'
|
30
|
+
spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
|
29
31
|
spec.add_development_dependency 'minitest-colorin', '~> 0.1'
|
30
32
|
spec.add_development_dependency 'minitest-line', '~> 0.6'
|
31
33
|
spec.add_development_dependency 'simplecov', '~> 0.12'
|
data/spec/coverage_helper.rb
CHANGED
@@ -2,4 +2,8 @@ require 'simplecov'
|
|
2
2
|
require 'coveralls'
|
3
3
|
|
4
4
|
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
|
5
|
-
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter 'lib/rasti/db/nql/syntax.rb'
|
8
|
+
add_filter 'spec/'
|
9
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe 'NQL::DepedencyTables' do
|
4
|
+
|
5
|
+
let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
|
6
|
+
|
7
|
+
def parse(expression)
|
8
|
+
parser.parse expression
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'must have no dependency tables' do
|
12
|
+
tree = parse 'column = 1'
|
13
|
+
|
14
|
+
tree.dependency_tables.must_be_empty
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'must have one dependency table' do
|
18
|
+
tree = parse 'relation_table_one.relation_table_two.column = 1'
|
19
|
+
|
20
|
+
tree.dependency_tables.must_equal ['relation_table_one.relation_table_two']
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'must have multiple dependency tables' do
|
24
|
+
tree = parse 'a.b.c = 1 & (d.e: 2 | f.g.h = 1) | i = 4'
|
25
|
+
|
26
|
+
tree.dependency_tables.must_equal ['a.b', 'd', 'f.g']
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'must have repeated sub-dependency' do
|
30
|
+
tree = parse 'a.b.c = 1 & a.d: 2'
|
31
|
+
|
32
|
+
tree.dependency_tables.must_equal ['a.b', 'a']
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe 'NQL::FilterCondition' do
|
4
|
+
|
5
|
+
let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
|
6
|
+
|
7
|
+
def filter_condition(expression)
|
8
|
+
tree = parser.parse expression
|
9
|
+
tree.filter_condition
|
10
|
+
end
|
11
|
+
|
12
|
+
def assert_identifier(identifier, expected_value)
|
13
|
+
identifier.must_be_instance_of Sequel::SQL::Identifier
|
14
|
+
identifier.value.must_equal expected_value
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_comparison(filter, expected_left, expected_comparator, expected_right)
|
18
|
+
filter.must_be_instance_of Sequel::SQL::BooleanExpression
|
19
|
+
filter.op.must_equal expected_comparator.to_sym
|
20
|
+
|
21
|
+
left, right = filter.args
|
22
|
+
assert_identifier left, expected_left
|
23
|
+
|
24
|
+
right.must_equal expected_right
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'Comparison' do
|
28
|
+
|
29
|
+
it 'must create filter from expression with <' do
|
30
|
+
splitted_expression = ['column', '<', 1]
|
31
|
+
|
32
|
+
assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'must create filter from expression with >' do
|
36
|
+
splitted_expression = ['column', '>', 1]
|
37
|
+
|
38
|
+
assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'must create filter from expression with <=' do
|
42
|
+
splitted_expression = ['column', '<=', 1]
|
43
|
+
|
44
|
+
assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'must create filter from expression with >=' do
|
48
|
+
splitted_expression = ['column', '>=', 1]
|
49
|
+
|
50
|
+
assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'must create filter from expression with !=' do
|
54
|
+
splitted_expression = ['column', '!=', 1]
|
55
|
+
|
56
|
+
assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'must create filter from expression with =' do
|
60
|
+
filter = filter_condition 'column = 1'
|
61
|
+
identifier, value = filter.first
|
62
|
+
|
63
|
+
assert_identifier identifier, 'column'
|
64
|
+
value.must_equal 1
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'must create filter from expression with ~' do
|
68
|
+
filter = filter_condition 'column ~ test'
|
69
|
+
assert_comparison filter, 'column', 'ILIKE', 'test'
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'must create filter from expression with :' do
|
73
|
+
filter = filter_condition 'column: test'
|
74
|
+
assert_comparison filter, 'column', 'ILIKE', '%test%'
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'must create filter from expression with !:' do
|
78
|
+
filter = filter_condition 'column!: test'
|
79
|
+
assert_comparison filter, 'column', 'NOT ILIKE', '%test%'
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'Constants' do
|
85
|
+
|
86
|
+
it 'must create filter from expression with LiteralString' do
|
87
|
+
filter = filter_condition 'column: "test "'
|
88
|
+
assert_comparison filter, 'column', 'ILIKE', '%test %'
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'must create filter from expression with Time' do
|
92
|
+
filter = filter_condition 'column > 2019-03-27T12:20:00-03:00'
|
93
|
+
assert_comparison filter, 'column', '>', '2019-03-27 12:20:00 -0300'
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'must create filter from expression with field with multiple tables' do
|
99
|
+
filter = filter_condition 'table_one.table_two.column = test'
|
100
|
+
identifier, value = filter.first
|
101
|
+
|
102
|
+
identifier.must_be_instance_of Sequel::SQL::QualifiedIdentifier
|
103
|
+
identifier.table.must_equal 'table_one__table_two'
|
104
|
+
identifier.column.must_equal :column
|
105
|
+
value.must_equal 'test'
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'must create filter from expression with conjunction' do
|
109
|
+
filter = filter_condition 'column_one > 1 & column_two < 3'
|
110
|
+
|
111
|
+
filter.must_be_instance_of Sequel::SQL::BooleanExpression
|
112
|
+
filter.op.must_equal :AND
|
113
|
+
major_expression, minor_expression = filter.args
|
114
|
+
|
115
|
+
assert_comparison major_expression, 'column_one', '>', 1
|
116
|
+
assert_comparison minor_expression, 'column_two', '<', 3
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'must create filter from expression with disjunction' do
|
120
|
+
filter = filter_condition 'column_one > 1 | column_two != 3'
|
121
|
+
|
122
|
+
filter.must_be_instance_of Sequel::SQL::BooleanExpression
|
123
|
+
filter.op.must_equal :OR
|
124
|
+
major_expression, not_equal_expression = filter.args
|
125
|
+
|
126
|
+
assert_comparison major_expression, 'column_one', '>', 1
|
127
|
+
assert_comparison not_equal_expression, 'column_two', '!=', 3
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'must create filter from expression with parenthesis' do
|
131
|
+
filter = filter_condition 'column_one > 1 & (column_two != 3 | column_three < 5)'
|
132
|
+
|
133
|
+
filter.must_be_instance_of Sequel::SQL::BooleanExpression
|
134
|
+
filter.op.must_equal :AND
|
135
|
+
|
136
|
+
major_expression, and_expression = filter.args
|
137
|
+
assert_comparison major_expression, 'column_one', '>', 1
|
138
|
+
|
139
|
+
and_expression.op.must_equal :OR
|
140
|
+
not_equal_expression, minor_expression = and_expression.args
|
141
|
+
|
142
|
+
assert_comparison not_equal_expression, 'column_two', '!=', 3
|
143
|
+
assert_comparison minor_expression, 'column_three', '<', 5
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe 'NQL::SyntaxParser' do
|
4
|
+
|
5
|
+
let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
|
6
|
+
|
7
|
+
def parse(expression)
|
8
|
+
parser.parse(expression).tap do |tree|
|
9
|
+
tree.wont_be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'Comparison' do
|
14
|
+
|
15
|
+
describe 'Comparators' do
|
16
|
+
|
17
|
+
{
|
18
|
+
'=' => Rasti::DB::NQL::Nodes::Comparisons::Equal,
|
19
|
+
'!=' => Rasti::DB::NQL::Nodes::Comparisons::NotEqual,
|
20
|
+
'>' => Rasti::DB::NQL::Nodes::Comparisons::GreaterThan,
|
21
|
+
'>=' => Rasti::DB::NQL::Nodes::Comparisons::GreaterThanOrEqual,
|
22
|
+
'<' => Rasti::DB::NQL::Nodes::Comparisons::LessThan,
|
23
|
+
'<=' => Rasti::DB::NQL::Nodes::Comparisons::LessThanOrEqual,
|
24
|
+
'~' => Rasti::DB::NQL::Nodes::Comparisons::Like,
|
25
|
+
':' => Rasti::DB::NQL::Nodes::Comparisons::Include,
|
26
|
+
'!:' => Rasti::DB::NQL::Nodes::Comparisons::NotInclude
|
27
|
+
}.each do |comparator, node_class|
|
28
|
+
it "must parse expression with '#{comparator}'" do
|
29
|
+
tree = parse "column #{comparator} value"
|
30
|
+
|
31
|
+
proposition = tree.proposition
|
32
|
+
proposition.must_be_instance_of node_class
|
33
|
+
proposition.comparator.text_value.must_equal comparator
|
34
|
+
proposition.field.text_value.must_equal 'column'
|
35
|
+
proposition.argument.text_value.must_equal 'value'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
it "must parse expression without spaces between elements" do
|
42
|
+
tree = parse 'column=value'
|
43
|
+
|
44
|
+
proposition = tree.proposition
|
45
|
+
proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Comparisons::Equal
|
46
|
+
proposition.comparator.text_value.must_equal '='
|
47
|
+
proposition.field.text_value.must_equal 'column'
|
48
|
+
proposition.argument.text_value.must_equal 'value'
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'Right hand Operand' do
|
52
|
+
|
53
|
+
it 'must parse expression with integer' do
|
54
|
+
tree = parse 'column = 1'
|
55
|
+
|
56
|
+
right_hand_operand = tree.proposition.argument
|
57
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Integer
|
58
|
+
right_hand_operand.value.must_equal 1
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'must parse expression with float' do
|
62
|
+
tree = parse 'column = 2.3'
|
63
|
+
|
64
|
+
right_hand_operand = tree.proposition.argument
|
65
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Float
|
66
|
+
right_hand_operand.value.must_equal 2.3
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'must parse expression with true' do
|
70
|
+
tree = parse 'column = true'
|
71
|
+
|
72
|
+
right_hand_operand = tree.proposition.argument
|
73
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::True
|
74
|
+
right_hand_operand.value.must_equal true
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'must parse expression with false' do
|
78
|
+
tree = parse 'column = false'
|
79
|
+
|
80
|
+
right_hand_operand = tree.proposition.argument
|
81
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::False
|
82
|
+
right_hand_operand.value.must_equal false
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'must parse expression with string' do
|
86
|
+
tree = parse 'column = String1 '
|
87
|
+
|
88
|
+
right_hand_operand = tree.proposition.argument
|
89
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::String
|
90
|
+
right_hand_operand.value.must_equal 'String1'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'must parse expression with literal string' do
|
94
|
+
tree = parse 'column = "a & (b | c) | d:=e"'
|
95
|
+
|
96
|
+
right_hand_operand = tree.proposition.argument
|
97
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::LiteralString
|
98
|
+
right_hand_operand.value.must_equal 'a & (b | c) | d:=e'
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'Time' do
|
102
|
+
|
103
|
+
it 'must parse expression with hours and minutes' do
|
104
|
+
tree = parse 'column > 12:20'
|
105
|
+
|
106
|
+
right_hand_operand = tree.proposition.argument
|
107
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
|
108
|
+
right_hand_operand.value.must_equal Timing::TimeInZone.parse('12:20').to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'must parse expression with date, hours, minutes and seconds' do
|
112
|
+
tree = parse 'column > 2019-03-27T12:20:00'
|
113
|
+
|
114
|
+
right_hand_operand = tree.proposition.argument
|
115
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
|
116
|
+
right_hand_operand.value.must_equal Timing::TimeInZone.parse('2019-03-27T12:20:00').to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'must parse expression with date, hours, minutes, seconds and timezone' do
|
120
|
+
tree = parse 'column > 2019-03-27T12:20:00-03:00'
|
121
|
+
|
122
|
+
right_hand_operand = tree.proposition.argument
|
123
|
+
right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
|
124
|
+
right_hand_operand.value.must_equal Timing::TimeInZone.parse('2019-03-27T12:20:00-03:00').to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'must parse expression with field with tables' do
|
132
|
+
tree = parse 'relation_table_one.relation_table_two.column = 1'
|
133
|
+
|
134
|
+
left_hand_operand = tree.proposition.field
|
135
|
+
left_hand_operand.tables.must_equal ['relation_table_one', 'relation_table_two']
|
136
|
+
left_hand_operand.column.must_equal 'column'
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
describe 'Parenthesis Sentence' do
|
142
|
+
|
143
|
+
it 'must parse parenthesis comparison' do
|
144
|
+
tree = parse '(column: name)'
|
145
|
+
|
146
|
+
tree.proposition.sentence.text_value.must_equal 'column: name'
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'must parse conjunction over parenthesis of disjunction' do
|
150
|
+
tree = parse '(column_one: 1 | column_two: two) & column_three: 3.0'
|
151
|
+
|
152
|
+
tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Conjunction
|
153
|
+
tree.proposition.left.sentence.text_value.must_equal 'column_one: 1 | column_two: two'
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
describe 'Conjunction' do
|
160
|
+
|
161
|
+
def parse_and_assert(expression, conjunction_values)
|
162
|
+
tree = parse expression
|
163
|
+
|
164
|
+
tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Conjunction
|
165
|
+
tree.proposition.values.map(&:text_value).zip(conjunction_values).each do |actual, expected|
|
166
|
+
actual.must_equal expected
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'must parse conjunction of two comparisons' do
|
171
|
+
parse_and_assert 'column_one != 1 & column_two: name', ['column_one != 1', 'column_two: name']
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'must parse multiple conjunctions' do
|
175
|
+
parse_and_assert 'column_one > 1 & column_two: name & column_three < 9.2', ['column_one > 1', 'column_two: name ', 'column_three < 9.2']
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'must parse conjunction with parenthesis expression' do
|
179
|
+
parse_and_assert '(column_one > 1 | column_two: name) & column_three < 9.2', ['(column_one > 1 | column_two: name)', 'column_three < 9.2']
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'Disjunction' do
|
185
|
+
|
186
|
+
def parse_and_assert(expression, disjunction_values)
|
187
|
+
tree = parse expression
|
188
|
+
|
189
|
+
tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Disjunction
|
190
|
+
tree.proposition.values.map(&:text_value).zip(disjunction_values).each do |actual, expected|
|
191
|
+
actual.must_equal expected
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'must parse disjunction of two disjunctions' do
|
196
|
+
parse_and_assert 'column_one != 1 | column_two: name', ['column_one != 1', 'column_two: name']
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'must parse disjunction over conjunction' do
|
200
|
+
parse_and_assert 'column_one != 1 & column_two: name | column_three < 1.2', ['column_one != 1 & column_two: name ', 'column_three < 1.2']
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'must parse multiple conjunctions' do
|
204
|
+
parse_and_assert 'column_one != 1 | column_two: name | column_three < 1.2', ['column_one != 1', 'column_two: name ', 'column_three < 1.2']
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|