rasti-db 1.4.0 → 2.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -3
  3. data/README.md +88 -24
  4. data/lib/rasti/db.rb +2 -1
  5. data/lib/rasti/db/collection.rb +79 -46
  6. data/lib/rasti/db/computed_attribute.rb +22 -0
  7. data/lib/rasti/db/data_source.rb +18 -0
  8. data/lib/rasti/db/environment.rb +32 -0
  9. data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
  10. data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
  11. data/lib/rasti/db/nql/nodes/comparisons/base.rb +5 -1
  12. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +2 -2
  13. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +2 -2
  14. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +2 -2
  15. data/lib/rasti/db/nql/nodes/comparisons/include.rb +2 -2
  16. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +2 -2
  17. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +2 -2
  18. data/lib/rasti/db/nql/nodes/comparisons/like.rb +2 -2
  19. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +2 -2
  20. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +2 -2
  21. data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
  22. data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
  23. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
  24. data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
  25. data/lib/rasti/db/nql/syntax.rb +33 -33
  26. data/lib/rasti/db/nql/syntax.treetop +12 -12
  27. data/lib/rasti/db/query.rb +107 -43
  28. data/lib/rasti/db/relations/base.rb +22 -8
  29. data/lib/rasti/db/relations/graph.rb +129 -0
  30. data/lib/rasti/db/relations/many_to_many.rb +58 -24
  31. data/lib/rasti/db/relations/many_to_one.rb +17 -12
  32. data/lib/rasti/db/relations/one_to_many.rb +27 -16
  33. data/lib/rasti/db/version.rb +1 -1
  34. data/rasti-db.gemspec +3 -7
  35. data/spec/collection_spec.rb +223 -52
  36. data/spec/computed_attribute_spec.rb +32 -0
  37. data/spec/minitest_helper.rb +76 -15
  38. data/spec/model_spec.rb +4 -2
  39. data/spec/nql/computed_attributes_spec.rb +29 -0
  40. data/spec/nql/filter_condition_spec.rb +4 -2
  41. data/spec/nql/syntax_parser_spec.rb +12 -5
  42. data/spec/query_spec.rb +319 -85
  43. data/spec/relations_spec.rb +27 -7
  44. metadata +41 -7
  45. data/lib/rasti/db/helpers.rb +0 -16
  46. data/lib/rasti/db/nql/nodes/field.rb +0 -23
  47. data/lib/rasti/db/relations/graph_builder.rb +0 -60
@@ -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
@@ -2,6 +2,7 @@ require 'coverage_helper'
2
2
  require 'rasti-db'
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/colorin'
5
+ require 'minitest/line/describe_track'
5
6
  require 'pry-nav'
6
7
  require 'logger'
7
8
  require 'sequel/extensions/pg_hstore'
@@ -12,21 +13,35 @@ Rasti::DB.configure do |config|
12
13
  config.type_converters = [Rasti::DB::TypeConverters::TimeInZone]
13
14
  end
14
15
 
15
- User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
16
- Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
16
+ User = Rasti::DB::Model[:id, :name, :posts, :comments, :person, :comments_count]
17
+ Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories, :language_id, :language, :notice, :author]
17
18
  Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
18
19
  Category = Rasti::DB::Model[:id, :name, :posts]
19
- Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
20
+ Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user, :languages, :full_name]
21
+ Language = Rasti::DB::Model[:id, :name, :people]
20
22
 
21
23
 
22
24
  class Users < Rasti::DB::Collection
23
25
  one_to_many :posts
24
26
  one_to_many :comments
25
27
  one_to_one :person
28
+
29
+ computed_attribute :comments_count do
30
+ Rasti::DB::ComputedAttribute.new(Sequel[:comments_count][:value]) do |dataset|
31
+ subquery = dataset.db.from(:comments)
32
+ .select(Sequel[:user_id], Sequel.function('count', :id).as(:value))
33
+ .group(:user_id)
34
+ .as(:comments_count)
35
+
36
+ dataset.join_table(:inner, subquery, :user_id => :id)
37
+ end
38
+ end
39
+
26
40
  end
