rasti-db 1.5.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -19
  3. data/lib/rasti/db.rb +11 -2
  4. data/lib/rasti/db/collection.rb +67 -36
  5. data/lib/rasti/db/computed_attribute.rb +22 -0
  6. data/lib/rasti/db/data_source.rb +18 -0
  7. data/lib/rasti/db/environment.rb +32 -0
  8. data/lib/rasti/db/nql/filter_condition_strategies/base.rb +17 -0
  9. data/lib/rasti/db/nql/filter_condition_strategies/postgres.rb +21 -0
  10. data/lib/rasti/db/nql/filter_condition_strategies/sqlite.rb +21 -0
  11. data/lib/rasti/db/nql/filter_condition_strategies/types/generic.rb +49 -0
  12. data/lib/rasti/db/nql/filter_condition_strategies/types/pg_array.rb +32 -0
  13. data/lib/rasti/db/nql/filter_condition_strategies/types/sqlite_array.rb +34 -0
  14. data/lib/rasti/db/nql/filter_condition_strategies/unsupported_type_comparison.rb +22 -0
  15. data/lib/rasti/db/nql/nodes/array_content.rb +21 -0
  16. data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
  17. data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
  18. data/lib/rasti/db/nql/nodes/comparisons/base.rb +15 -1
  19. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +0 -4
  20. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +0 -4
  21. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +0 -4
  22. data/lib/rasti/db/nql/nodes/comparisons/include.rb +0 -4
  23. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +0 -4
  24. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +0 -4
  25. data/lib/rasti/db/nql/nodes/comparisons/like.rb +0 -4
  26. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +0 -4
  27. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +0 -4
  28. data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
  29. data/lib/rasti/db/nql/nodes/constants/array.rb +17 -0
  30. data/lib/rasti/db/nql/nodes/constants/base.rb +17 -0
  31. data/lib/rasti/db/nql/nodes/constants/false.rb +1 -1
  32. data/lib/rasti/db/nql/nodes/constants/float.rb +1 -1
  33. data/lib/rasti/db/nql/nodes/constants/integer.rb +1 -1
  34. data/lib/rasti/db/nql/nodes/constants/literal_string.rb +1 -1
  35. data/lib/rasti/db/nql/nodes/constants/string.rb +1 -1
  36. data/lib/rasti/db/nql/nodes/constants/time.rb +1 -1
  37. data/lib/rasti/db/nql/nodes/constants/true.rb +1 -1
  38. data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
  39. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
  40. data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
  41. data/lib/rasti/db/nql/syntax.rb +262 -44
  42. data/lib/rasti/db/nql/syntax.treetop +27 -14
  43. data/lib/rasti/db/query.rb +55 -23
  44. data/lib/rasti/db/relations/base.rb +22 -8
  45. data/lib/rasti/db/relations/graph.rb +10 -16
  46. data/lib/rasti/db/relations/many_to_many.rb +57 -23
  47. data/lib/rasti/db/relations/many_to_one.rb +9 -7
  48. data/lib/rasti/db/relations/one_to_many.rb +21 -13
  49. data/lib/rasti/db/type_converters/sqlite.rb +62 -0
  50. data/lib/rasti/db/type_converters/sqlite_types/array.rb +34 -0
  51. data/lib/rasti/db/version.rb +1 -1
  52. data/rasti-db.gemspec +1 -0
  53. data/spec/collection_spec.rb +210 -50
  54. data/spec/computed_attribute_spec.rb +32 -0
  55. data/spec/minitest_helper.rb +77 -15
  56. data/spec/model_spec.rb +4 -2
  57. data/spec/nql/computed_attributes_spec.rb +29 -0
  58. data/spec/nql/filter_condition_spec.rb +23 -4
  59. data/spec/nql/filter_condition_strategies_spec.rb +112 -0
  60. data/spec/nql/syntax_parser_spec.rb +36 -5
  61. data/spec/query_spec.rb +340 -54
  62. data/spec/relations_spec.rb +27 -7
  63. data/spec/type_converters/sqlite_spec.rb +66 -0
  64. metadata +40 -4
  65. data/lib/rasti/db/helpers.rb +0 -16
  66. data/lib/rasti/db/nql/nodes/field.rb +0 -23
