rasti-db 1.5.0 → 2.3.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 (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