rasti-db 1.4.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -15,25 +15,44 @@ module Rasti
15
15
  @relation_collection_name ||= options[:relation_collection_name] || [source_collection_class.collection_name, target_collection_class.collection_name].sort.join('_').to_sym
16
16
  end
17
17
 
18
- def qualified_relation_collection_name(schema=nil)
19
- schema.nil? ? Sequel[relation_collection_name] : Sequel[schema][relation_collection_name]
18
+ def relation_data_source_name
19
+ @relation_data_source_name ||= options[:relation_data_source_name] || source_collection_class.data_source_name
20
20
  end
21
21
 
22
- def graph_to(rows, db, schema=nil, relations=[])
22
+ def fetch_graph(environment, rows, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
23
23
  pks = rows.map { |row| row[source_collection_class.primary_key] }
24
24
 
25
- target_collection = target_collection_class.new db, schema
25
+ if target_collection_class.data_source_name == relation_data_source_name
26
+ target_data_source = environment.data_source_of target_collection_class
26
27
 
27
- relation_name = qualified_relation_collection_name schema
28
+ dataset = target_data_source.db.from(environment.qualify_collection(target_collection_class))
29
+ .join(qualified_relation_collection_name(environment), target_foreign_key => target_collection_class.primary_key)
30
+ .where(Sequel[relation_collection_name][source_foreign_key] => pks)
31
+ .select_all(target_collection_class.collection_name)
28
32
 
29
- join_rows = target_collection.dataset
30
- .join(relation_name, target_foreign_key => target_collection_class.primary_key)
31
- .where(Sequel[relation_name][source_foreign_key] => pks)
32
- .select_all(qualified_target_collection_name(schema))
33
- .select_append(Sequel[relation_name][source_foreign_key].as(:source_foreign_key))
34
- .all
33
+ selected_attributes ||= target_collection_class.collection_attributes - excluded_attributes if excluded_attributes
34
+ dataset = dataset.select(*selected_attributes.map { |a| Sequel[target_collection_class.collection_name][a] }) if selected_attributes
35
35
 
36
- GraphBuilder.graph_to join_rows, relations, target_collection_class, db, schema
36
+ join_rows = dataset.select_append(Sequel[relation_collection_name][source_foreign_key].as(:source_foreign_key)).all
37
+ else
38
+ relation_data_source = environment.data_source relation_data_source_name
39
+
40
+ relation_index = relation_data_source.db.from(relation_data_source.qualify(relation_collection_name))
41
+ .where(source_foreign_key => pks)
42
+ .select_hash_groups(target_foreign_key, source_foreign_key)
43
+
44
+ query = target_collection_class.new environment
45
+ query = query.exclude_attributes(*excluded_attributes) if excluded_attributes
46
+ query = query.select_attributes(*selected_attributes) if selected_attributes
47
+
48
+ join_rows = query.where(target_collection_class.primary_key => relation_index.keys).raw.flat_map do |row|
49
+ relation_index[row[target_collection_class.primary_key]].map do |source_primary_key|
50
+ row.merge(source_foreign_key: source_primary_key)
51
+ end
52
+ end
53
+ end
54
+
55
+ relations_graph.fetch_graph join_rows if relations_graph
37
56
 
38
57
  relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
39
58
  attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
@@ -41,17 +60,19 @@ module Rasti
41
60
  end
42
61
 
43
62
  rows.each do |row|
44
- row[name] = relation_rows.fetch row[target_collection_class.primary_key], []
63
+ row[name] = relation_rows.fetch row[source_collection_class.primary_key], []
45
64
  end
46
65
  end
47
66
 
48
- def join_to(dataset, schema=nil, prefix=nil)
67
+ def add_join(environment, dataset, prefix=nil)
68
+ validate_join!
69
+
49
70
  many_to_many_relation_alias = with_prefix prefix, "#{relation_collection_name}_#{SecureRandom.base64}"
50
71
 
51
- qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
72
+ relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
52
73
 
53
74
  many_to_many_condition = {
54
- Sequel[many_to_many_relation_alias][source_foreign_key] => qualified_relation_source[source_collection_class.primary_key]
75
+ Sequel[many_to_many_relation_alias][source_foreign_key] => relation_name[source_collection_class.primary_key]
55
76
  }
56
77
 
57
78
  relation_alias = join_relation_name prefix
@@ -60,17 +81,30 @@ module Rasti
60
81
  Sequel[relation_alias][target_collection_class.primary_key] => Sequel[many_to_many_relation_alias][target_foreign_key]
61
82
  }
62
83
 
