rasti-db 1.4.0 → 1.5.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 +37 -6
- data/lib/rasti/db/collection.rb +17 -15
- data/lib/rasti/db/query.rb +66 -34
- data/lib/rasti/db/relations/graph.rb +135 -0
- data/lib/rasti/db/relations/many_to_many.rb +5 -5
- data/lib/rasti/db/relations/many_to_one.rb +10 -7
- data/lib/rasti/db/relations/one_to_many.rb +8 -5
- data/lib/rasti/db/version.rb +1 -1
- data/lib/rasti/db.rb +2 -0
- data/rasti-db.gemspec +3 -7
- data/spec/collection_spec.rb +36 -17
- data/spec/minitest_helper.rb +3 -2
- data/spec/query_spec.rb +96 -59
- data/spec/relations_spec.rb +4 -4
- metadata +33 -5
- data/lib/rasti/db/relations/graph_builder.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b801c94dd51c4ee11f624cb542d784a42e445622e0090c42cb9d47322b87e855
|
4
|
+
data.tar.gz: 518c58daf408e477f80cdac52837a0e2d3256938352c888a73e369f3eca5b7a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae1452c9b77c6d50f9aa8b05b85f9970c801369e7e3a67a7a88436762fbacea4191390e114be066458d7bc2dc563f834cda93a364185354c74267db86798a889
|
7
|
+
data.tar.gz: 2707b44943ea63f17cd0f613c4fa08bdfc0663e8fde17f604c8c6ce9ba8f1fff23b4fa1641dfe42f77810a55fda6794491f5e8d8cb756c4fc19adb1cbb8cba76
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
[](https://travis-ci.org/gabynaiman/rasti-db)
|
5
5
|
[](https://coveralls.io/github/gabynaiman/rasti-db?branch=master)
|
6
6
|
[](https://codeclimate.com/github/gabynaiman/rasti-db)
|
7
|
-
[](https://gemnasium.com/gabynaiman/rasti-db)
|
8
7
|
|
9
8
|
Database collections and relations
|
10
9
|
|
@@ -108,7 +107,7 @@ class Posts < Rasti::DB::Collection
|
|
108
107
|
chainable do
|
109
108
|
dataset.join(with_schema(:comments), post_id: :id)
|
110
109
|
.where(with_schema(:comments, :user_id) => user_id)
|
111
|
-
.select_all(
|
110
|
+
.select_all(:posts)
|
112
111
|
.distinct
|
113
112
|
end
|
114
113
|
end
|
@@ -162,17 +161,49 @@ end
|
|
162
161
|
posts.all # => [Post, ...]
|
163
162
|
posts.first # => Post
|
164
163
|
posts.count # => 1
|
164
|
+
|
165
165
|
posts.where(id: [1,2]) # => [Post, ...]
|
166
166
|
posts.where{id > 1}.limit(10).offset(20) } # => [Post, ...]
|
167
|
-
|
168
|
-
posts.created_by(1) # => [Post, ...]
|
169
|
-
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
|
170
|
-
posts.with_categories([1,2]) # => [Post, ...]
|
167
|
+
|
171
168
|
posts.where(id: [1,2]).raw # => [{id:1, ...}, {id:2, ...}]
|
172
169
|
posts.where(id: [1,2]).primary_keys # => [1,2]
|
173
170
|
posts.where(id: [1,2]).pluck(:id) # => [1,2]
|
174
171
|
posts.where(id: [1,2]).pluck(:id, :title) # => [[1, ...], [2, ...]]
|
172
|
+
|
173
|
+
posts.created_by(1) # => [Post, ...]
|
174
|
+
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
|
175
|
+
posts.with_categories([1,2]) # => [Post, ...]
|
176
|
+
|
177
|
+
posts.graph(:user, :categories, 'comments.user') # => [Post(User, [Categories, ...], [Comments(User)]), ...]
|
178
|
+
|
175
179
|
posts.join(:user).where(name: 'User 4') # => [Post, ...]
|
180
|
+
|
181
|
+
posts.select_attributes(:id, :title) # => [Post, ...]
|
182
|
+
posts.exclude_attributes(:id, :title) # => [Post, ...]
|
183
|
+
posts.all_attributes # => [Post, ...]
|
184
|
+
|
185
|
+
posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
|
186
|
+
posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
|
187
|
+
posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
|
188
|
+
```
|
189
|
+
### Natural Query Language
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
posts.nql('id = 1') # => Equal
|
193
|
+
posts.nql('id != 1') # => Not equal
|
194
|
+
posts.nql('title: My post') # => Include
|
195
|
+
posts.nql('title !: My post') # => Not include
|
196
|
+
posts.nql('title ~ My post') # => Insensitive like
|
197
|
+
posts.nql('id > 1') # => Greater
|
198
|
+
posts.nql('id >= 1') # => Greater or equal
|
199
|
+
posts.nql('id < 10') # => Less
|
200
|
+
posts.nql('id <= 10') # => Less or equal
|
201
|
+
|
202
|
+
posts.nql('id = 1 | id = 2') # => Or
|
203
|
+
posts.nql('id > 1 & title: "My post"') # => And
|
204
|
+
posts.nql('(id > 3 & id < 10) | title: "My post"') # => Precedence
|
205
|
+
|
206
|
+
posts.nql('comments.user.person.document_number = 7') # => Nested
|
176
207
|
```
|
177
208
|
|
178
209
|
## Development
|
data/lib/rasti/db/collection.rb
CHANGED
@@ -2,9 +2,8 @@ module Rasti
|
|
2
2
|
module DB
|
3
3
|
class Collection
|
4
4
|
|
5
|
-
QUERY_METHODS =
|
5
|
+
QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
|
6
6
|
|
7
|
-
include Enumerable
|
8
7
|
include Helpers::WithSchema
|
9
8
|
|
10
9
|
class << self
|
@@ -16,8 +15,8 @@ module Rasti
|
|
16
15
|
@collection_name ||= underscore(demodulize(name)).to_sym
|
17
16
|
end
|
18
17
|
|
19
|
-
def
|
20
|
-
@
|
18
|
+
def collection_attributes
|
19
|
+
@collection_attributes ||= model.attributes - relations.keys
|
21
20
|
end
|
22
21
|
|
23
22
|
def primary_key
|
@@ -80,7 +79,7 @@ module Rasti
|
|
80
79
|
queries[name] = lambda || block
|
81
80
|
|
82
81
|
define_method name do |*args|
|
83
|
-
|
82
|
+
default_query.instance_exec(*args, &self.class.queries.fetch(name))
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
@@ -93,6 +92,12 @@ module Rasti
|
|
93
92
|
@schema = schema ? schema.to_sym : nil
|
94
93
|
end
|
95
94
|
|
95
|
+
QUERY_METHODS.each do |method|
|
96
|
+
define_method method do |*args, &block|
|
97
|
+
default_query.public_send method, *args, &block
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
96
101
|
def dataset
|
97
102
|
db[qualified_collection_name]
|
98
103
|
end
|
@@ -172,12 +177,6 @@ module Rasti
|
|
172
177
|
where(self.class.primary_key => primary_key).graph(*relations).first
|
173
178
|
end
|
174
179
|
|
175
|
-
QUERY_METHODS.each do |method|
|
176
|
-
define_method method do |*args, &block|
|
177
|
-
query.public_send method, *args, &block
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
180
|
def exists?(filter=nil, &block)
|
182
181
|
build_query(filter, &block).any?
|
183
182
|
end
|
@@ -199,16 +198,19 @@ module Rasti
|
|
199
198
|
schema.nil? ? Sequel[self.class.collection_name] : Sequel[schema][self.class.collection_name]
|
200
199
|
end
|
201
200
|
|
202
|
-
def
|
203
|
-
Query.new self.class,
|
201
|
+
def default_query
|
202
|
+
Query.new collection_class: self.class,
|
203
|
+
dataset: dataset.select_all(self.class.collection_name),
|
204
|
+
schema: schema
|
204
205
|
end
|
205
206
|
|
206
207
|
def build_query(filter=nil, &block)
|
207
208
|
raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
|
209
|
+
|
208
210
|
if filter
|
209
|
-
|
211
|
+
default_query.where filter
|
210
212
|
else
|
211
|
-
block.arity == 0 ?
|
213
|
+
block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
|
212
214
|
end
|
213
215
|
end
|
214
216
|
|
data/lib/rasti/db/query.rb
CHANGED
@@ -7,19 +7,25 @@ module Rasti
|
|
7
7
|
include Enumerable
|
8
8
|
include Helpers::WithSchema
|
9
9
|
|
10
|
-
def initialize(collection_class
|
10
|
+
def initialize(collection_class:, dataset:, relations_graph:nil, schema:nil)
|
11
11
|
@collection_class = collection_class
|
12
12
|
@dataset = dataset
|
13
|
-
@
|
13
|
+
@relations_graph = relations_graph || Relations::Graph.new(dataset.db, schema, collection_class)
|
14
14
|
@schema = schema
|
15
15
|
end
|
16
16
|
|
17
|
+
DATASET_CHAINED_METHODS.each do |method|
|
18
|
+
define_method method do |*args, &block|
|
19
|
+
build_query dataset: dataset.public_send(method, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
17
23
|
def raw
|
18
24
|
dataset.all
|
19
25
|
end
|
20
26
|
|
21
27
|
def pluck(*attributes)
|
22
|
-
ds = dataset.select(*attributes.map { |
|
28
|
+
ds = dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
|
23
29
|
attributes.count == 1 ? ds.map { |r| r[attributes.first] } : ds.map(&:values)
|
24
30
|
end
|
25
31
|
|
@@ -27,6 +33,31 @@ module Rasti
|
|
27
33
|
pluck collection_class.primary_key
|
28
34
|
end
|
29
35
|
|
36
|
+
def select_attributes(*attributes)
|
37
|
+
build_query dataset: dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
|
38
|
+
end
|
39
|
+
|
40
|
+
def exclude_attributes(*excluded_attributes)
|
41
|
+
attributes = collection_class.collection_attributes - excluded_attributes
|
42
|
+
select_attributes(*attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_attributes
|
46
|
+
build_query dataset: dataset.select_all(collection_class.collection_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def select_graph_attributes(selected_attributes)
|
50
|
+
build_query relations_graph: relations_graph.merge(selected_attributes: selected_attributes)
|
51
|
+
end
|
52
|
+
|
53
|
+
def exclude_graph_attributes(excluded_attributes)
|
54
|
+
build_query relations_graph: relations_graph.merge(excluded_attributes: excluded_attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_graph_attributes(*relations)
|
58
|
+
build_query relations_graph: relations_graph.with_all_attributes_for(relations)
|
59
|
+
end
|
60
|
+
|
30
61
|
def all
|
31
62
|
with_graph(dataset.all).map do |row|
|
32
63
|
collection_class.model.new row
|
@@ -38,27 +69,18 @@ module Rasti
|
|
38
69
|
all.each(&block)
|
39
70
|
end
|
40
71
|
|
41
|
-
|
42
|
-
|
43
|
-
Query.new collection_class,
|
44
|
-
dataset.public_send(method, *args, &block),
|
45
|
-
relations,
|
46
|
-
schema
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def graph(*rels)
|
51
|
-
Query.new collection_class,
|
52
|
-
dataset,
|
53
|
-
(relations | rels),
|
54
|
-
schema
|
72
|
+
def graph(*relations)
|
73
|
+
build_query relations_graph: relations_graph.merge(relations: relations)
|
55
74
|
end
|
56
75
|
|
57
|
-
def join(*
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
76
|
+
def join(*relations)
|
77
|
+
graph = Relations::Graph.new dataset.db, schema, collection_class, relations
|
78
|
+
|
79
|
+
ds = graph.add_joins(dataset)
|
80
|
+
.distinct
|
81
|
+
.select_all(collection_class.collection_name)
|
82
|
+
|
83
|
+
build_query dataset: ds
|
62
84
|
end
|
63
85
|
|
64
86
|
def count
|
@@ -105,25 +127,41 @@ module Rasti
|
|
105
127
|
|
106
128
|
private
|
107
129
|
|
130
|
+
attr_reader :collection_class, :dataset, :relations_graph, :schema
|
131
|
+
|
132
|
+
def build_query(**args)
|
133
|
+
current_args = {
|
134
|
+
collection_class: collection_class,
|
135
|
+
dataset: dataset,
|
136
|
+
relations_graph: relations_graph,
|
137
|
+
schema: schema
|
138
|
+
}
|
139
|
+
|
140
|
+
Query.new(**current_args.merge(args))
|
141
|
+
end
|
142
|
+
|
108
143
|
def chainable(&block)
|
109
|
-
|
110
|
-
Query.new collection_class, ds, relations, schema
|
144
|
+
build_query dataset: instance_eval(&block)
|
111
145
|
end
|
112
146
|
|
113
147
|
def with_related(relation_name, primary_keys)
|
114
148
|
ds = collection_class.relations[relation_name].apply_filter dataset, schema, primary_keys
|
115
|
-
|
149
|
+
build_query dataset: ds
|
116
150
|
end
|
117
151
|
|
118
152
|
def with_graph(data)
|
119
153
|
rows = data.is_a?(Array) ? data : [data]
|
120
|
-
|
154
|
+
relations_graph.fetch_graph rows
|
121
155
|
data
|
122
156
|
end
|
123
157
|
|
158
|
+
def nql_parser
|
159
|
+
NQL::SyntaxParser.new
|
160
|
+
end
|
161
|
+
|
124
162
|
def method_missing(method, *args, &block)
|
125
|
-
if collection_class.queries.key?
|
126
|
-
instance_exec(*args, &collection_class.queries
|
163
|
+
if collection_class.queries.key? method
|
164
|
+
instance_exec(*args, &collection_class.queries.fetch(method))
|
127
165
|
else
|
128
166
|
super
|
129
167
|
end
|
@@ -133,12 +171,6 @@ module Rasti
|
|
133
171
|
collection_class.queries.key?(method) || super
|
134
172
|
end
|
135
173
|
|
136
|
-
def nql_parser
|
137
|
-
NQL::SyntaxParser.new
|
138
|
-
end
|
139
|
-
|
140
|
-
attr_reader :collection_class, :dataset, :relations, :schema
|
141
|
-
|
142
174
|
end
|
143
175
|
end
|
144
176
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class Graph
|
5
|
+
|
6
|
+
def initialize(db, schema, collection_class, relations=[], selected_attributes={}, excluded_attributes={})
|
7
|
+
@db = db
|
8
|
+
@schema = schema
|
9
|
+
@collection_class = collection_class
|
10
|
+
@graph = build_graph relations,
|
11
|
+
Hash::Indifferent.new(selected_attributes),
|
12
|
+
Hash::Indifferent.new(excluded_attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge(relations:[], selected_attributes:{}, excluded_attributes:{})
|
16
|
+
Graph.new db,
|
17
|
+
schema,
|
18
|
+
collection_class,
|
19
|
+
(flat_relations | relations),
|
20
|
+
flat_selected_attributes.merge(selected_attributes),
|
21
|
+
flat_excluded_attributes.merge(excluded_attributes)
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_all_attributes_for(relations)
|
25
|
+
relations_with_all_attributes = relations.map { |r| [r, nil] }.to_h
|
26
|
+
|
27
|
+
merge selected_attributes: relations_with_all_attributes,
|
28
|
+
excluded_attributes: relations_with_all_attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply_to(query)
|
32
|
+
query.graph(*flat_relations)
|
33
|
+
.select_graph_attributes(flat_selected_attributes)
|
34
|
+
.exclude_graph_attributes(flat_excluded_attributes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_graph(rows)
|
38
|
+
return if rows.empty?
|
39
|
+
|
40
|
+
graph.roots.each do |node|
|
41
|
+
relation_of(node).fetch_graph rows,
|
42
|
+
db,
|
43
|
+
schema,
|
44
|
+
node[:selected_attributes],
|
45
|
+
node[:excluded_attributes] ,
|
46
|
+
subgraph_of(node)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_joins(dataset, prefix=nil)
|
51
|
+
graph.roots.each do |node|
|
52
|
+
relation = relation_of node
|
53
|
+
dataset = relation.add_join dataset, schema, prefix
|
54
|
+
dataset = subgraph_of(node).add_joins dataset, relation.join_relation_name(prefix)
|
55
|
+
end
|
56
|
+
|
57
|
+
dataset
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :db, :schema, :collection_class, :graph
|
63
|
+
|
64
|
+
def relation_of(node)
|
65
|
+
collection_class.relations.fetch(node[:name])
|
66
|
+
end
|
67
|
+
|
68
|
+
def flat_relations
|
69
|
+
graph.map(&:id)
|
70
|
+
end
|
71
|
+
|
72
|
+
def flat_selected_attributes
|
73
|
+
graph.each_with_object(Hash::Indifferent.new) do |node, hash|
|
74
|
+
hash[node.id] = node[:selected_attributes]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def flat_excluded_attributes
|
79
|
+
graph.each_with_object(Hash::Indifferent.new) do |node, hash|
|
80
|
+
hash[node.id] = node[:excluded_attributes]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def subgraph_of(node)
|
85
|
+
relations = []
|
86
|
+
selected = Hash::Indifferent.new
|
87
|
+
excluded = Hash::Indifferent.new
|
88
|
+
|
89
|
+
node.descendants.each do |descendant|
|
90
|
+
id = descendant.id[node[:name].length+1..-1]
|
91
|
+
relations << id
|
92
|
+
selected[id] = descendant[:selected_attributes]
|
93
|
+
excluded[id] = descendant[:excluded_attributes]
|
94
|
+
end
|
95
|
+
|
96
|
+
Graph.new db,
|
97
|
+
schema,
|
98
|
+
relation_of(node).target_collection_class,
|
99
|
+
relations,
|
100
|
+
selected,
|
101
|
+
excluded
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_graph(relations, selected_attributes, excluded_attributes)
|
105
|
+
HierarchicalGraph.new.tap do |graph|
|
106
|
+
flatten(relations).each do |relation|
|
107
|
+
sections = relation.split('.')
|
108
|
+
|
109
|
+
graph.add_node relation, name: sections.last.to_sym,
|
110
|
+
selected_attributes: selected_attributes[relation],
|
111
|
+
excluded_attributes: excluded_attributes[relation]
|
112
|
+
|
113
|
+
if sections.count > 1
|
114
|
+
parent_id = sections[0..-2].join('.')
|
115
|
+
graph.add_relation parent_id: parent_id,
|
116
|
+
child_id: relation
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def flatten(relations)
|
123
|
+
relations.flat_map do |relation|
|
124
|
+
parents = []
|
125
|
+
relation.to_s.split('.').map do |section|
|
126
|
+
parents << section
|
127
|
+
parents.compact.join('.')
|
128
|
+
end
|
129
|
+
end.uniq.sort
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -19,7 +19,7 @@ module Rasti
|
|
19
19
|
schema.nil? ? Sequel[relation_collection_name] : Sequel[schema][relation_collection_name]
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def fetch_graph(rows, db, schema=nil, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
|
23
23
|
pks = rows.map { |row| row[source_collection_class.primary_key] }
|
24
24
|
|
25
25
|
target_collection = target_collection_class.new db, schema
|
@@ -29,11 +29,11 @@ module Rasti
|
|
29
29
|
join_rows = target_collection.dataset
|
30
30
|
.join(relation_name, target_foreign_key => target_collection_class.primary_key)
|
31
31
|
.where(Sequel[relation_name][source_foreign_key] => pks)
|
32
|
-
.select_all(
|
32
|
+
.select_all(target_collection_class.collection_name)
|
33
33
|
.select_append(Sequel[relation_name][source_foreign_key].as(:source_foreign_key))
|
34
34
|
.all
|
35
35
|
|
36
|
-
|
36
|
+
relations_graph.fetch_graph join_rows if relations_graph
|
37
37
|
|
38
38
|
relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
|
39
39
|
attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
|
@@ -45,7 +45,7 @@ module Rasti
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
48
|
+
def add_join(dataset, schema=nil, prefix=nil)
|
49
49
|
many_to_many_relation_alias = with_prefix prefix, "#{relation_collection_name}_#{SecureRandom.base64}"
|
50
50
|
|
51
51
|
qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
|
@@ -69,7 +69,7 @@ module Rasti
|
|
69
69
|
|
70
70
|
dataset.join(relation_name, source_foreign_key => target_collection_class.primary_key)
|
71
71
|
.where(Sequel[relation_name][target_foreign_key] => primary_keys)
|
72
|
-
.select_all(
|
72
|
+
.select_all(source_collection_class.collection_name)
|
73
73
|
.distinct
|
74
74
|
end
|
75
75
|
|
@@ -7,23 +7,26 @@ module Rasti
|
|
7
7
|
@foreign_key ||= options[:foreign_key] || target_collection_class.foreign_key
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def fetch_graph(rows, db, schema=nil, selected_attributes=nil, excluded_attributes=nil, relations_graph=nil)
|
11
11
|
fks = rows.map { |row| row[foreign_key] }.uniq
|
12
12
|
|
13
13
|
target_collection = target_collection_class.new db, schema
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
29
|
+
def add_join(dataset, schema=nil, prefix=nil)
|
27
30
|
relation_alias = join_relation_name prefix
|
28
31
|
|
29
32
|
qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
|
@@ -7,21 +7,24 @@ module Rasti
|
|
7
7
|
@foreign_key ||= options[:foreign_key] || source_collection_class.foreign_key
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def fetch_graph(rows, db, schema=nil, 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
13
|
target_collection = target_collection_class.new db, schema
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
27
|
+
def add_join(dataset, schema=nil, prefix=nil)
|
25
28
|
relation_alias = join_relation_name prefix
|
26
29
|
|
27
30
|
qualified_relation_source = prefix ? Sequel[prefix] : qualified_source_collection_name(schema)
|
data/lib/rasti/db/version.rb
CHANGED
data/lib/rasti/db.rb
CHANGED
data/rasti-db.gemspec
CHANGED
@@ -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', '~>
|
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
|
data/spec/collection_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe 'Collection' do
|
|
6
6
|
|
7
7
|
it 'Implicit' do
|
8
8
|
Users.collection_name.must_equal :users
|
9
|
-
Users.
|
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
|
@@ -14,7 +14,7 @@ describe 'Collection' do
|
|
14
14
|
|
15
15
|
it 'Explicit' do
|
16
16
|
People.collection_name.must_equal :people
|
17
|
-
People.
|
17
|
+
People.collection_attributes.must_equal [:document_number, :first_name, :last_name, :birth_date, :user_id]
|
18
18
|
People.model.must_equal Person
|
19
19
|
People.primary_key.must_equal :document_number
|
20
20
|
People.foreign_key.must_equal :document_number
|
@@ -244,6 +244,24 @@ describe 'Collection' do
|
|
244
244
|
users.find_graph(user_id, :posts).must_equal User.new id: user_id, name: 'User 1', posts: posts.all
|
245
245
|
end
|
246
246
|
|
247
|
+
it 'Select attributes' do
|
248
|
+
id = db[:users].insert name: 'User 1'
|
249
|
+
|
250
|
+
users.select_attributes(:id).all.must_equal [User.new(id: id)]
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'Exclude attributes' do
|
254
|
+
db[:users].insert name: 'User 1'
|
255
|
+
|
256
|
+
users.exclude_attributes(:id).all.must_equal [User.new(name: 'User 1')]
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'All attributes' do
|
260
|
+
id = db[:users].insert name: 'User 1'
|
261
|
+
|
262
|
+
users.select_attributes(:id).all_attributes.all.must_equal [User.new(id: id, name: 'User 1')]
|
263
|
+
end
|
264
|
+
|
247
265
|
it 'Count' do
|
248
266
|
1.upto(10) { |i| db[:users].insert name: "User #{i}" }
|
249
267
|
|
@@ -430,21 +448,21 @@ describe 'Collection' do
|
|
430
448
|
stubs = Proc.new do |sql|
|
431
449
|
case sql
|
432
450
|
|
433
|
-
when 'SELECT
|
434
|
-
'SELECT
|
451
|
+
when 'SELECT users.* FROM custom_schema.users',
|
452
|
+
'SELECT users.* FROM custom_schema.users WHERE (id IN (2, 1))'
|
435
453
|
[
|
436
454
|
{id: 1},
|
437
455
|
{id: 2}
|
438
456
|
]
|
439
457
|
|
440
|
-
when 'SELECT
|
441
|
-
'SELECT
|
458
|
+
when 'SELECT posts.* FROM custom_schema.posts',
|
459
|
+
'SELECT posts.* FROM custom_schema.posts WHERE (user_id IN (1, 2))'
|
442
460
|
[
|
443
461
|
{id: 3, user_id: 1},
|
444
462
|
{id: 4, user_id: 2}
|
445
463
|
]
|
446
464
|
|
447
|
-
when 'SELECT
|
465
|
+
when 'SELECT comments.* FROM custom_schema.comments WHERE (post_id IN (3, 4))'
|
448
466
|
[
|
449
467
|
{id: 5, user_id: 2, post_id: 3},
|
450
468
|
{id: 6, user_id: 1, post_id: 3},
|
@@ -506,33 +524,34 @@ describe 'Collection' do
|
|
506
524
|
|
507
525
|
it 'Chained query' do
|
508
526
|
stub_users.where(id: [1,2]).limit(1).order(:name).all
|
509
|
-
stub_db.sqls.must_equal ['SELECT
|
527
|
+
stub_db.sqls.must_equal ['SELECT users.* FROM custom_schema.users WHERE (id IN (1, 2)) ORDER BY name LIMIT 1']
|
510
528
|
end
|
511
529
|
|
512
530
|
it 'Graph' do
|
513
531
|
stub_posts.graph(:user, :categories, 'comments.user.posts.categories').all
|
532
|
+
|
514
533
|
stub_db.sqls.must_equal [
|
515
|
-
'SELECT
|
516
|
-
'SELECT
|
517
|
-
'SELECT
|
518
|
-
'SELECT
|
519
|
-
'SELECT
|
520
|
-
'SELECT
|
521
|
-
'SELECT
|
534
|
+
'SELECT posts.* FROM custom_schema.posts',
|
535
|
+
'SELECT 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))',
|
536
|
+
'SELECT comments.* FROM custom_schema.comments WHERE (post_id IN (3, 4))',
|
537
|
+
'SELECT users.* FROM custom_schema.users WHERE (id IN (2, 1))',
|
538
|
+
'SELECT posts.* FROM custom_schema.posts WHERE (user_id IN (1, 2))',
|
539
|
+
'SELECT 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))',
|
540
|
+
'SELECT users.* FROM custom_schema.users WHERE (id IN (1, 2))'
|
522
541
|
]
|
523
542
|
end
|
524
543
|
|
525
544
|
it 'Named query' do
|
526
545
|
stub_posts.commented_by(1).all
|
527
546
|
stub_db.sqls.must_equal [
|
528
|
-
'SELECT DISTINCT
|
547
|
+
'SELECT DISTINCT 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)'
|
529
548
|
]
|
530
549
|
end
|
531
550
|
|
532
551
|
it 'Custom query' do
|
533
552
|
stub_comments.posts_commented_by(2)
|
534
553
|
stub_db.sqls.must_equal [
|
535
|
-
'SELECT
|
554
|
+
'SELECT 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)'
|
536
555
|
]
|
537
556
|
end
|
538
557
|
|
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'
|
@@ -40,7 +41,7 @@ class Posts < Rasti::DB::Collection
|
|
40
41
|
chainable do
|
41
42
|
dataset.join(with_schema(:comments), post_id: :id)
|
42
43
|
.where(with_schema(:comments, :user_id) => user_id)
|
43
|
-
.select_all(
|
44
|
+
.select_all(:posts)
|
44
45
|
.distinct
|
45
46
|
end
|
46
47
|
end
|
@@ -53,7 +54,7 @@ class Comments < Rasti::DB::Collection
|
|
53
54
|
def posts_commented_by(user_id)
|
54
55
|
dataset.where(Sequel[:comments][:user_id] => user_id)
|
55
56
|
.join(with_schema(:posts), id: :post_id)
|
56
|
-
.select_all(
|
57
|
+
.select_all(:posts)
|
57
58
|
.map { |row| Post.new row }
|
58
59
|
end
|
59
60
|
end
|
data/spec/query_spec.rb
CHANGED
@@ -3,18 +3,38 @@ require 'minitest_helper'
|
|
3
3
|
describe 'Query' do
|
4
4
|
|
5
5
|
before do
|
6
|
-
1.upto(10)
|
6
|
+
1.upto(10) do |i|
|
7
|
+
db[:users].insert name: "User #{i}"
|
8
|
+
|
9
|
+
db[:people].insert user_id: i,
|
10
|
+
document_number: i,
|
11
|
+
first_name: "Name #{i}",
|
12
|
+
last_name: "Last Name #{i}",
|
13
|
+
birth_date: Time.now
|
14
|
+
end
|
7
15
|
|
8
16
|
db[:posts].insert user_id: 2, title: 'Sample post', body: '...'
|
9
17
|
db[:posts].insert user_id: 1, title: 'Another post', body: '...'
|
10
18
|
db[:posts].insert user_id: 4, title: 'Best post', body: '...'
|
11
|
-
end
|
12
19
|
|
13
|
-
|
20
|
+
1.upto(3) { |i| db[:categories].insert name: "Category #{i}" }
|
21
|
+
|
22
|
+
db[:comments].insert post_id: 1, user_id: 5, text: 'Comment 1'
|
23
|
+
db[:comments].insert post_id: 1, user_id: 7, text: 'Comment 2'
|
24
|
+
db[:comments].insert post_id: 2, user_id: 2, text: 'Comment 3'
|
25
|
+
|
26
|
+
db[:categories_posts].insert post_id: 1, category_id: 1
|
27
|
+
db[:categories_posts].insert post_id: 1, category_id: 2
|
28
|
+
db[:categories_posts].insert post_id: 2, category_id: 2
|
29
|
+
db[:categories_posts].insert post_id: 2, category_id: 3
|
30
|
+
db[:categories_posts].insert post_id: 3, category_id: 3
|
31
|
+
end
|
14
32
|
|
15
|
-
let(:
|
33
|
+
let(:users_query) { Rasti::DB::Query.new collection_class: Users, dataset: db[:users] }
|
34
|
+
|
35
|
+
let(:posts_query) { Rasti::DB::Query.new collection_class: Posts, dataset: db[:posts] }
|
16
36
|
|
17
|
-
let(:comments_query) { Rasti::DB::Query.new Comments, db[:comments] }
|
37
|
+
let(:comments_query) { Rasti::DB::Query.new collection_class: Comments, dataset: db[:comments] }
|
18
38
|
|
19
39
|
it 'Count' do
|
20
40
|
users_query.count.must_equal 10
|
@@ -37,6 +57,61 @@ describe 'Query' do
|
|
37
57
|
users_query.primary_keys.must_equal db[:users].map { |u| u[:id] }
|
38
58
|
end
|
39
59
|
|
60
|
+
it 'Select attributes' do
|
61
|
+
posts_query.select_attributes(:id, :user_id).all.must_equal db[:posts].select(:id, :user_id).map { |r| Post.new r }
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'Exclude attributes' do
|
65
|
+
posts_query.exclude_attributes(:body).all.must_equal db[:posts].select(:id, :user_id, :title).map { |r| Post.new r }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'All attributes' do
|
69
|
+
posts_query.exclude_attributes(:body).all_attributes.all.must_equal db[:posts].map { |r| Post.new r }
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'Select graph attributes' do
|
73
|
+
person = Person.new db[:people].where(document_number: 2).select(:first_name, :last_name, :user_id).first
|
74
|
+
|
75
|
+
user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
|
76
|
+
|
77
|
+
post = Post.new db[:posts].where(id: 1).first.merge(user: user)
|
78
|
+
|
79
|
+
posts_query.where(id: 1)
|
80
|
+
.graph('user.person')
|
81
|
+
.select_graph_attributes(user: [:id], 'user.person' => [:first_name, :last_name, :user_id])
|
82
|
+
.all
|
83
|
+
.must_equal [post]
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'Exclude graph attributes' do
|
87
|
+
person = Person.new db[:people].where(document_number: 2).select(:document_number, :last_name, :user_id).first
|
88
|
+
|
89
|
+
user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
|
90
|
+
|
91
|
+
post = Post.new db[:posts].where(id: 1).first.merge(user: user)
|
92
|
+
|
93
|
+
posts_query.where(id: 1)
|
94
|
+
.graph('user.person')
|
95
|
+
.exclude_graph_attributes(user: [:name], 'user.person' => [:first_name, :birth_date])
|
96
|
+
.all
|
97
|
+
.must_equal [post]
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'All graph attributes' do
|
101
|
+
person = Person.new db[:people].where(document_number: 2).first
|
102
|
+
|
103
|
+
user = User.new db[:users].where(id: 2).select(:id).first.merge(person: person)
|
104
|
+
|
105
|
+
post = Post.new db[:posts].where(id: 1).first.merge(user: user)
|
106
|
+
|
107
|
+
posts_query.where(id: 1)
|
108
|
+
.graph('user.person')
|
109
|
+
.exclude_graph_attributes(user: [:name], 'user.person' => [:birth_date, :first_name, :last_name])
|
110
|
+
.all_graph_attributes('user.person')
|
111
|
+
.all
|
112
|
+
.must_equal [post]
|
113
|
+
end
|
114
|
+
|
40
115
|
it 'Map' do
|
41
116
|
users_query.map(&:name).must_equal db[:users].map(:name)
|
42
117
|
end
|
@@ -96,12 +171,14 @@ describe 'Query' do
|
|
96
171
|
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: '...')])
|
97
172
|
end
|
98
173
|
|
99
|
-
it '
|
174
|
+
it 'Any?' do
|
100
175
|
users_query.empty?.must_equal false
|
101
176
|
users_query.any?.must_equal true
|
102
177
|
end
|
103
178
|
|
104
|
-
it '
|
179
|
+
it 'Empty?' do
|
180
|
+
db[:comments].truncate
|
181
|
+
|
105
182
|
comments_query.empty?.must_equal true
|
106
183
|
comments_query.any?.must_equal false
|
107
184
|
end
|
@@ -126,28 +203,6 @@ describe 'Query' do
|
|
126
203
|
|
127
204
|
describe 'Join' do
|
128
205
|
|
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
206
|
it 'One to Many' do
|
152
207
|
users_query.join(:posts).where(title: 'Sample post').all.must_equal [User.new(id: 2, name: 'User 2')]
|
153
208
|
end
|
@@ -179,55 +234,37 @@ describe 'Query' do
|
|
179
234
|
|
180
235
|
describe 'NQL' do
|
181
236
|
|
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
|
189
|
-
end
|
190
|
-
|
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'
|
196
|
-
|
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
|
202
|
-
end
|
203
|
-
|
204
237
|
it 'Invalid expression' do
|
205
238
|
error = proc { posts_query.nql('a + b') }.must_raise Rasti::DB::NQL::InvalidExpressionError
|
206
239
|
error.message.must_equal 'Invalid filter expression: a + b'
|
207
240
|
end
|
208
241
|
|
209
242
|
it 'Filter to self table' do
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
.sort
|
215
|
-
.must_equal [8, 9, 10]
|
243
|
+
posts_query.nql('user_id > 1')
|
244
|
+
.pluck(:user_id)
|
245
|
+
.sort
|
246
|
+
.must_equal [2, 4]
|
216
247
|
end
|
217
248
|
|
218
249
|
it 'Filter to join table' do
|
219
250
|
posts_query.nql('categories.name = Category 2')
|
220
|
-
.
|
251
|
+
.pluck(:id)
|
221
252
|
.sort
|
222
253
|
.must_equal [1, 2]
|
223
254
|
end
|
224
255
|
|
225
256
|
it 'Filter to 2nd order relation' do
|
226
257
|
posts_query.nql('comments.user.person.document_number = 7')
|
227
|
-
.
|
258
|
+
.pluck(:id)
|
228
259
|
.must_equal [1]
|
229
260
|
end
|
230
261
|
|
262
|
+
it 'Filter combined' do
|
263
|
+
posts_query.nql('(categories.id = 1 | categories.id = 3) & comments.user.person.document_number = 2')
|
264
|
+
.pluck(:id)
|
265
|
+
.must_equal [2]
|
266
|
+
end
|
267
|
+
|
231
268
|
end
|
232
269
|
|
233
270
|
end
|
data/spec/relations_spec.rb
CHANGED
@@ -37,7 +37,7 @@ describe 'Relations' do
|
|
37
37
|
1.upto(2) { |i| db[:posts].insert user_id: user_id, title: "Post #{i}", body: '...' }
|
38
38
|
rows = db[:users].all
|
39
39
|
|
40
|
-
Users.relations[:posts].
|
40
|
+
Users.relations[:posts].fetch_graph rows, db
|
41
41
|
|
42
42
|
rows[0][:posts].must_equal posts.where(user_id: user_id).all
|
43
43
|
end
|
@@ -79,7 +79,7 @@ describe 'Relations' do
|
|
79
79
|
db[:posts].insert user_id: user_id, title: 'Post 1', body: '...'
|
80
80
|
rows = db[:posts].all
|
81
81
|
|
82
|
-
Posts.relations[:user].
|
82
|
+
Posts.relations[:user].fetch_graph rows, db
|
83
83
|
|
84
84
|
rows[0][:user].must_equal users.first
|
85
85
|
end
|
@@ -136,7 +136,7 @@ describe 'Relations' do
|
|
136
136
|
|
137
137
|
rows = db[:posts].all
|
138
138
|
|
139
|
-
Posts.relations[:categories].
|
139
|
+
Posts.relations[:categories].fetch_graph rows, db
|
140
140
|
|
141
141
|
rows[0][:categories].must_equal categories.where(id: [1,2]).all
|
142
142
|
rows[1][:categories].must_equal categories.where(id: [3,4]).all
|
@@ -186,7 +186,7 @@ describe 'Relations' do
|
|
186
186
|
|
187
187
|
rows = db[:users].all
|
188
188
|
|
189
|
-
Users.relations[:person].
|
189
|
+
Users.relations[:person].fetch_graph rows, db
|
190
190
|
|
191
191
|
2.times do |i|
|
192
192
|
rows[i][:person].must_equal people.find("document_#{i}")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rasti-db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Naiman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -112,20 +112,48 @@ dependencies:
|
|
112
112
|
- - "~>"
|
113
113
|
- !ruby/object:Gem::Version
|
114
114
|
version: '1.0'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: hierarchical_graph
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.0'
|
122
|
+
type: :runtime
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - "~>"
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '1.0'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: hash_ext
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0.5'
|
136
|
+
type: :runtime
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0.5'
|
115
143
|
- !ruby/object:Gem::Dependency
|
116
144
|
name: rake
|
117
145
|
requirement: !ruby/object:Gem::Requirement
|
118
146
|
requirements:
|
119
147
|
- - "~>"
|
120
148
|
- !ruby/object:Gem::Version
|
121
|
-
version: '
|
149
|
+
version: '12.3'
|
122
150
|
type: :development
|
123
151
|
prerelease: false
|
124
152
|
version_requirements: !ruby/object:Gem::Requirement
|
125
153
|
requirements:
|
126
154
|
- - "~>"
|
127
155
|
- !ruby/object:Gem::Version
|
128
|
-
version: '
|
156
|
+
version: '12.3'
|
129
157
|
- !ruby/object:Gem::Dependency
|
130
158
|
name: minitest
|
131
159
|
requirement: !ruby/object:Gem::Requirement
|
@@ -279,7 +307,7 @@ files:
|
|
279
307
|
- lib/rasti/db/nql/syntax.treetop
|
280
308
|
- lib/rasti/db/query.rb
|
281
309
|
- lib/rasti/db/relations/base.rb
|
282
|
-
- lib/rasti/db/relations/
|
310
|
+
- lib/rasti/db/relations/graph.rb
|
283
311
|
- lib/rasti/db/relations/many_to_many.rb
|
284
312
|
- lib/rasti/db/relations/many_to_one.rb
|
285
313
|
- lib/rasti/db/relations/one_to_many.rb
|
@@ -1,60 +0,0 @@
|
|
1
|
-
module Rasti
|
2
|
-
module DB
|
3
|
-
module Relations
|
4
|
-
class GraphBuilder
|
5
|
-
class << self
|
6
|
-
|
7
|
-
def graph_to(rows, relations, collection_class, db, schema=nil)
|
8
|
-
return if rows.empty?
|
9
|
-
|
10
|
-
parse(relations).each do |relation_name, nested_relations|
|
11
|
-
relation = get_relation collection_class, relation_name
|
12
|
-
relation.graph_to rows, db, schema, nested_relations
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def joins_to(dataset, relations, collection_class, schema=nil)
|
17
|
-
ds = recursive_joins dataset, recursive_parse(relations), collection_class, schema
|
18
|
-
qualified_collection_name = schema ? Sequel[schema][collection_class.collection_name] : Sequel[collection_class.collection_name]
|
19
|
-
ds.distinct.select_all(qualified_collection_name)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def get_relation(collection_class, relation_name)
|
25
|
-
raise "Undefined relation #{relation_name} for #{collection_class}" unless collection_class.relations.key? relation_name
|
26
|
-
collection_class.relations[relation_name]
|
27
|
-
end
|
28
|
-
|
29
|
-
def parse(relations)
|
30
|
-
relations.each_with_object({}) do |relation, hash|
|
31
|
-
tail = relation.to_s.split '.'
|
32
|
-
head = tail.shift.to_sym
|
33
|
-
hash[head] ||= []
|
34
|
-
hash[head] << tail.join('.') unless tail.empty?
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def recursive_parse(relations)
|
39
|
-
parse(relations).each_with_object({}) do |(key, value), hash|
|
40
|
-
hash[key] = recursive_parse value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def recursive_joins(dataset, joins, collection_class, schema, prefix=nil)
|
45
|
-
joins.each do |relation_name, nested_joins|
|
46
|
-
relation = get_relation collection_class, relation_name
|
47
|
-
|
48
|
-
dataset = relation.join_to dataset, schema, prefix
|
49
|
-
|
50
|
-
dataset = recursive_joins dataset, nested_joins, relation.target_collection_class, schema, relation.join_relation_name(prefix) unless nested_joins.empty?
|
51
|
-
end
|
52
|
-
|
53
|
-
dataset
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|