@@ -0,0 +1,32 @@
1
+ require 'minitest_helper'
2
+
3
+ describe 'ComputedAttribute' do
4
+
5
+ it 'Apply Join wiht join attribute must generate correct query' do
6
+ dataset = db[:users]
7
+ computed_attribute = Rasti::DB::ComputedAttribute.new(Sequel[:comments_count][:value]) do |dataset|
8
+ subquery = dataset.db.from(:comments)
9
+ .select(Sequel[:user_id], Sequel.function('count', :id).as(:value))
10
+ .group(:user_id)
11
+ .as(:comments_count)
12
+
13
+ dataset.join_table(:inner, subquery, :user_id => :id)
14
+ end
15
+ expected_query = "SELECT *, `comments_count`.`value` AS 'value' FROM `users` INNER JOIN (SELECT `user_id`, count(`id`) AS 'value' FROM `comments` GROUP BY `user_id`) AS 'comments_count' ON (`comments_count`.`user_id` = `users`.`id`)"
16
+ computed_attribute.apply_join(dataset)
17
+ .select_append(computed_attribute.identifier)
18
+ .sql
19
+ .must_equal expected_query
20
+ end
21
+
22
+ it 'Apply join without join attribute must generate correct query' do
23
+ dataset = db[:people]
24
+ computed_attribute = Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
25
+ expected_query = "SELECT * FROM `people` WHERE ((`first_name` || ' ' || `last_name`) = 'FULL NAME')"
26
+ computed_attribute.apply_join(dataset)
27
+ .where(computed_attribute.identifier => 'FULL NAME')
28
+ .sql
29
+ .must_equal expected_query
30
+ end
31
+
32
+ end
@@ -10,24 +10,39 @@ require 'sequel/extensions/pg_array'
10
10
  require 'sequel/extensions/pg_json'
11
11
 
12
12
  Rasti::DB.configure do |config|
13
- config.type_converters = [Rasti::DB::TypeConverters::TimeInZone]
13
+ config.type_converters = [Rasti::DB::TypeConverters::TimeInZone, Rasti::DB::TypeConverters::SQLite]
14
+ config.nql_filter_condition_strategy = Rasti::DB::NQL::FilterConditionStrategies::SQLite.new
14
15
  end
15
16
 
16
- User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
17
- Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
18
- Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
17
+ User = Rasti::DB::Model[:id, :name, :posts, :comments, :person, :comments_count]
18
+ Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories, :language_id, :language, :notice, :author]
19
+ Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post, :tags]
19
20
  Category = Rasti::DB::Model[:id, :name, :posts]
20
- Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
21
+ Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user, :languages, :full_name]
22
+ Language = Rasti::DB::Model[:id, :name, :people]
21
23
 
22
24
 
23
25
  class Users < Rasti::DB::Collection
24
26
  one_to_many :posts
25
27
  one_to_many :comments
26
28
  one_to_one :person
29
+
30
+ computed_attribute :comments_count do
31
+ Rasti::DB::ComputedAttribute.new(Sequel[:comments_count][:value]) do |dataset|
32
+ subquery = dataset.db.from(:comments)
33
+ .select(Sequel[:user_id], Sequel.function('count', :id).as(:value))
34
+ .group(:user_id)
35
+ .as(:comments_count)
36
+
37
+ dataset.join_table(:inner, subquery, :user_id => :id)
38
+ end
39
+ end
40
+
27
41
  end
28
42
 
29
43
  class Posts < Rasti::DB::Collection
30
44
  many_to_one :user
45
+ many_to_one :language
31
46
  many_to_many :categories
32
47
  one_to_many :comments
33
48
 
@@ -39,12 +54,21 @@ class Posts < Rasti::DB::Collection
39
54
 
40
55
  query :commented_by do |user_id|
41
56
  chainable do
42
- dataset.join(with_schema(:comments), post_id: :id)
43
- .where(with_schema(:comments, :user_id) => user_id)
57
+ dataset.join(qualify(:comments), post_id: :id)
58
+ .where(Sequel[:comments][:user_id] => user_id)
44
59
  .select_all(:posts)