27
41
 
28
42
  class Posts < Rasti::DB::Collection
29
43
  many_to_one :user
44
+ many_to_one :language
30
45
  many_to_many :categories
31
46
  one_to_many :comments
32
47
 
@@ -38,12 +53,21 @@ class Posts < Rasti::DB::Collection
38
53
 
39
54
  query :commented_by do |user_id|
40
55
  chainable do
41
- dataset.join(with_schema(:comments), post_id: :id)
42
- .where(with_schema(:comments, :user_id) => user_id)
43
- .select_all(with_schema(:posts))
56
+ dataset.join(qualify(:comments), post_id: :id)
57
+ .where(Sequel[:comments][:user_id] => user_id)
58
+ .select_all(:posts)
44
59
  .distinct
45
60
  end
46
61
  end
62
+
63
+ computed_attribute :notice do
64
+ Rasti::DB::ComputedAttribute.new Sequel.join([:title, ': ', :body])
65
+ end
66
+
67
+ computed_attribute :author do
68
+ Rasti::DB::ComputedAttribute.new Sequel[:user]
69
+ end
70
+
47
71
  end
48
72
 
49
73
  class Comments < Rasti::DB::Collection
@@ -52,8 +76,8 @@ class Comments < Rasti::DB::Collection
52
76
 
53
77
  def posts_commented_by(user_id)
54
78
  dataset.where(Sequel[:comments][:user_id] => user_id)
55
- .join(with_schema(:posts), id: :post_id)
56
- .select_all(with_schema(:posts))
79
+ .join(qualify(:posts, data_source_name: :default), id: :post_id)
80
+ .select_all(:posts)
57
81
  .map { |row| Post.new row }
58
82
  end
59
83
  end
@@ -69,24 +93,43 @@ class People < Rasti::DB::Collection
69
93
  set_model Person
70
94
 
71
95
  many_to_one :user
96
+ many_to_many :languages
97
+
98
+ computed_attribute :full_name do |db|
99
+ Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
100
+ end
101
+ end
102
+
103
+ class Languages < Rasti::DB::Collection
104
+ set_data_source_name :custom
105
+
106
+ many_to_many :people, collection: People, relation_data_source_name: :default
107
+ one_to_many :posts
72
108
  end
73
109
 
74
110
 
75
111
  class Minitest::Spec
76
112
 
77
- let(:users) { Users.new db }
113
+ let(:users) { Users.new environment }
78
114
 
79
- let(:posts) { Posts.new db }
115
+ let(:posts) { Posts.new environment }
80
116
 
81
- let(:comments) { Comments.new db }
117
+ let(:comments) { Comments.new environment }
82
118
 
83
- let(:categories) { Categories.new db }
119
+ let(:categories) { Categories.new environment }
84
120
 
85
- let(:people) { People.new db }
121
+ let(:people) { People.new environment }
86
122
 
87
- let :db do
88
- driver = (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite}
123
+ let(:languages) { Languages.new environment }
124
+
125
+ let(:driver) { (RUBY_ENGINE == 'jruby') ? 'jdbc:sqlite::memory:' : {adapter: :sqlite} }
126
+
127
+ let :environment do
128
+ Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(db),
129
+ custom: Rasti::DB::DataSource.new(custom_db)
130
+ end
89
131
 
132
+ let :db do
90
133
  Sequel.connect(driver).tap do |db|
91
134
 
92
135
  db.create_table :users do
@@ -98,6 +141,7 @@ class Minitest::Spec
98
141
  primary_key :id
99
142
  String :title, null: false, unique: true
100
143
  String :body, null: false
144
+ Integer :language_id, null: false, index: true
101
145
  foreign_key :user_id, :users, null: false, index: true
102
146
  end
103
147
 