63
- dataset.join(qualified_relation_collection_name(schema).as(many_to_many_relation_alias), many_to_many_condition)
64
- .join(qualified_target_collection_name(schema).as(relation_alias), relation_condition)
84
+ dataset.join(qualified_relation_collection_name(environment).as(many_to_many_relation_alias), many_to_many_condition)
85
+ .join(environment.qualify_collection(target_collection_class).as(relation_alias), relation_condition)
86
+ end
87
+
88
+ def apply_filter(environment, dataset, primary_keys)
89
+ if source_collection_class.data_source_name == relation_data_source_name
90
+ dataset.join(qualified_relation_collection_name(environment), source_foreign_key => source_collection_class.primary_key)
91
+ .where(Sequel[relation_collection_name][target_foreign_key] => primary_keys)
92
+ .select_all(source_collection_class.collection_name)
93
+ .distinct
94
+ else
95
+ data_source = environment.data_source relation_data_source_name
96
+ fks = data_source.db.from(data_source.qualify(relation_collection_name))
97
+ .where(target_collection_class.foreign_key => primary_keys)
98
+ .select_map(source_collection_class.foreign_key)
99
+ .uniq
100
+ dataset.where(source_collection_class.primary_key => fks)
101
+ end
65
102
  end
66
103
 
67
- def apply_filter(dataset, schema=nil, primary_keys=[])
68
- relation_name = qualified_relation_collection_name schema
104
+ private
69
105
 
70
- dataset.join(relation_name, source_foreign_key => target_collection_class.primary_key)
71
- .where(Sequel[relation_name][target_foreign_key] => primary_keys)
72
- .select_all(qualified_source_collection_name(schema))
73
- .distinct
106
+ def qualified_relation_collection_name(environment)
107
+ environment.qualify relation_data_source_name, relation_collection_name
74
108
  end
75
109
 
76
110
  end
@@ -7,35 +7,40 @@ module Rasti
7
7
  @foreign_key ||= options[:foreign_key] || target_collection_class.foreign_key
8
8
  end
9
9
 