45
60
  .distinct
46
61
  end
47
62
  end
63
+
64
+ computed_attribute :notice do
65
+ Rasti::DB::ComputedAttribute.new Sequel.join([:title, ': ', :body])
66
+ end
67
+
68
+ computed_attribute :author do
69
+ Rasti::DB::ComputedAttribute.new Sequel[:user]
70
+ end
71
+
48
72
  end
49
73
 
50
74
  class Comments < Rasti::DB::Collection
@@ -53,7 +77,7 @@ class Comments < Rasti::DB::Collection
53
77
 
54
78
  def posts_commented_by(user_id)
55
79
  dataset.where(Sequel[:comments][:user_id] => user_id)
56
- .join(with_schema(:posts), id: :post_id)
80
+ .join(qualify(:posts, data_source_name: :default), id: :post_id)
57
81
  .select_all(:posts)
58
82
  .map { |row| Post.new row }
59
83
  end
@@ -70,24 +94,43 @@ class People < Rasti::DB::Collection
70
94
  set_model Person
71
95
 
72
96
  many_to_one :user
97
+ many_to_many :languages
98
+
99
+ computed_attribute :full_name do |db|
100
+ Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
101
+ end
102
+ end
103
+
104
+ class Languages < Rasti::DB::Collection
105
+ set_data_source_name :custom
106
+
107
+ many_to_many :people, collection: People, relation_data_source_name: :default
108
+ one_to_many :posts
73
109
  end
74
110
 
75
111
 
76
112
  class Minitest::Spec
77
113
 
78
- let(:users) { Users.new db }
114
+ let(:users) { Users.new environment }
79
115
 
80
- let(:posts) { Posts.new db }
116
+ let(:posts) { Posts.new environment }
81
117
 
82
- let(:comments) { Comments.new db }
118
+ let(:comments) { Comments.new environment }
83
119
 
84
- let(:categories) { Categories.new db }
120
+ let(:categories) { Categories.new environment }
85
121
 
86
- let(:people) { People.new db }
122
+ let(:people) { People.new environment }
87
123
 
88
- let :db do
89
- driver = (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite}
124
+ let(:languages) { Languages.new environment }
125
+
126
+ let(:driver) { (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite} }
127
+
128
+ let :environment do
129
+ Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(db),
130
+ custom: Rasti::DB::DataSource.new(custom_db)
131
+ end
90
132
 
133
+ let :db do
91
134
  Sequel.connect(driver).tap do |db|
92
135
 
93
136
  db.create_table :users do
@@ -99,12 +142,14 @@ class Minitest::Spec
99
142
  primary_key :id
100
143
  String :title, null: false, unique: true
101
144
  String :body, null: false
145
+ Integer :language_id, null: false, index: true
102
146
  foreign_key :user_id, :users, null: false, index: true
103
147
  end
104
148
 
105
149
  db.create_table :comments do
106
150
  primary_key :id
107
151
  String :text, null: false
152
+ String :tags, default: Sequel.lit("'[]'")
108
153
  foreign_key :user_id, :users, null: false, index: true
109
154
  foreign_key :post_id, :posts, null: false, index: true
110
155
  end
@@ -128,6 +173,23 @@ class Minitest::Spec
128
173
  foreign_key :user_id, :users, null: false, unique: true
129
174
  end
130
175
 
176
+ db.create_table :languages_people do
177
+ Integer :language_id, null: false, index: true
178
+ foreign_key :document_number, :people, type: String, null: false, index: true
179
+ primary_key [:language_id, :document_number]
180
+ end
181
+
182
+ end
183
+ end
184
+
185
+ let :custom_db do
186
+ Sequel.connect(driver).tap do |db|
187
+
188
+ db.create_table :languages do
189
+ primary_key :id
190
+ String :name, null: false, unique: true
191
+ end
192
+
131
193
  end
132
194
  end
133
195
 
@@ -10,7 +10,7 @@ describe 'Model' do
10
10
  end
11
11
 
12
12
  it 'Invalid definition' do