@@ -127,6 +171,23 @@ class Minitest::Spec
127
171
  foreign_key :user_id, :users, null: false, unique: true
128
172
  end
129
173
 
174
+ db.create_table :languages_people do
175
+ Integer :language_id, null: false, index: true
176
+ foreign_key :document_number, :people, type: String, null: false, index: true
177
+ primary_key [:language_id, :document_number]
178
+ end
179
+
180
+ end
181
+ end
182
+
183
+ let :custom_db do
184
+ Sequel.connect(driver).tap do |db|
185
+
186
+ db.create_table :languages do
187
+ primary_key :id
188
+ String :name, null: false, unique: true
189
+ end
190
+
130
191
  end
131
192
  end
132
193
 
@@ -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)
@@ -95,7 +97,7 @@ describe 'NQL::FilterCondition' do
95
97
 
96
98
  end
97
99
 
98
- it 'must create filter from expression with field with multiple tables' do
100
+ it 'must create filter from expression with attribute with multiple tables' do
99
101
  filter = filter_condition 'table_one.table_two.column = test'
100
102
  identifier, value = filter.first
101
103
 
@@ -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
@@ -3,18 +3,46 @@ require 'minitest_helper'
3
3
  describe 'Query' do
4
4
 
5
5
  before do
6
- 1.upto(10) { |i| db[:users].insert name: "User #{i}" }
6
+ custom_db[:languages].insert name: 'Spanish'
7
7
 
8
- db[:posts].insert user_id: 2, title: 'Sample post', body: '...'
9
- db[:posts].insert user_id: 1, title: 'Another post', body: '...'
10
- db[:posts].insert user_id: 4, title: 'Best post', body: '...'
8
+ 1.upto(10) do |i|
9
+ db[:users].insert name: "User #{i}"
10
+
11
+ db[:people].insert user_id: i,
12
+ document_number: "document_#{i}",
13
+ first_name: "Name #{i}",
14
+ last_name: "Last Name #{i}",
15
+ birth_date: Date.parse('2020-04-24')
16
+
17
+ db[:languages_people].insert language_id: 1, document_number: "document_#{i}"
18
+ end
19
+
20
+ db[:posts].insert user_id: 2, title: 'Sample post', body: '...', language_id: 1
21
+ db[:posts].insert user_id: 1, title: 'Another post', body: '...', language_id: 1
22
+ db[:posts].insert user_id: 4, title: 'Best post', body: '...', language_id: 1
23
+
24
+ 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
25
+
26
+ db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
27
+ db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
28
+ db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
29
+
30
+ db[:categories_posts].insert post_id: 1, category_id: 1
31
+ db[:categories_posts].insert post_id: 1, category_id: 2
32
+ db[:categories_posts].insert post_id: 2, category_id: 2
33
+ db[:categories_posts].insert post_id: 2, category_id: 3
34
+ db[:categories_posts].insert post_id: 3, category_id: 3
11
35
  end
12
36
 
13
- let(:users_query) { Rasti::DB::Query.new Users, db[:users] }
37
+ let(:users_query) { Rasti::DB::Query.new collection_class: Users, dataset: db[:users], environment: environment }
14
38
 
15
- let(:posts_query) { Rasti::DB::Query.new Posts, db[:posts] }
16
-
17
- let(:comments_query) { Rasti::DB::Query.new Comments, db[:comments] }
39
+ let(:posts_query) { Rasti::DB::Query.new collection_class: Posts, dataset: db[:posts], environment: environment }
40
+
41
+ let(:comments_query) { Rasti::DB::Query.new collection_class: Comments, dataset: db[:comments], environment: environment }
42
+
43
+ let(:people_query) { Rasti::DB::Query.new collection_class: People, dataset: db[:people], environment: environment }
44
+
45
+ let(:languages_query) { Rasti::DB::Query.new collection_class: Languages, dataset: custom_db[:languages], environment: environment }
18
46
 
19
47
  it 'Count' do
