rasti-db 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/gabynaiman/rasti-db.svg?branch=master)](https://travis-ci.org/gabynaiman/rasti-db)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/github/gabynaiman/rasti-db/badge.svg?branch=master)](https://coveralls.io/github/gabynaiman/rasti-db?branch=master)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-db.svg)](https://codeclimate.com/github/gabynaiman/rasti-db)
|
7
|
-
[![Dependency Status](https://gemnasium.com/gabynaiman/rasti-db.svg)](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
|