10
- def graph_to(rows, db, schema=nil, relations=[])
10
+ def fetch_graph(environment, rows, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
11
11
  fks = rows.map { |row| row[foreign_key] }.uniq
12
12
 
13
- target_collection = target_collection_class.new db, schema
13
+ target_collection = target_collection_class.new environment
14
14
 
15
- relation_rows = target_collection.where(source_collection_class.primary_key => fks)
16
- .graph(*relations)
17
- .each_with_object({}) do |row, hash|
18
- hash[row.public_send(source_collection_class.primary_key)] = row
19
- end
15
+ query = target_collection.where(source_collection_class.primary_key => fks)
16
+ query = query.exclude_attributes(*excluded_attributes) if excluded_attributes
17
+ query = query.select_attributes(*selected_attributes) if selected_attributes
18
+ query = relations_graph.apply_to query if relations_graph
19
+
20
+ relation_rows = query.each_with_object({}) do |row, hash|
21
+ hash[row.public_send(source_collection_class.primary_key)] = row
22
+ end
20
23
 
21
24
  rows.each do |row|
22
25
  row[name] = relation_rows[row[foreign_key]]
23
26
  end
24
27
  end
25
28
 
26
- def join_to(dataset, schema=nil, prefix=nil)
29
+ def add_join(environment, dataset, prefix=nil)
30
+ validate_join!
31
+
27
32
  relation_alias = join_relation_name prefix
28
33
 
29
- qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
34
+ relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
30
35
 
31
36
  relation_condition = {
32
- Sequel[relation_alias][target_collection_class.primary_key] => qualified_relation_source[foreign_key]
37
+ Sequel[relation_alias][target_collection_class.primary_key] => relation_name[foreign_key]
33
38
  }
34
39
 
35
- dataset.join(qualified_target_collection_name(schema).as(relation_alias), relation_condition)
40
+ dataset.join(environment.qualify_collection(target_collection_class).as(relation_alias), relation_condition)
36
41
  end
37
42
 
38
- def apply_filter(dataset, schema=nil, primary_keys=[])
43
+ def apply_filter(environment, dataset, primary_keys)
39
44
  dataset.where(foreign_key => primary_keys)
40
45
  end
41
46
 
@@ -7,39 +7,50 @@ module Rasti
7
7
  @foreign_key ||= options[:foreign_key] || source_collection_class.foreign_key
8
8
  end
9
9
 
10
- def graph_to(rows, db, schema=nil, relations=[])
10
+ def fetch_graph(environment, rows, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
11
11
  pks = rows.map { |row| row[source_collection_class.primary_key] }.uniq
12
12
 
13
- target_collection = target_collection_class.new db, schema
13
+ target_collection = target_collection_class.new environment
14
14
 
15
- relation_rows = target_collection.where(foreign_key => pks)
16
- .graph(*relations)
17
- .group_by { |r| r.public_send(foreign_key) }
15
+ query = target_collection.where(foreign_key => pks)
16
+ query = query.exclude_attributes(*excluded_attributes) if excluded_attributes
17
+ query = query.select_attributes(*selected_attributes) if selected_attributes
18
+ query = relations_graph.apply_to query if relations_graph
19
+
20
+ relation_rows = query.group_by(&foreign_key)
18
21
 
19
22
  rows.each do |row|
20
23
  row[name] = build_graph_result relation_rows.fetch(row[source_collection_class.primary_key], [])
21
24
  end
22
25
  end
23
26
 
24
- def join_to(dataset, schema=nil, prefix=nil)
27
+ def add_join(environment, dataset, prefix=nil)
28
+ validate_join!
29
+
25
30
  relation_alias = join_relation_name prefix
26
31
 
27
- qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
32
+ relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
28
33
 
29
34
  relation_condition = {
30
- Sequel[relation_alias][foreign_key] => qualified_relation_source[source_collection_class.primary_key]
35
+ Sequel[relation_alias][foreign_key] => relation_name[source_collection_class.primary_key]
31
36
  }
32
37
 
33
- dataset.join(qualified_target_collection_name(schema).as(relation_alias), relation_condition)
38
+ dataset.join(environment.qualify_collection(target_collection_class).as(relation_alias), relation_condition)
34
39
  end
35
40
 
36
- def apply_filter(dataset, schema=nil, primary_keys=[])
37
- target_name = qualified_target_collection_name schema
38
-
39
- dataset.join(target_name, foreign_key => source_collection_class.primary_key)
40
- .where(Sequel[target_name][target_collection_class.primary_key] => primary_keys)
41
- .select_all(qualified_source_collection_name(schema))
42
- .distinct
41
+ def apply_filter(environment, dataset, primary_keys)
42
+ if source_collection_class.data_source_name == target_collection_class.data_source_name
43
+ dataset.join(environment.qualify_collection(target_collection_class), foreign_key => source_collection_class.primary_key)
44
+ .where(Sequel[target_collection_class.collection_name][target_collection_class.primary_key] => primary_keys)
45
+ .select_all(target_collection_class.collection_name)
46
+ .distinct
47
+ else
48
+ target_collection = target_collection_class.new environment
49
+ fks = target_collection.where(target_collection_class.primary_key => primary_keys)
50
+ .pluck(foreign_key)
51
+ .uniq
52
+ dataset.where(source_collection_class.primary_key => fks)
53
+ end
43
54
  end
44
55
 
45
56
  private
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module DB
3
- VERSION = '1.4.0'
3
+ VERSION = '2.2.0'
4
4
  end
5
5
  end
@@ -24,8 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency 'timing', '~> 0.1', '>= 0.1.3'
25
25
  spec.add_runtime_dependency 'class_config', '~> 0.0', '>= 0.0.2'
26
26
  spec.add_runtime_dependency 'multi_require', '~> 1.0'
27
+ spec.add_runtime_dependency 'hierarchical_graph', '~> 1.0'
28
+ spec.add_runtime_dependency 'hash_ext', '~> 0.5'
27
29
 
28
- spec.add_development_dependency 'rake', '~> 11.0'
30
+ spec.add_development_dependency 'rake', '~> 12.3'
29
31
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
30
32
  spec.add_development_dependency 'minitest-colorin', '~> 0.1'
31
33
  spec.add_development_dependency 'minitest-line', '~> 0.6'
@@ -38,10 +40,4 @@ Gem::Specification.new do |spec|
38
40
  else
39
41
  spec.add_development_dependency 'sqlite3', '~> 1.3'
40
42
  end
41
-
42
- if RUBY_VERSION < '2'
43
- spec.add_development_dependency 'term-ansicolor', '~> 1.3.0'
44
- spec.add_development_dependency 'tins', '~> 1.6.0'
45
- spec.add_development_dependency 'json', '~> 1.8'
46
- end
47
43
  end
@@ -6,18 +6,21 @@ describe 'Collection' do
6
6
 
7
7
  it 'Implicit' do
8
8
  Users.collection_name.must_equal :users
9
- Users.collection_fields.must_equal [:id, :name]
9
+ Users.collection_attributes.must_equal [:id, :name]
10
10
  Users.model.must_equal User
11
11
  Users.primary_key.must_equal :id
12
12
  Users.foreign_key.must_equal :user_id
13
+ Users.data_source_name.must_equal :default
13
14
  end
14
15
 
15
16
  it 'Explicit' do
16
17
  People.collection_name.must_equal :people
17
- People.collection_fields.must_equal [:document_number, :first_name, :last_name, :birth_date, :user_id]
18
+ People.collection_attributes.must_equal [:document_number, :first_name, :last_name, :birth_date, :user_id]
18
19
  People.model.must_equal Person
19
20
  People.primary_key.must_equal :document_number
20
21
  People.foreign_key.must_equal :document_number
22
+
23
+ Languages.data_source_name.must_equal :custom
21
24
  end
22
25
 
23
26
  it 'Lazy model name' do
@@ -42,11 +45,11 @@ describe 'Collection' do
42
45
  user_id = db[:users].insert name: 'User 1'
43
46
 
44
47
  1.upto(2) do |i|
45
- db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...'
48
+ db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...', language_id: 1
46
49
  db[:categories].insert name: "Category #{i}"
47
50
  end
48
51
 
49
- post_id = posts.insert user_id: user_id, title: 'Post title', body: '...', categories: [1,2]
52
+ post_id = posts.insert user_id: user_id, title: 'Post title', body: '...', categories: [1,2], language_id: 1
50
53
  category_id = categories.insert name: 'Category', posts: [1,2]
51
54
 
52
55
  db[:categories_posts].where(post_id: post_id).map(:category_id).must_equal [1,2]
@@ -59,7 +62,7 @@ describe 'Collection' do
59
62
  end
60
63
 
61
64
  user_id = db[:users].insert name: 'User 1'
62
- post_id = db[:posts].insert user_id: user_id, title: 'Post title', body: '...'
65
+ post_id = db[:posts].insert user_id: user_id, title: 'Post title', body: '...', language_id: 1
63
66
  1.upto(2) { |category_id| db[:categories_posts].insert post_id: post_id, category_id: category_id }
64
67
 
65
68
  posts.insert_relations post_id, categories: [3]
@@ -91,7 +94,7 @@ describe 'Collection' do
91
94
  user_id = db[:users].insert name: 'User 1'
92
95
 
93
96
  1.upto(3) do |i|
94
- db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...'
97
+ db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...', language_id: 1
95
98
  db[:categories].insert name: "Category #{i}"
96
99
  end
97
100
 
@@ -114,7 +117,7 @@ describe 'Collection' do
114
117
 
115
118
  it 'Bulk update' do
116
119
  user_id = db[:users].insert name: 'User 1'
117
- 1.upto(3) { |i| db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...' }
120
+ 1.upto(3) { |i| db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...', language_id: 1 }
118
121
 
119
122
  posts.bulk_update(body: 'Updated ...') { where id: [1,2] }
120
123
 
@@ -139,7 +142,7 @@ describe 'Collection' do
139
142
  end
140
143
 
141
144
  user_id = db[:users].insert name: 'User 1'
142
- post_id = db[:posts].insert user_id: user_id, title: 'Post title', body: '...'
145
+ post_id = db[:posts].insert user_id: user_id, title: 'Post title', body: '...', language_id: 1
143
146
  1.upto(3) { |category_id| db[:categories_posts].insert post_id: post_id, category_id: category_id }
144
147
 
145
148
  posts.delete_relations post_id, categories: [3]
@@ -170,7 +173,7 @@ describe 'Collection' do
170
173
  category_id = db[:categories].insert name: "Category #{i}"
171
174
 
172
175
  1.upto(3) do |n|
173
- post_id = db[:posts].insert user_id: user_id, title: "Post #{i}.#{n}", body: '...'
176
+ post_id = db[:posts].insert user_id: user_id, title: "Post #{i}.#{n}", body: '...', language_id: 1
174
177
  db[:categories_posts].insert post_id: post_id, category_id: category_id
175
178
  end
176
179
  end
@@ -227,6 +230,65 @@ describe 'Collection' do
227
230
 
228
231
  end
229
232
 
233
+ describe 'Multiple data sources' do
234
+
235
+ before do
236
+ 1.upto(3) do |i|
237
+ db[:users].insert name: "User #{i}"
238
+ db[:people].insert document_number: "document_#{i}",
239
+ first_name: "John #{i}",
240
+ last_name: "Doe #{i}",
241
+ birth_date: Time.now - i,
242
+ user_id: i
243
+ end
244
+ end
245
+
246
+ it 'Insert' do
247
+ id = languages.insert name: 'Spanish', people: ['document_1', 'document_2']
248
+
249
+ custom_db[:languages][id: id][:name].must_equal 'Spanish'
250
+ db[:languages_people].where(language_id: id).select_map(:document_number).must_equal ['document_1', 'document_2']
251
+ end
252
+
253
+ it 'Update' do
254
+ id = custom_db[:languages].insert name: 'Spanish'
255
+ db[:languages_people].insert language_id: id, document_number: 'document_1'
256
+
257
+ custom_db[:languages][id: id][:name].must_equal 'Spanish'
258
+ db[:languages_people].where(language_id: id).select_map(:document_number).must_equal ['document_1']
259
+
260
+ languages.update id, name: 'English', people: ['document_2', 'document_3']
261
+
262
+ custom_db[:languages][id: id][:name].must_equal 'English'
263
+ db[:languages_people].where(language_id: id).select_map(:document_number).must_equal ['document_2', 'document_3']
264
+ end
265
+
266
+ it 'Delete' do
267
+ id = custom_db[:languages].insert name: 'Spanish'
268
+ db[:languages_people].insert language_id: id, document_number: 'document_1'
269
+ db[:posts].insert user_id: 1, title: 'Post 1', body: '...', language_id: id
270
+
271
+ languages.delete id
272
+
273
+ custom_db[:languages].count.must_equal 0
274
+ db[:languages_people].where(language_id: id).count.must_equal 1
275
+ db[:posts].where(language_id: id).count.must_equal 1
276
+ end
277
+
278
+ it 'Delete cascade' do
279
+ id = custom_db[:languages].insert name: 'Spanish'
280
+ db[:languages_people].insert language_id: id, document_number: 'document_1'
281
+ db[:posts].insert user_id: 1, title: 'Post 1', body: '...', language_id: id
282
+
283
+ languages.delete_cascade id
284
+
285
+ custom_db[:languages].count.must_equal 0
286
+ db[:languages_people].where(language_id: id).count.must_equal 0
287
+ db[:posts].where(language_id: id).count.must_equal 0
288
+ end
289
+
290
+ end
291
+
230
292
  end
231
293
 
232
294
  describe 'Queries' do
@@ -239,11 +301,29 @@ describe 'Collection' do
239
301
 
240
302
  it 'Find graph' do
241
303
  user_id = db[:users].insert name: 'User 1'
242
- db[:posts].insert user_id: user_id, title: 'Post 1', body: '...'
304
+ db[:posts].insert user_id: user_id, title: 'Post 1', body: '...', language_id: 1
243
305
 
244
306
  users.find_graph(user_id, :posts).must_equal User.new id: user_id, name: 'User 1', posts: posts.all
245
307
  end
246
308
 
309
+ it 'Select attributes' do
310
+ id = db[:users].insert name: 'User 1'
311
+
312
+ users.select_attributes(:id).all.must_equal [User.new(id: id)]
313
+ end
314
+
315
+ it 'Exclude attributes' do
316
+ db[:users].insert name: 'User 1'
317
+
318
+ users.exclude_attributes(:id).all.must_equal [User.new(name: 'User 1')]
319
+ end
320
+
321
+ it 'All attributes' do
322
+ id = db[:users].insert name: 'User 1'
323
+
324
+ users.select_attributes(:id).all_attributes.all.must_equal [User.new(id: id, name: 'User 1')]
325
+ end
326
+
247
327
  it 'Count' do
248
328
  1.upto(10) { |i| db[:users].insert name: "User #{i}" }
249
329
 
@@ -298,42 +378,51 @@ describe 'Collection' do
298
378
 
299
379
  it 'Chain dataset as query' do
300
380
  1.upto(2) { |i| db[:users].insert name: "User #{i}" }
301
- 1.upto(3) { |i| db[:posts].insert user_id: 1, title: "Post #{i}", body: '...' }
381
+ 1.upto(3) { |i| db[:posts].insert user_id: 1, title: "Post #{i}", body: '...', language_id: 1 }
302
382
  1.upto(2) { |i| db[:comments].insert post_id: i, user_id: 2, text: 'Comment' }
303
383
 
304
384
  models = posts.commented_by(2).all
305
- models.must_equal [1,2].map { |i| Post.new(id: i, user_id: 1, title: "Post #{i}", body: '...') }
385
+ models.must_equal [1,2].map { |i| Post.new(id: i, user_id: 1, title: "Post #{i}", body: '...', language_id: 1) }
306
386
  end
307
387
 
308
388
  it 'Custom query' do
309
389
  1.upto(2) { |i| db[:users].insert name: "User #{i}" }
310
- 1.upto(3) { |i| db[:posts].insert user_id: 1, title: "Post #{i}", body: '...' }
390
+ 1.upto(3) { |i| db[:posts].insert user_id: 1, title: "Post #{i}", body: '...', language_id: 1 }
311
391
  1.upto(2) { |i| db[:comments].insert post_id: i, user_id: 2, text: 'Comment' }
312
392
 
313
393
  models = comments.posts_commented_by(2)
314
- models.must_equal [1,2].map { |i| Post.new(id: i, user_id: 1, title: "Post #{i}", body: '...') }
394
+ models.must_equal [1,2].map { |i| Post.new(id: i, user_id: 1, title: "Post #{i}", body: '...', language_id: 1) }
315
395
  end
316
396
 
317
397
  describe 'Named queries' do
318
398
 
319
399
  before do
400
+ custom_db[:languages].insert name: 'Spanish'
401
+ custom_db[:languages].insert name: 'English'
402
+
320
403
  1.upto(2) do |i|
321
404
  db[:categories].insert name: "Category #{i}"
405
+
322
406
  db[:users].insert name: "User #{i}"
407
+
323
408
  db[:people].insert document_number: "document_#{i}",
324
409
  first_name: "John #{i}",
325
410
  last_name: "Doe #{i}",
326
411
  birth_date: Time.now - i,
327
412
  user_id: i
413
+
328
414
  end
329
415
 
416
+ db[:languages_people].insert language_id: 1, document_number: 'document_1'
417
+ db[:languages_people].insert language_id: 2, document_number: 'document_2'
418
+
330
419
  1.upto(3) do |i|
331
- db[:posts].insert user_id: 1, title: "Post #{i}", body: '...'
420
+ db[:posts].insert user_id: 1, title: "Post #{i}", body: '...', language_id: 1
332
421
  db[:categories_posts].insert category_id: 1, post_id: i
333
422
  end
334
423
 
335
424
  4.upto(5) do |i|
336
- db[:posts].insert user_id: 2, title: "Post #{i}", body: '...'
425
+ db[:posts].insert user_id: 2, title: "Post #{i}", body: '...', language_id: 2
337
426
  db[:categories_posts].insert category_id: 2, post_id: i
338
427
  end
339
428
  end
@@ -356,6 +445,23 @@ describe 'Collection' do
356
445
  users.with_people('document_1').primary_keys.must_equal [1]
357
446
  end
358
447
 
448
+ describe 'Multiple data sources' do
449
+
450
+ it 'One to many' do
451
+ languages.with_posts([1,2]).primary_keys.must_equal [1]
452
+ end
453
+
454
+ it 'Many to one' do
455
+ posts.with_languages([2]).primary_keys.must_equal [4,5]
456
+ end
457
+
458
+ it 'Many to Many' do
459
+ languages.with_people(['document_1']).primary_keys.must_equal [1]
460
+ people.with_languages([2]).primary_keys.must_equal ['document_2']
461
+ end
462
+
463
+ end
464
+
359
465
  end
360
466
 
361
467
  it 'Global' do
@@ -382,7 +488,7 @@ describe 'Collection' do
382
488
  birth_date: Time.now - i,
383
489
  user_id: i
384
490
  db[:categories].insert name: "Category #{i}"
385
- db[:posts].insert user_id: i, title: "Post #{i}", body: '...'
491
+ db[:posts].insert user_id: i, title: "Post #{i}", body: '...', language_id: 1
386
492
  db[:categories_posts].insert post_id: i, category_id: i
387
493
  end
388
494
 
@@ -430,21 +536,21 @@ describe 'Collection' do
430
536
  stubs = Proc.new do |sql|
431
537
  case sql
432
538
 
433
- when 'SELECT * FROM custom_schema.users',
434
- 'SELECT * FROM custom_schema.users WHERE (id IN (2, 1))'
539
+ when 'SELECT users.* FROM schema_1.users',
540
+ 'SELECT users.* FROM schema_1.users WHERE (users.id IN (2, 1))'
435
541
  [
436
542
  {id: 1},
437
543
  {id: 2}
438
544
  ]
439
545
 
440
- when 'SELECT * FROM custom_schema.posts',
441
- 'SELECT * FROM custom_schema.posts WHERE (user_id IN (1, 2))'
546
+ when 'SELECT posts.* FROM schema_1.posts',
547
+ 'SELECT posts.* FROM schema_1.posts WHERE (posts.user_id IN (1, 2))'
442
548
  [
443
- {id: 3, user_id: 1},
444
- {id: 4, user_id: 2}
549
+ {id: 3, user_id: 1, language_id: 1},
550
+ {id: 4, user_id: 2, language_id: 2}
445
551
  ]
446
552
 
447
- when 'SELECT * FROM custom_schema.comments WHERE (post_id IN (3, 4))'
553
+ when 'SELECT comments.* FROM schema_1.comments WHERE (comments.post_id IN (3, 4))'
448
554
  [
449
555
  {id: 5, user_id: 2, post_id: 3},
450
556
  {id: 6, user_id: 1, post_id: 3},
@@ -452,87 +558,152 @@ describe 'Collection' do
452
558
  {id: 8, user_id: 2, post_id: 4}
453
559
  ]
454
560
 
455
- else
456
- nil
561
+ when 'SELECT languages.* FROM schema_2.languages WHERE (languages.id IN (1, 2))'
562
+ [
563
+ {id: 1},
564
+ {id: 2}
565
+ ]
566
+
457
567
  end
458
568
  end
459
569
 
460
- Sequel.mock(fetch: stubs, autoid: 1).tap do |mock|
461
- get_schema_block = ->(table_name) { db.schema table_name }
462
- mock.define_singleton_method(:schema_parse_table) do |table_name, opts|
463
- get_schema_block.call table_name
570
+ last_id = 0
571
+ autoid = Proc.new do |sql|
572
+ case sql
573
+ when "INSERT INTO schema_1.people (document_number, first_name, last_name, birth_date, user_id) VALUES ('document_1', 'John', 'Doe', '2020-04-24 00:00:00.000000', 1)"
574
+ 'document_1'
575
+ else
576
+ last_id += 1
464
577
  end
465
578
  end
579
+
580
+ Sequel.mock fetch: stubs, autoid: autoid
581
+ end
582
+
583
+ let :stub_environment do
584
+ Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(stub_db, :schema_1),
585
+ custom: Rasti::DB::DataSource.new(stub_db, :schema_2)
466
586
  end
467
587
 
468
- let(:stub_users) { Users.new stub_db, :custom_schema }
469
- let(:stub_posts) { Posts.new stub_db, :custom_schema }
470
- let(:stub_comments) { Comments.new stub_db, :custom_schema }
588
+ let(:stub_users) { Users.new stub_environment }
589
+ let(:stub_posts) { Posts.new stub_environment }
590
+ let(:stub_comments) { Comments.new stub_environment }
591
+ let(:stub_people) { People.new stub_environment }
592
+ let(:stub_languages) { Languages.new stub_environment }
471
593
 
472
594
  it 'Insert' do
473
595
  stub_users.insert name: 'User 1'
596
+
474
597
  stub_db.sqls.must_equal [
475
598
  'BEGIN',
476
- "INSERT INTO custom_schema.users (name) VALUES ('User 1')",
599
+ "INSERT INTO schema_1.users (name) VALUES ('User 1')",
477
600
  'COMMIT'
478
601
  ]
479
602
  end
480
603
 
481
604
  it 'Insert with many to many relation' do
482
- stub_posts.insert user_id: 1, title: 'Post 1', body: '...', categories: [2,3]
605
+ stub_posts.insert user_id: 1, title: 'Post 1', body: '...', categories: [2,3], language_id: 1
606
+
607
+ stub_db.sqls.must_equal [
608
+ 'BEGIN',
609
+ "INSERT INTO schema_1.posts (user_id, title, body, language_id) VALUES (1, 'Post 1', '...', 1)",
610
+ 'DELETE FROM schema_1.categories_posts WHERE (post_id IN (1))',
611
+ 'INSERT INTO schema_1.categories_posts (post_id, category_id) VALUES (1, 2)',
612
+ 'INSERT INTO schema_1.categories_posts (post_id, category_id) VALUES (1, 3)',
613
+ 'COMMIT'
614
+ ]
615
+ end
616
+
617
+ it 'Insert in multiple schemas' do
618
+ stub_languages.insert name: 'Spanish'
619
+
620
+ stub_users.insert name: 'User 1'
621
+
622
+ stub_people.insert document_number: 'document_1',
623
+ first_name: 'John',
624
+ last_name: 'Doe',
625
+ birth_date: Time.parse('2020-04-24'),
626
+ user_id: 1,
627
+ languages: [1]
628
+
483
629
  stub_db.sqls.must_equal [
484
630
  'BEGIN',
485
- "INSERT INTO custom_schema.posts (user_id, title, body) VALUES (1, 'Post 1', '...')",
486
- 'DELETE FROM custom_schema.categories_posts WHERE (post_id IN (1))',
487
- 'INSERT INTO custom_schema.categories_posts (post_id, category_id) VALUES (1, 2)',
488
- 'INSERT INTO custom_schema.categories_posts (post_id, category_id) VALUES (1, 3)',
631
+ "INSERT INTO schema_2.languages (name) VALUES ('Spanish')",
632
+ 'COMMIT',
633
+ 'BEGIN',
634
+ "INSERT INTO schema_1.users (name) VALUES ('User 1')",
635
+ 'COMMIT',
636
+ 'BEGIN',
637
+ "INSERT INTO schema_1.people (document_number, first_name, last_name, birth_date, user_id) VALUES ('document_1', 'John', 'Doe', '2020-04-24 00:00:00.000000', 1)",
638
+ "DELETE FROM schema_1.languages_people WHERE (document_number IN ('document_1'))",
639
+ "INSERT INTO schema_1.languages_people (document_number, language_id) VALUES ('document_1', 1)",
489
640
  'COMMIT'
490
641
  ]
491
642
  end
492
643
 
493
644
  it 'Update' do
494
645
  stub_users.update 1, name: 'Updated name'
646
+
495
647
  stub_db.sqls.must_equal [
496
648
  'BEGIN',
497
- "UPDATE custom_schema.users SET name = 'Updated name' WHERE (id = 1)",
649
+ "UPDATE schema_1.users SET name = 'Updated name' WHERE (id = 1)",
498
650
  'COMMIT'
499
651
  ]
500
652
  end
501
653
 
502
654
  it 'Delete' do
503
655
  stub_users.delete 1
504
- stub_db.sqls.must_equal ['DELETE FROM custom_schema.users WHERE (id = 1)']
656
+
657
+ stub_db.sqls.must_equal [
658
+ 'DELETE FROM schema_1.users WHERE (id = 1)'
659
+ ]
505
660
  end
506
661
 
507
662
  it 'Chained query' do
508
663
  stub_users.where(id: [1,2]).limit(1).order(:name).all
509
- stub_db.sqls.must_equal ['SELECT * FROM custom_schema.users WHERE (id IN (1, 2)) ORDER BY name LIMIT 1']
664
+
665
+ stub_db.sqls.must_equal [
666
+ 'SELECT users.* FROM schema_1.users WHERE (users.id IN (1, 2)) ORDER BY users.name LIMIT 1'
667
+ ]
510
668
  end
511
669
 
512
670
  it 'Graph' do
513
- stub_posts.graph(:user, :categories, 'comments.user.posts.categories').all
671
+ stub_posts.graph(:user, :categories, 'comments.user.posts.categories', 'language.people').all
672
+
673
+ stub_db.sqls.must_equal [
674
+ 'SELECT posts.* FROM schema_1.posts',
675
+ 'SELECT categories.*, categories_posts.post_id AS source_foreign_key FROM schema_1.categories INNER JOIN schema_1.categories_posts ON (schema_1.categories_posts.category_id = schema_1.categories.id) WHERE (categories_posts.post_id IN (3, 4))',
676
+ 'SELECT comments.* FROM schema_1.comments WHERE (comments.post_id IN (3, 4))',
677
+ 'SELECT users.* FROM schema_1.users WHERE (users.id IN (2, 1))',
678
+ 'SELECT posts.* FROM schema_1.posts WHERE (posts.user_id IN (1, 2))',
679
+ 'SELECT categories.*, categories_posts.post_id AS source_foreign_key FROM schema_1.categories INNER JOIN schema_1.categories_posts ON (schema_1.categories_posts.category_id = schema_1.categories.id) WHERE (categories_posts.post_id IN (3, 4))',
680
+ 'SELECT languages.* FROM schema_2.languages WHERE (languages.id IN (1, 2))',
681
+ 'SELECT people.*, languages_people.language_id AS source_foreign_key FROM schema_1.people INNER JOIN schema_1.languages_people ON (schema_1.languages_people.document_number = schema_1.people.document_number) WHERE (languages_people.language_id IN (1, 2))',
682
+ 'SELECT users.* FROM schema_1.users WHERE (users.id IN (1, 2))'
683
+ ]
684
+ end
685
+
686
+ it 'Join' do
687
+ stub_posts.join('user.person').where(document_number: 'document_1').all
688
+
514
689
  stub_db.sqls.must_equal [
515
- 'SELECT * FROM custom_schema.posts',
516
- 'SELECT * FROM custom_schema.users WHERE (id IN (1, 2))',
517
- 'SELECT custom_schema.categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))',
518
- 'SELECT * FROM custom_schema.comments WHERE (post_id IN (3, 4))',
519
- 'SELECT * FROM custom_schema.users WHERE (id IN (2, 1))',
520
- 'SELECT * FROM custom_schema.posts WHERE (user_id IN (1, 2))',
521
- 'SELECT custom_schema.categories.*, custom_schema.categories_posts.post_id AS source_foreign_key FROM custom_schema.categories INNER JOIN custom_schema.categories_posts ON (custom_schema.categories_posts.category_id = custom_schema.categories.id) WHERE (custom_schema.categories_posts.post_id IN (3, 4))'
690
+ "SELECT DISTINCT posts.* FROM schema_1.posts INNER JOIN schema_1.users AS user ON (user.id = posts.user_id) INNER JOIN schema_1.people AS user__person ON (user__person.user_id = user.id) WHERE (posts.document_number = 'document_1')"
522
691
  ]
523
692
  end
524
693
 
525
694
  it 'Named query' do
526
695
  stub_posts.commented_by(1).all
696
+
527
697
  stub_db.sqls.must_equal [
528
- 'SELECT DISTINCT custom_schema.posts.* FROM custom_schema.posts INNER JOIN custom_schema.comments ON (custom_schema.comments.post_id = custom_schema.posts.id) WHERE (custom_schema.comments.user_id = 1)'
698
+ 'SELECT DISTINCT posts.* FROM schema_1.posts INNER JOIN schema_1.comments ON (schema_1.comments.post_id = schema_1.posts.id) WHERE (comments.user_id = 1)'
529
699
  ]
530
700
  end
531
701
 
532
702
  it 'Custom query' do
533
703
  stub_comments.posts_commented_by(2)
704
+
534
705
  stub_db.sqls.must_equal [
535
- 'SELECT custom_schema.posts.* FROM custom_schema.comments INNER JOIN custom_schema.posts ON (custom_schema.posts.id = custom_schema.comments.post_id) WHERE (comments.user_id = 2)'
706
+ 'SELECT posts.* FROM schema_1.comments INNER JOIN schema_1.posts ON (schema_1.posts.id = schema_1.comments.post_id) WHERE (comments.user_id = 2)'
536
707
  ]
537
708
  end
538
709