20
48
  users_query.count.must_equal 10
@@ -37,6 +65,107 @@ describe 'Query' do
37
65
  users_query.primary_keys.must_equal db[:users].map { |u| u[:id] }
38
66
  end
39
67
 
68
+ it 'Select attributes' do
69
+ posts_query.select_attributes(:id, :user_id).all.must_equal db[:posts].select(:id, :user_id).map { |r| Post.new r }
70
+ end
71
+
72
+ it 'Exclude attributes' do
73
+ posts_query.exclude_attributes(:body).all.must_equal db[:posts].select(:id, :user_id, :title, :language_id).map { |r| Post.new r }
74
+ end
75
+
76
+ it 'All attributes' do
77
+ posts_query.exclude_attributes(:body).all_attributes.all.must_equal db[:posts].map { |r| Post.new r }
78
+ end
79
+
80
+ it 'Select graph attributes' do
81
+ language = Language.new custom_db[:languages].where(id: 1).select(:id).first
82
+
83
+ person = Person.new db[:people].where(document_number: 'document_2').select(:document_number, :user_id).first.merge(languages: [language])
84
+
85
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
86
+
87
+ categories = db[:categories].where(id: [1,2]).select(:id).map { |c| Category.new c }
88
+
89
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user, categories: categories)
90
+
91
+ selected_attributes = {
92
+ user: [:id],
93
+ 'user.person' => [:document_number, :user_id],
94
+ 'user.person.languages' => [:id],
95
+ categories: [:id]
96
+ }
97
+
98
+ posts_query.where(id: 1)
99
+ .graph(*selected_attributes.keys)
100
+ .select_graph_attributes(selected_attributes)
101
+ .all
102
+ .must_equal [post]
103
+ end
104
+
105
+ it 'Exclude graph attributes' do
106
+ language = Language.new custom_db[:languages].where(id: 1).select(:id).first
107
+
108
+ person = Person.new db[:people].where(document_number: 'document_2').select(:document_number, :user_id).first.merge(languages: [language])
109
+
110
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
111
+
112
+ categories = db[:categories].where(id: [1,2]).select(:id).map { |c| Category.new c }
113
+
114
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user, categories: categories)
115
+
116
+ excluded_attributes = {
117
+ user: [:name],
118
+ 'user.person' => [:first_name, :last_name, :birth_date],
119
+ 'user.person.languages' => [:name],
120
+ categories: [:name]
121
+ }
122
+
123
+ posts_query.where(id: 1)
124
+ .graph(*excluded_attributes.keys)
125
+ .exclude_graph_attributes(excluded_attributes)
126
+ .all
127
+ .must_equal [post]
128
+ end
129
+
130
+ it 'All graph attributes' do
131
+ person = Person.new db[:people].where(document_number: 'document_2').first
132
+
133
+ user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
134
+
135
+ post = Post.new db[:posts].where(id: 1).first.merge(user: user)
136
+
137
+ posts_query.where(id: 1)
138
+ .graph('user.person')
139
+ .exclude_graph_attributes(user: [:name], 'user.person' => [:birth_date, :first_name, :last_name])
140
+ .all_graph_attributes('user.person')
141
+ .all
142
+ .must_equal [post]
143
+ end
144
+
145
+ describe 'Select computed attributes' do
146
+ it 'With join' do
147
+ db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 4'
148
+ users_query.select_computed_attributes(:comments_count)
149
+ .where(id: 5)
150
+ .all
151
+ .must_equal [User.new(id: 5, name: 'User 5', comments_count: 2)]
152
+ end
153
+
154
+ it 'Without join' do
155
+ person_expected = Person.new user_id: 1,
156
+ document_number: 'document_1',
157
+ first_name: 'Name 1',
158
+ last_name: 'Last Name 1',
159
+ birth_date: Date.parse('2020-04-24'),
160
+ full_name: 'Name 1 Last Name 1'
161
+
162
+ people_query.select_computed_attributes(:full_name)
163
+ .where(document_number: 'document_1')
164
+ .all
165
+ .must_equal [person_expected]
166
+ end
167
+ end
168
+
40
169
  it 'Map' do
