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.
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