13
- error = proc { model = Rasti::DB::Model[:id, :name, :name] }.must_raise ArgumentError
13
+ error = proc { Rasti::DB::Model[:id, :name, :name] }.must_raise ArgumentError
14
14
  error.message.must_equal 'Attribute name already exists'
15
15
  end
16
16
 
@@ -42,7 +42,7 @@ describe 'Model' do
42
42
  describe 'To String' do
43
43
 
44
44
  it 'Class' do
45
- User.to_s.must_equal 'User[id, name, posts, comments, person]'
45
+ User.to_s.must_equal 'User[id, name, posts, comments, person, comments_count]'
46
46
  end
47
47
 
48
48
  it 'Instance' do
@@ -82,6 +82,8 @@ describe 'Model' do
82
82
  it 'Merge' do
83
83
  user = User.new(id: 1, name: 'User 1')
84
84
  changed_user = user.merge(name: 'User 2')
85
+
86
+ user.must_equal User.new(id: 1, name: 'User 1')
85
87
  changed_user.must_equal User.new(id: 1, name: 'User 2')
86
88
  end
87
89
 
@@ -0,0 +1,29 @@
1
+ require 'minitest_helper'
2
+
3
+ describe 'NQL::ComputedAttributes' 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 one computed attributes' do
12
+ tree = parse 'notice = any notice'
13
+
14
+ tree.computed_attributes(Posts).must_equal [:notice]
15
+ end
16
+
17
+ it 'must have multiple computed attributes' do
18
+ tree = parse 'notice = any notice & (author: anonym | title = good morning)'
19
+
20
+ tree.computed_attributes(Posts).must_equal [:notice, :author]
21
+ end
22
+
23
+ it 'must have not repeated computed attributes when expression have it' do
24
+ tree = parse 'notice = Hi | notice = Bye'
25
+
26
+ tree.computed_attributes(Posts).must_equal [:notice]
27
+ end
28
+
29
+ end
@@ -4,9 +4,11 @@ describe 'NQL::FilterCondition' do
4
4
 
5
5
  let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
6
6
 
7
+ let(:collection_class) { Rasti::DB::Collection }
8
+
7
9
  def filter_condition(expression)
8
10
  tree = parser.parse expression
9
- tree.filter_condition
11
+ tree.filter_condition(collection_class)
10
12
  end
11
13
 
12
14
  def assert_identifier(identifier, expected_value)
@@ -17,13 +19,30 @@ describe 'NQL::FilterCondition' do
17
19
  def assert_comparison(filter, expected_left, expected_comparator, expected_right)
18
20
  filter.must_be_instance_of Sequel::SQL::BooleanExpression
19
21
  filter.op.must_equal expected_comparator.to_sym
20
-
22
+
21
23
  left, right = filter.args
22
24
  assert_identifier left, expected_left
23
25
 
24
26
  right.must_equal expected_right
25
27
  end
26
28
 
29
+ describe 'None Filter Condition Strategy Validation' do
30
+
31
+ before do
32
+ Rasti::DB.nql_filter_condition_strategy = nil
33
+ end
34
+
35
+ after do
36
+ Rasti::DB.nql_filter_condition_strategy = Rasti::DB::NQL::FilterConditionStrategies::SQLite.new
37
+ end
38
+
39
+ it 'must raise error' do
40
+ error = proc { filter_condition 'column = value' }.must_raise RuntimeError
41
+ error.message.must_equal 'Undefined Rasti::DB.nql_filter_condition_strategy'
42
+ end
43
+
44
+ end
45
+
27
46
  describe 'Comparison' do
28
47
 
29
48
  it 'must create filter from expression with <' do
@@ -95,7 +114,7 @@ describe 'NQL::FilterCondition' do
95
114
 
96
115
  end
97
116
 
98
- it 'must create filter from expression with field with multiple tables' do
117
+ it 'must create filter from expression with attribute with multiple tables' do
99
118
  filter = filter_condition 'table_one.table_two.column = test'
100
119
  identifier, value = filter.first
101
120
 
@@ -132,7 +151,7 @@ describe 'NQL::FilterCondition' do
132
151
 
133
152
  filter.must_be_instance_of Sequel::SQL::BooleanExpression
134
153
  filter.op.must_equal :AND