41
170
  users_query.map(&:name).must_equal db[:users].map(:name)
42
171
  end
@@ -45,45 +174,88 @@ describe 'Query' do
45
174
  users_query.detect(id: 3).must_equal User.new(id: 3, name: 'User 3')
46
175
  end
47
176
 
177
+ describe 'Each' do
178
+
179
+ it 'without size' do
180
+ users = []
181
+
182
+ users_query.each do |user|
183
+ users << user
184
+ end
185
+
186
+ users.size.must_equal 10
187
+ users.each_with_index do |user, i|
188
+ user.must_equal User.new(id: i+1, name: "User #{i+1}")
189
+ end
190
+ end
191
+
192
+ it 'with size' do
193
+ users = []
194
+ users_query.each(batch_size: 2) do |user|
195
+ users << user
196
+ end
197
+
198
+ users.size.must_equal 10
199
+ users.each_with_index do |user, i|
200
+ user.must_equal User.new(id: i+1, name: "User #{i+1}")
201
+ end
202
+ end
203
+
204
+ end
205
+
206
+ it 'Each batch' do
207
+ users_batch = []
208
+ users_query.each_batch(size: 2) do |page|
209
+ users_batch << page
210
+ end
211
+
212
+ users_batch.size.must_equal 5
213
+ i = 1
214
+ users_batch.each do |user_page|
215
+ user_page.must_equal [User.new(id: i, name: "User #{i}"), User.new(id: i+1, name: "User #{i+1}")]
216
+ i += 2
217
+ end
218
+ end
219
+
48
220
  it 'Where' do
49
221
  users_query.where(id: 3).all.must_equal [User.new(id: 3, name: 'User 3')]
50
222
  end
51
-
223
+
52
224
  it 'Exclude' do
53
225
  users_query.exclude(id: [1,2,3,4,5,6,7,8,9]).all.must_equal [User.new(id: 10, name: 'User 10')]
54
226
  end
55
-
227
+
56
228
  it 'And' do
57
229
  users_query.where(id: [1,2]).where(name: 'User 2').all.must_equal [User.new(id: 2, name: 'User 2')]
58
230
  end
59
-
231
+
60
232
  it 'Or' do
61
233
  users_query.where(id: 1).or(name: 'User 2').all.must_equal [
62
- User.new(id: 1, name: 'User 1'),
234
+ User.new(id: 1, name: 'User 1'),
63
235
  User.new(id: 2, name: 'User 2')
64
236
  ]
65
237
  end
66
-
238
+
67
239
  it 'Order' do
68
240
  posts_query.order(:title).all.must_equal [
69
- Post.new(id: 2, user_id: 1, title: 'Another post', body: '...'),
70
- Post.new(id: 3, user_id: 4, title: 'Best post', body: '...'),
71
- Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...')
241
+ Post.new(id: 2, user_id: 1, title: 'Another post', body: '...', language_id: 1),
242
+ Post.new(id: 3, user_id: 4, title: 'Best post', body: '...', language_id: 1),
243
+ Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...', language_id: 1)
72
244
  ]
73
245
  end
74
-
246
+
75
247
  it 'Reverse order' do
76
248
  posts_query.reverse_order(:title).all.must_equal [
77
- Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...'),
78
- Post.new(id: 3, user_id: 4, title: 'Best post', body: '...'),
79
- Post.new(id: 2, user_id: 1, title: 'Another post', body: '...')
249
+ Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...', language_id: 1),
250
+ Post.new(id: 3, user_id: 4, title: 'Best post', body: '...', language_id: 1),
251
+ Post.new(id: 2, user_id: 1, title: 'Another post', body: '...', language_id: 1)
80
252
  ]
81
253
  end
82
-
254
+
83
255
  it 'Limit and offset' do
