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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/README.md +88 -24
- data/lib/rasti/db.rb +2 -1
- data/lib/rasti/db/collection.rb +79 -46
- data/lib/rasti/db/computed_attribute.rb +22 -0
- data/lib/rasti/db/data_source.rb +18 -0
- data/lib/rasti/db/environment.rb +32 -0
- data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
- data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
- data/lib/rasti/db/nql/nodes/comparisons/base.rb +5 -1
- data/lib/rasti/db/nql/nodes/comparisons/equal.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/include.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/like.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +2 -2
- data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +2 -2
- data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
- data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
- data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
- data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
- data/lib/rasti/db/nql/syntax.rb +33 -33
- data/lib/rasti/db/nql/syntax.treetop +12 -12
- data/lib/rasti/db/query.rb +107 -43
- data/lib/rasti/db/relations/base.rb +22 -8
- data/lib/rasti/db/relations/graph.rb +129 -0
- data/lib/rasti/db/relations/many_to_many.rb +58 -24
- data/lib/rasti/db/relations/many_to_one.rb +17 -12
- data/lib/rasti/db/relations/one_to_many.rb +27 -16
- data/lib/rasti/db/version.rb +1 -1
- data/rasti-db.gemspec +3 -7
- data/spec/collection_spec.rb +223 -52
- data/spec/computed_attribute_spec.rb +32 -0
- data/spec/minitest_helper.rb +76 -15
- data/spec/model_spec.rb +4 -2
- data/spec/nql/computed_attributes_spec.rb +29 -0
- data/spec/nql/filter_condition_spec.rb +4 -2
- data/spec/nql/syntax_parser_spec.rb +12 -5
- data/spec/query_spec.rb +319 -85
- data/spec/relations_spec.rb +27 -7
- metadata +41 -7
- data/lib/rasti/db/helpers.rb +0 -16
- data/lib/rasti/db/nql/nodes/field.rb +0 -23
- 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
|
data/spec/minitest_helper.rb
CHANGED
@@ -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(
|
42
|
-
.where(
|
43
|
-
.select_all(
|
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(
|
56
|
-
.select_all(
|
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
|
113
|
+
let(:users) { Users.new environment }
|
78
114
|
|
79
|
-
let(:posts) { Posts.new
|
115
|
+
let(:posts) { Posts.new environment }
|
80
116
|
|
81
|
-
let(:comments) { Comments.new
|
117
|
+
let(:comments) { Comments.new environment }
|
82
118
|
|
83
|
-
let(:categories) { Categories.new
|
119
|
+
let(:categories) { Categories.new environment }
|
84
120
|
|
85
|
-
let(:people) { People.new
|
121
|
+
let(:people) { People.new environment }
|
86
122
|
|
87
|
-
let
|
88
|
-
|
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
|
|
data/spec/model_spec.rb
CHANGED
@@ -10,7 +10,7 @@ describe 'Model' do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'Invalid definition' do
|
13
|
-
error = proc {
|
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
|
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.
|
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.
|
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
|
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.
|
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
|
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
|
data/spec/query_spec.rb
CHANGED
@@ -3,18 +3,46 @@ require 'minitest_helper'
|
|
3
3
|
describe 'Query' do
|
4
4
|
|
5
5
|
before do
|
6
|
-
|
6
|
+
custom_db[:languages].insert name: 'Spanish'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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 '
|
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 '
|
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
|
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
|
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
|
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
|
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
|
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] =>
|
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
|
-
|
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
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
.
|
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 =
|
227
|
-
.
|
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
|