135
-
154
+
136
155
  major_expression, and_expression = filter.args
137
156
  assert_comparison major_expression, 'column_one', '>', 1
138
157
 
@@ -0,0 +1,112 @@
1
+ require 'minitest_helper'
2
+
3
+ describe 'NQL::FilterConditionStrategies' do
4
+
5
+ let(:comments_query) { Rasti::DB::Query.new collection_class: Comments, dataset: db[:comments], environment: environment }
6
+
7
+ def sqls_where(query)
8
+ "#<Rasti::DB::Query: \"SELECT `comments`.* FROM `comments` WHERE (#{query})\">"
9
+ end
10
+
11
+ def sqls_where_not(query)
12
+ "#<Rasti::DB::Query: \"SELECT `comments`.* FROM `comments` WHERE NOT (#{query})\">"
13
+ end
14
+
15
+ def nql_s(nql_query)
16
+ comments_query.nql(nql_query).to_s
17
+ end
18
+
19
+ describe 'Generic' do
20
+
21
+ it 'Equal' do
22
+ nql_s('text = hola').must_equal sqls_where("`comments`.`text` = 'hola'")
23
+ end
24
+
25
+ it 'Not Equal' do
26
+ nql_s('text != hola').must_equal sqls_where("`comments`.`text` != 'hola'")
27
+ end
28
+
29
+ it 'Greather Than' do
30
+ nql_s('id > 1').must_equal sqls_where("`comments`.`id` > 1")
31
+ end
32
+
33
+ it 'Greather Than or Equal' do
34
+ nql_s('id >= 1').must_equal sqls_where("`comments`.`id` >= 1")
35
+ end
36
+
37
+ it 'Less Than' do
38
+ nql_s('id < 1').must_equal sqls_where("`comments`.`id` < 1")
39
+ end
40
+
41
+ it 'Less Than or Equal' do
42
+ nql_s('id <= 1').must_equal sqls_where("`comments`.`id` <= 1")
43
+ end
44
+
45
+ it 'Like' do
46
+ nql_s('text ~ hola').must_equal sqls_where("UPPER(`comments`.`text`) LIKE UPPER('hola') ESCAPE '\\'")
47
+ end
48
+
49
+ it 'Include' do
50
+ nql_s('text: hola').must_equal sqls_where("UPPER(`comments`.`text`) LIKE UPPER('%hola%') ESCAPE '\\'")
51
+ end
52
+
53
+ it 'Not Include' do
54
+ nql_s('text!: hola').must_equal sqls_where_not("UPPER(`comments`.`text`) LIKE UPPER('%hola%') ESCAPE '\\'")
55
+ end
56
+
57
+ end
58
+
59
+ describe 'SQLite Array' do
60
+
61
+ it 'Equal' do
62
+ nql_s('tags = [notice]').must_equal sqls_where("`comments`.`tags` = '[\"notice\"]'")
63
+ end
64
+
65
+ it 'Not Equal' do
66
+ nql_s('tags != [notice]').must_equal sqls_where_not("`comments`.`tags` LIKE '%\"notice\"%' ESCAPE '\\'")
67
+ end
68
+
69
+ it 'Like' do
70
+ nql_s('tags ~ [notice]').must_equal sqls_where("`comments`.`tags` LIKE '%notice%' ESCAPE '\\'")
71
+ end
72
+
73
+ it 'Include' do
74
+ nql_s('tags: [notice]').must_equal sqls_where("`comments`.`tags` LIKE '%\"notice\"%' ESCAPE '\\'")
75
+ end
76
+
77
+ it 'Not Include' do
78
+ nql_s('tags!: [notice]').must_equal sqls_where_not("`comments`.`tags` LIKE '%\"notice\"%' ESCAPE '\\'")
79
+ end
80
+
81
+ end
82
+
83
+ describe 'Postgres Array' do
84
+
85
+ before do
86
+ Rasti::DB.nql_filter_condition_strategy = Rasti::DB::NQL::FilterConditionStrategies::Postgres.new
87
+ Sequel.extension :pg_array_ops
88
+ end
89
+
90
+ after do
91
+ Rasti::DB.nql_filter_condition_strategy = Rasti::DB::NQL::FilterConditionStrategies::SQLite.new
92
+ end
93
+
94
+ it 'Equal' do
95
+ nql_s('tags = [notice]').must_equal sqls_where("(`comments`.`tags` @> ARRAY['notice']) AND (`comments`.`tags` <@ ARRAY['notice'])")
96
+ end
97
+
98
+ it 'Not Equal' do
99
+ nql_s('tags != [notice]').must_equal sqls_where("NOT (`comments`.`tags` @> ARRAY['notice']) OR NOT (`comments`.`tags` <@ ARRAY['notice'])")
100
+ end
101
+
102
+ it 'Include' do
103
+ nql_s('tags: [notice]').must_equal sqls_where("`comments`.`tags` && ARRAY['notice']")
104
+ end
105
+
106
+ it 'Not Include' do
107
+ nql_s('tags!: [notice]').must_equal sqls_where_not("`comments`.`tags` && ARRAY['notice']")
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -31,7 +31,7 @@ describe 'NQL::SyntaxParser' do
31
31
  proposition = tree.proposition