84
256
  users_query.limit(1).offset(1).all.must_equal [User.new(id: 2, name: 'User 2')]
85
257
  end
86
-
258
+
87
259
  it 'First' do
88
260
  users_query.first.must_equal User.new(id: 1, name: 'User 1')
89
261
  end
@@ -93,21 +265,48 @@ describe 'Query' do
93
265
  end
94
266
 
95
267
  it 'Graph' do
96
- users_query.graph(:posts).where(id: 1).first.must_equal User.new(id: 1, name: 'User 1', posts: [Post.new(id: 2, user_id: 1, title: 'Another post', body: '...')])
268
+ users_query.graph(:posts).where(id: 1).first.must_equal User.new(id: 1, name: 'User 1', posts: [Post.new(id: 2, user_id: 1, title: 'Another post', body: '...', language_id: 1)])
97
269
  end
98
270
 
99
- it 'Empty?' do
271
+ it 'Graph with multiple data sources' do
272
+ language = Language.new id: 1, name: 'Spanish'
273
+
274
+ person = Person.new user_id: 2,
275
+ document_number: 'document_2',
276
+ first_name: 'Name 2',
277
+ last_name: 'Last Name 2',
278
+ birth_date: Date.parse('2020-04-24'),
279
+ languages: [language]
280
+
281
+ user = User.new id: 2,
282
+ name: 'User 2',
283
+ person: person
284
+
285
+ post = Post.new id: 1,
286
+ user_id: 2,
287
+ user: user,
288
+ title: 'Sample post',
289
+ body: '...',
290
+ language_id: 1,
291
+ language: language
292
+
293
+ posts_query.where(id: 1).graph(:language, 'user.person.languages').first.must_equal post
294
+ end
295
+
296
+ it 'Any?' do
100
297
  users_query.empty?.must_equal false
101
298
  users_query.any?.must_equal true
102
299
  end
103
300
 
104
- it 'Any?' do
301
+ it 'Empty?' do
302
+ db[:comments].truncate
303
+
105
304
  comments_query.empty?.must_equal true
106
305
  comments_query.any?.must_equal false
107
306
  end
108
307
 
109
308
  it 'To String' do
110
- users_query.where(id: [1,2,3]).order(:name).to_s.must_equal '#<Rasti::DB::Query: "SELECT * FROM `users` WHERE (`id` IN (1, 2, 3)) ORDER BY `name`">'
309
+ users_query.where(id: [1,2,3]).order(:name).to_s.must_equal '#<Rasti::DB::Query: "SELECT `users`.* FROM `users` WHERE (`users`.`id` IN (1, 2, 3)) ORDER BY `users`.`name`">'
111
310
  end
112
311
 
113
312
  describe 'Named queries' do
@@ -126,108 +325,143 @@ describe 'Query' do
126
325
 
127
326
  describe 'Join' do
128
327
 
129
- before do
130
- 1.upto(10) do |i|
131
- db[:people].insert user_id: i,
132
- document_number: i,
133
- first_name: "Name #{i}",
134
- last_name: "Last Name #{i}",
135
- birth_date: Time.now
136
- end
137
-
138
- 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
139
-
140
- db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
141
- db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
142
- db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
143
-
144
- db[:categories_posts].insert post_id: 1, category_id: 1
145
- db[:categories_posts].insert post_id: 1, category_id: 2
146
- db[:categories_posts].insert post_id: 2, category_id: 2
147
- db[:categories_posts].insert post_id: 2, category_id: 3
148
- db[:categories_posts].insert post_id: 3, category_id: 3
149
- end
150
-
151
328
  it 'One to Many' do
152
- users_query.join(:posts).where(title: 'Sample post').all.must_equal [User.new(id: 2, name: 'User 2')]
329
+ users_query.join(:posts).where(Sequel[:posts][:title] => 'Sample post').all.must_equal [User.new(id: 2, name: 'User 2')]
153
330
  end
