rasti-db 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/README.md +8 -0
  4. data/lib/rasti/db.rb +9 -18
  5. data/lib/rasti/db/collection.rb +1 -1
  6. data/lib/rasti/db/nql/invalid_expression_error.rb +19 -0
  7. data/lib/rasti/db/nql/nodes/binary_node.rb +25 -0
  8. data/lib/rasti/db/nql/nodes/comparisons/base.rb +17 -0
  9. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +17 -0
  10. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +17 -0
  11. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +17 -0
  12. data/lib/rasti/db/nql/nodes/comparisons/include.rb +17 -0
  13. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +17 -0
  14. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +17 -0
  15. data/lib/rasti/db/nql/nodes/comparisons/like.rb +17 -0
  16. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +17 -0
  17. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +17 -0
  18. data/lib/rasti/db/nql/nodes/conjunction.rb +15 -0
  19. data/lib/rasti/db/nql/nodes/constants/false.rb +17 -0
  20. data/lib/rasti/db/nql/nodes/constants/float.rb +17 -0
  21. data/lib/rasti/db/nql/nodes/constants/integer.rb +17 -0
  22. data/lib/rasti/db/nql/nodes/constants/literal_string.rb +17 -0
  23. data/lib/rasti/db/nql/nodes/constants/string.rb +17 -0
  24. data/lib/rasti/db/nql/nodes/constants/time.rb +23 -0
  25. data/lib/rasti/db/nql/nodes/constants/true.rb +17 -0
  26. data/lib/rasti/db/nql/nodes/disjunction.rb +15 -0
  27. data/lib/rasti/db/nql/nodes/field.rb +23 -0
  28. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +19 -0
  29. data/lib/rasti/db/nql/nodes/sentence.rb +19 -0
  30. data/lib/rasti/db/nql/syntax.rb +2266 -0
  31. data/lib/rasti/db/nql/syntax.treetop +168 -0
  32. data/lib/rasti/db/query.rb +15 -0
  33. data/lib/rasti/db/relations/graph_builder.rb +3 -3
  34. data/lib/rasti/db/version.rb +1 -1
  35. data/rasti-db.gemspec +3 -1
  36. data/spec/coverage_helper.rb +5 -1
  37. data/spec/nql/dependency_tables_spec.rb +35 -0
  38. data/spec/nql/filter_condition_spec.rb +146 -0
  39. data/spec/nql/syntax_parser_spec.rb +209 -0
  40. data/spec/query_spec.rb +53 -0
  41. 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
@@ -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 = recusrive_joins dataset, recursive_parse(relations), collection_class, schema
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 recusrive_joins(dataset, joins, collection_class, schema, prefix=nil)
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 = recusrive_joins dataset, nested_joins, relation.target_collection_class, schema, relation.join_relation_name(prefix) unless nested_joins.empty?
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
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module DB
3
- VERSION = '1.1.1'
3
+ VERSION = '1.2.0'
4
4
  end
5
5
  end
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'
@@ -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
- SimpleCov.start
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