32
32
  proposition.must_be_instance_of node_class
33
33
  proposition.comparator.text_value.must_equal comparator
34
- proposition.field.text_value.must_equal 'column'
34
+ proposition.attribute.text_value.must_equal 'column'
35
35
  proposition.argument.text_value.must_equal 'value'
36
36
  end
37
37
  end
@@ -44,7 +44,7 @@ describe 'NQL::SyntaxParser' do
44
44
  proposition = tree.proposition
45
45
  proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Comparisons::Equal
46
46
  proposition.comparator.text_value.must_equal '='
47
- proposition.field.text_value.must_equal 'column'
47
+ proposition.attribute.text_value.must_equal 'column'
48
48
  proposition.argument.text_value.must_equal 'value'
49
49
  end
50
50
 
@@ -128,12 +128,19 @@ describe 'NQL::SyntaxParser' do
128
128
 
129
129
  end
130
130
 
131
- it 'must parse expression with field with tables' do
131
+ it 'must parse expression with attribute with tables' do
132
132
  tree = parse 'relation_table_one.relation_table_two.column = 1'
133
133
 
134
- left_hand_operand = tree.proposition.field
134
+ left_hand_operand = tree.proposition.attribute
135
135
  left_hand_operand.tables.must_equal ['relation_table_one', 'relation_table_two']
136
- left_hand_operand.column.must_equal 'column'
136
+ left_hand_operand.column.must_equal :column
137
+ end
138
+
139
+ it 'must parse expression with computed attribute' do
140
+ tree = parse 'comments_count = 1'
141
+ computed = tree.proposition.attribute
142
+ computed.tables.must_equal []
143
+ computed.computed_attributes(Users).must_equal [:comments_count]
137
144
  end
138
145
 
139
146
  end
@@ -206,4 +213,28 @@ describe 'NQL::SyntaxParser' do
206
213
 
207
214
  end
208
215
 
216
+ describe 'Array' do
217
+
218
+ it 'must parse array with one element' do
219
+ tree = parse 'column = [ a ]'
220
+ tree.proposition.argument.value.must_equal ['a']
221
+ end
222
+
223
+ it 'must parse array with many elements' do
224
+ tree = parse 'column = [ a, b, c ]'
225
+ tree.proposition.argument.value.must_equal ['a', 'b', 'c']
226
+ end
227
+
228
+ it 'must parse array respecting content types' do
229
+ tree = parse 'column = [ true, 12:00, 1.1, 1, "literal string", string ]'
230
+ tree.proposition.argument.value.must_equal [true, Timing::TimeInZone.parse('12:00').to_s, 1.1, 1, "literal string", 'string']
231
+ end
232
+
233
+ it 'must parse array with literal string allowing reserved characters in conflict with array' do
234
+ tree = parse 'column = [ ",", "[", "]", "[,", "],", "[,]", "simple literal" ]'
235
+ tree.proposition.argument.value.must_equal [ ',', '[', ']', '[,', '],', '[,]', 'simple literal' ]
236
+ end
237
+
238
+ end
239
+
209
240
  end