154
331
 
155
332
  it 'Many to One' do
156
- posts_query.join(:user).where(name: 'User 4').all.must_equal [Post.new(id: 3, user_id: 4, title: 'Best post', body: '...')]
333
+ posts_query.join(:user).where(Sequel[:user][:name] => 'User 4').all.must_equal [Post.new(id: 3, user_id: 4, title: 'Best post', body: '...', language_id: 1)]
157
334
  end
158
335
 
159
336
  it 'One to One' do
160
- users_query.join(:person).where(document_number: 1).all.must_equal [User.new(id: 1, name: 'User 1')]
337
+ users_query.join(:person).where(Sequel[:person][:document_number] => 'document_1').all.must_equal [User.new(id: 1, name: 'User 1')]
161
338
  end
162
339
 
163
340
  it 'Many to Many' do
164
- posts_query.join(:categories).where(name: 'Category 3').order(:id).all.must_equal [
165
- Post.new(id: 2, user_id: 1, title: 'Another post', body: '...'),
166
- Post.new(id: 3, user_id: 4, title: 'Best post', body: '...'),
341
+ posts_query.join(:categories).where(Sequel[:categories][:name] => 'Category 3').order(:id).all.must_equal [
342
+ Post.new(id: 2, user_id: 1, title: 'Another post', body: '...', language_id: 1),
343
+ Post.new(id: 3, user_id: 4, title: 'Best post', body: '...', language_id: 1),
167
344
  ]
168
345
  end
169
346
 
170
347
  it 'Nested' do
171
348
  posts_query.join('categories', 'comments.user.person')
172
349
  .where(Sequel[:categories][:name] => 'Category 2')
173
- .where(Sequel[:comments__user__person][:document_number] => 7)
350
+ .where(Sequel[:comments__user__person][:document_number] => 'document_7')
174
351
  .all
175
- .must_equal [Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...')]
352
+ .must_equal [Post.new(id: 1, user_id: 2, title: 'Sample post', body: '...', language_id: 1)]
176
353
  end
177
354
 
178
- end
355
+ it 'Excluded attributes permanents excluded when join' do
356
+ posts_query.join(:user)
357
+ .exclude_attributes(:body)
358
+ .where(Sequel[:user][:name] => 'User 4')
359
+ .all
360
+ .must_equal [Post.new(id: 3, title: 'Best post', user_id: 4, language_id: 1)]
179
361
 
180
- describe 'NQL' do
362
+ posts_query.exclude_attributes(:body)
363
+ .join(:user)
364
+ .where(Sequel[:user][:name] => 'User 4')
365
+ .all
366
+ .must_equal [Post.new(id: 3, title: 'Best post', user_id: 4, language_id: 1)]
367
+ end
181
368
 
182
- before do
183
- 1.upto(10) do |i|
184
- db[:people].insert user_id: i,
185
- document_number: i,
186
- first_name: "Name #{i}",
187
- last_name: "Last Name #{i}",
188
- birth_date: Time.now
369
+ describe 'Multiple data sources' do
370
+
371
+ it 'One to Many' do
372
+ error = proc { languages_query.join(:posts).all }.must_raise RuntimeError
373
+ error.message.must_equal 'Invalid join of multiple data sources: custom.languages > default.posts'
189
374
  end
190
375
 
191
- 1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
192
-
193
- db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
194
- db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
195
- db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
376
+ it 'Many to One' do
377
+ error = proc { posts_query.join(:language).all }.must_raise RuntimeError
378
+ error.message.must_equal 'Invalid join of multiple data sources: default.posts > custom.languages'
379
+ end
196
380
 
197
- db[:categories_posts].insert post_id: 1, category_id: 1
198
- db[:categories_posts].insert post_id: 1, category_id: 2
199
- db[:categories_posts].insert post_id: 2, category_id: 2
200
- db[:categories_posts].insert post_id: 2, category_id: 3
201
- db[:categories_posts].insert post_id: 3, category_id: 3
381
+ it 'Many to Many' do
382
+ error = proc { languages_query.join(:people).all }.must_raise RuntimeError
383
+ error.message.must_equal 'Invalid join of multiple data sources: custom.languages > default.people'
384
+ end
202
385
  end
203
386
 
387
+ end
388
+
389
+ describe 'NQL' do
390
+
204
391
  it 'Invalid expression' do
205
392
  error = proc { posts_query.nql('a + b') }.must_raise Rasti::DB::NQL::InvalidExpressionError
206
393
  error.message.must_equal 'Invalid filter expression: a + b'
207
394
  end
208
-
209
- it 'Filter to self table' do
210
- people_query = Rasti::DB::Query.new People, db[:people]
211
395
 
212
- people_query.nql('user_id > 7')
213
- .map(&:user_id)
214
- .sort
215
- .must_equal [8, 9, 10]
396
+ it 'Filter to self table' do
397
+ posts_query.nql('user_id > 1')
398
+ .pluck(:user_id)
399
+ .sort
400
+ .must_equal [2, 4]
216
401
  end
217
402
 
218
403
  it 'Filter to join table' do
219
404
  posts_query.nql('categories.name = Category 2')
220
- .map(&:id)
405
+ .pluck(:id)
221
406
  .sort
222
407
  .must_equal [1, 2]
223
408
  end
224
409
 
225
410
  it 'Filter to 2nd order relation' do
226
- posts_query.nql('comments.user.person.document_number = 7')
227
- .map(&:id)
411
+ posts_query.nql('comments.user.person.document_number = document_7')
412
+ .pluck(:id)
228
413
  .must_equal [1]
229
414
  end
230
415
 
416
+ it 'Filter combined' do
417
+ posts_query.nql('(categories.id = 1 | categories.id = 3) & comments.user.person.document_number = document_2')
418
+ .pluck(:id)
419
+ .must_equal [2]
420
+ end
421
+
422
+ describe 'Computed Attributes' do
423
+
424
+ it 'Filter relation computed attribute' do
425
+ db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 4'
426
+ users_query.nql('comments_count = 2').all.must_equal [User.new(id: 5, name: 'User 5')]
427
+ end
428
+
429
+ it 'Filter with relation computed attribute with "and" combined' do
430
+ db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 4'
431
+ db[:comments].insert post_id: 1, user_id: 4, text: 'Comment 3'
432
+ users_query.nql('(comments_count > 1) & (id = 5)').all.must_equal [User.new(id: 5, name: 'User 5')]
433
+ end
434
+
435
+ it 'Filter relation computed attribute with "or" combined' do
436
+ db[:comments].insert post_id: 1, user_id: 2, text: 'Comment 3'
437
+ users_query.nql('(comments_count = 2) | (id = 5)')
438
+ .order(:id)
439
+ .all
440
+ .must_equal [ User.new(id: 2, name: 'User 2'), User.new(id: 5, name: 'User 5') ]
441
+ end
442
+
443
+ it 'Filter relation computed attribute with "and" and "or" combined' do
444
+ db[:comments].insert post_id: 1, user_id: 2, text: 'Comment 3'
445
+ users_query.nql('((comments_count = 2) | (id = 5)) & (name: User 5)')
446
+ .order(:id)
447
+ .all
448
+ .must_equal [ User.new(id: 5, name: 'User 5') ]
449
+ end
450
+
451
+ it 'Filter simple computed attribute' do
452
+ person_expected = Person.new user_id: 1,
453
+ document_number: 'document_1',
454
+ first_name: 'Name 1',
455
+ last_name: 'Last Name 1',
456
+ birth_date: Date.parse('2020-04-24')
457
+
458
+ people_query.nql('full_name = Name 1 Last Name 1')
459
+ .all
460
+ .must_equal [person_expected]
461
+ end
462
+
463
+ end
464
+
231
465
  end
232
466
 
233
467
  end