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
@@ -42,44 +42,44 @@ module Rasti
|
|
42
42
|
comparison_equal
|
43
43
|
end
|
44
44
|
|
45
|
-
rule
|
46
|
-
_tables:(table:
|
45
|
+
rule attribute
|
46
|
+
_tables:(table:attribute_name '.')* _column:attribute_name <Nodes::Attribute>
|
47
47
|
end
|
48
48
|
|
49
49
|
rule comparison_include
|
50
|
-
|
50
|
+
attribute:attribute space* comparator:':' space* argument:basic <Nodes::Comparisons::Include>
|
51
51
|
end
|
52
52
|
|
53
53
|
rule comparison_not_include
|
54
|
-
|
54
|
+
attribute:attribute space* comparator:'!:' space* argument:basic <Nodes::Comparisons::NotInclude>
|
55
55
|
end
|
56
56
|
|
57
57
|
rule comparison_like
|
58
|
-
|
58
|
+
attribute:attribute space* comparator:'~' space* argument:basic <Nodes::Comparisons::Like>
|
59
59
|
end
|
60
60
|
|
61
61
|
rule comparison_greater_than
|
62
|
-
|
62
|
+
attribute:attribute space* comparator:'>' space* argument:basic <Nodes::Comparisons::GreaterThan>
|
63
63
|
end
|
64
64
|
|
65
65
|
rule comparison_greater_than_or_equal
|
66
|
-
|
66
|
+
attribute:attribute space* comparator:'>=' space* argument:basic <Nodes::Comparisons::GreaterThanOrEqual>
|
67
67
|
end
|
68
68
|
|
69
69
|
rule comparison_less_than
|
70
|
-
|
70
|
+
attribute:attribute space* comparator:'<' space* argument:basic <Nodes::Comparisons::LessThan>
|
71
71
|
end
|
72
72
|
|
73
73
|
rule comparison_less_than_or_equal
|
74
|
-
|
74
|
+
attribute:attribute space* comparator:'<=' space* argument:basic <Nodes::Comparisons::LessThanOrEqual>
|
75
75
|
end
|
76
76
|
|
77
77
|
rule comparison_not_equal
|
78
|
-
|
78
|
+
attribute:attribute space* comparator:'!=' space* argument:basic <Nodes::Comparisons::NotEqual>
|
79
79
|
end
|
80
80
|
|
81
81
|
rule comparison_equal
|
82
|
-
|
82
|
+
attribute:attribute space* comparator:'=' space* argument:basic <Nodes::Comparisons::Equal>
|
83
83
|
end
|
84
84
|
|
85
85
|
rule basic
|
@@ -95,7 +95,7 @@ module Rasti
|
|
95
95
|
[\s\t\n]
|
96
96
|
end
|
97
97
|
|
98
|
-
rule
|
98
|
+
rule attribute_name
|
99
99
|
[a-z_]+
|
100
100
|
end
|
101
101
|
|
data/lib/rasti/db/query.rb
CHANGED
@@ -5,13 +5,18 @@ module Rasti
|
|
5
5
|
DATASET_CHAINED_METHODS = [:where, :exclude, :or, :order, :reverse_order, :limit, :offset].freeze
|
6
6
|
|
7
7
|
include Enumerable
|
8
|
-
include Helpers::WithSchema
|
9
8
|
|
10
|
-
def initialize(collection_class
|
9
|
+
def initialize(environment:, collection_class:, dataset:, relations_graph:nil)
|
10
|
+
@environment = environment
|
11
11
|
@collection_class = collection_class
|
12
|
-
@dataset = dataset
|
13
|
-
@
|
14
|
-
|
12
|
+
@dataset = dataset.qualify collection_class.collection_name
|
13
|
+
@relations_graph = relations_graph || Relations::Graph.new(environment, collection_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
DATASET_CHAINED_METHODS.each do |method|
|
17
|
+
define_method method do |*args, &block|
|
18
|
+
build_query dataset: dataset.public_send(method, *args, &block)
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
22
|
def raw
|
@@ -19,7 +24,7 @@ module Rasti
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def pluck(*attributes)
|
22
|
-
ds = dataset.select(*attributes.map { |
|
27
|
+
ds = dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
|
23
28
|
attributes.count == 1 ? ds.map { |r| r[attributes.first] } : ds.map(&:values)
|
24
29
|
end
|
25
30
|
|
@@ -27,38 +32,73 @@ module Rasti
|
|
27
32
|
pluck collection_class.primary_key
|
28
33
|
end
|
29
34
|
|
35
|
+
def select_attributes(*attributes)
|
36
|
+
build_query dataset: dataset.select(*attributes.map { |a| Sequel[collection_class.collection_name][a] })
|
37
|
+
end
|
38
|
+
|
39
|
+
def exclude_attributes(*excluded_attributes)
|
40
|
+
attributes = collection_class.collection_attributes - excluded_attributes
|
41
|
+
select_attributes(*attributes)
|
42
|
+
end
|
43
|
+
|
44
|
+
def all_attributes
|
45
|
+
build_query dataset: dataset.select_all(collection_class.collection_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_graph_attributes(selected_attributes)
|
49
|
+
build_query relations_graph: relations_graph.merge(selected_attributes: selected_attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
def exclude_graph_attributes(excluded_attributes)
|
53
|
+
build_query relations_graph: relations_graph.merge(excluded_attributes: excluded_attributes)
|
54
|
+
end
|
55
|
+
|
56
|
+
def all_graph_attributes(*relations)
|
57
|
+
build_query relations_graph: relations_graph.with_all_attributes_for(relations)
|
58
|
+
end
|
59
|
+
|
60
|
+
def select_computed_attributes(*computed_attributes)
|
61
|
+
ds = computed_attributes.inject(dataset) do |ds, name|
|
62
|
+
computed_attribute = collection_class.computed_attributes[name]
|
63
|
+
computed_attribute.apply_join(ds).select_append(computed_attribute.identifier.as(name))
|
64
|
+
end
|
65
|
+
build_query dataset: ds
|
66
|
+
end
|
67
|
+
|
30
68
|
def all
|
31
|
-
with_graph(dataset.all).map do |row|
|
69
|
+
with_graph(dataset.all).map do |row|
|
32
70
|
collection_class.model.new row
|
33
71
|
end
|
34
72
|
end
|
35
73
|
alias_method :to_a, :all
|
36
74
|
|
37
|
-
def each(&block)
|
38
|
-
|
75
|
+
def each(batch_size:nil, &block)
|
76
|
+
if batch_size.nil?
|
77
|
+
all.each(&block)
|
78
|
+
else
|
79
|
+
each_batch(size: batch_size) do |models|
|
80
|
+
models.each { |model| block.call model }
|
81
|
+
end
|
82
|
+
end
|
39
83
|
end
|
40
84
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
relations,
|
46
|
-
schema
|
85
|
+
def each_batch(size:, &block)
|
86
|
+
primary_keys.each_slice(size) do |pks|
|
87
|
+
query = where(collection_class.primary_key => pks)
|
88
|
+
block.call query.all
|
47
89
|
end
|
48
90
|
end
|
49
91
|
|
50
|
-
def graph(*
|
51
|
-
|
52
|
-
dataset,
|
53
|
-
(relations | rels),
|
54
|
-
schema
|
92
|
+
def graph(*relations)
|
93
|
+
build_query relations_graph: relations_graph.merge(relations: relations)
|
55
94
|
end
|
56
95
|
|
57
|
-
def join(*
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
96
|
+
def join(*relations)
|
97
|
+
graph = Relations::Graph.new environment, collection_class, relations
|
98
|
+
|
99
|
+
ds = graph.add_joins(dataset).distinct
|
100
|
+
|
101
|
+
build_query dataset: ds
|
62
102
|
end
|
63
103
|
|
64
104
|
def count
|
@@ -75,12 +115,12 @@ module Rasti
|
|
75
115
|
|
76
116
|
def first
|
77
117
|
row = dataset.first
|
78
|
-
row ?
|
118
|
+
row ? build_model(row) : nil
|
79
119
|
end
|
80
120
|
|
81
121
|
def last
|
82
122
|
row = dataset.last
|
83
|
-
row ?
|
123
|
+
row ? build_model(row) : nil
|
84
124
|
end
|
85
125
|
|
86
126
|
def detect(*args, &block)
|
@@ -97,33 +137,63 @@ module Rasti
|
|
97
137
|
|
98
138
|
raise NQL::InvalidExpressionError.new(filter_expression) if sentence.nil?
|
99
139
|
|
140
|
+
ds = sentence.computed_attributes(collection_class).inject(dataset) do |ds, name|
|
141
|
+
collection_class.computed_attributes[name].apply_join ds
|
142
|
+
end
|
143
|
+
query = build_query dataset: ds
|
144
|
+
|
100
145
|
dependency_tables = sentence.dependency_tables
|
101
|
-
query =
|
102
|
-
|
103
|
-
query.where sentence.filter_condition
|
146
|
+
query = query.join(*dependency_tables) unless dependency_tables.empty?
|
147
|
+
|
148
|
+
query.where sentence.filter_condition(collection_class)
|
104
149
|
end
|
105
150
|
|
106
151
|
private
|
107
152
|
|
153
|
+
attr_reader :environment, :collection_class, :dataset, :relations_graph
|
154
|
+
|
155
|
+
def build_query(**args)
|
156
|
+
current_args = {
|
157
|
+
environment: environment,
|
158
|
+
collection_class: collection_class,
|
159
|
+
dataset: dataset,
|
160
|
+
relations_graph: relations_graph
|
161
|
+
}
|
162
|
+
|
163
|
+
Query.new(**current_args.merge(args))
|
164
|
+
end
|
165
|
+
|
166
|
+
def build_model(row)
|
167
|
+
collection_class.model.new with_graph(row)
|
168
|
+
end
|
169
|
+
|
108
170
|
def chainable(&block)
|
109
|
-
|
110
|
-
Query.new collection_class, ds, relations, schema
|
171
|
+
build_query dataset: instance_eval(&block)
|
111
172
|
end
|
112
173
|
|
113
174
|
def with_related(relation_name, primary_keys)
|
114
|
-
ds = collection_class.relations[relation_name].apply_filter
|
115
|
-
|
175
|
+
ds = collection_class.relations[relation_name].apply_filter environment, dataset, primary_keys
|
176
|
+
build_query dataset: ds
|
116
177
|
end
|
117
178
|
|
118
179
|
def with_graph(data)
|
119
180
|
rows = data.is_a?(Array) ? data : [data]
|
120
|
-
|
181
|
+
relations_graph.fetch_graph rows
|
121
182
|
data
|
122
183
|
end
|
123
184
|
|
185
|
+
def qualify(collection_name, data_source_name: nil)
|
186
|
+
data_source_name ||= collection_class.data_source_name
|
187
|
+
environment.qualify data_source_name, collection_name
|
188
|
+
end
|
189
|
+
|
190
|
+
def nql_parser
|
191
|
+
NQL::SyntaxParser.new
|
192
|
+
end
|
193
|
+
|
124
194
|
def method_missing(method, *args, &block)
|
125
|
-
if collection_class.queries.key?
|
126
|
-
instance_exec(*args, &collection_class.queries
|
195
|
+
if collection_class.queries.key? method
|
196
|
+
instance_exec(*args, &collection_class.queries.fetch(method))
|
127
197
|
else
|
128
198
|
super
|
129
199
|
end
|
@@ -133,12 +203,6 @@ module Rasti
|
|
133
203
|
collection_class.queries.key?(method) || super
|
134
204
|
end
|
135
205
|
|
136
|
-
def nql_parser
|
137
|
-
NQL::SyntaxParser.new
|
138
|
-
end
|
139
|
-
|
140
|
-
attr_reader :collection_class, :dataset, :relations, :schema
|
141
|
-
|
142
206
|
end
|
143
207
|
end
|
144
208
|
end
|
@@ -33,25 +33,39 @@ module Rasti
|
|
33
33
|
self.class == OneToOne
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
36
|
+
def from_one?
|
37
|
+
one_to_one? || one_to_many?
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
def from_many?
|
41
|
+
many_to_one? || many_to_many?
|
42
|
+
end
|
41
43
|
|
42
|
-
|
44
|
+
def to_one?
|
45
|
+
one_to_one? || many_to_one?
|
46
|
+
end
|
43
47
|
|
44
|
-
def
|
45
|
-
|
48
|
+
def to_many?
|
49
|
+
one_to_many? || many_to_many?
|
46
50
|
end
|
47
51
|
|
48
|
-
def
|
49
|
-
|
52
|
+
def join_relation_name(prefix)
|
53
|
+
with_prefix prefix, name
|
50
54
|
end
|
51
55
|
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :options
|
59
|
+
|
52
60
|
def with_prefix(prefix, name)
|
53
61
|
[prefix, name].compact.join('__').to_sym
|
54
62
|
end
|
63
|
+
|
64
|
+
def validate_join!
|
65
|
+
if source_collection_class.data_source_name != target_collection_class.data_source_name
|
66
|
+
raise "Invalid join of multiple data sources: #{source_collection_class.data_source_name}.#{source_collection_class.collection_name} > #{target_collection_class.data_source_name}.#{target_collection_class.collection_name}"
|
67
|
+
end
|
68
|
+
end
|
55
69
|
|
56
70
|
end
|
57
71
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module Relations
|
4
|
+
class Graph
|
5
|
+
|
6
|
+
def initialize(environment, collection_class, relations=[], selected_attributes={}, excluded_attributes={})
|
7
|
+
@environment = environment
|
8
|
+
@collection_class = collection_class
|
9
|
+
@graph = build_graph relations,
|
10
|
+
Hash::Indifferent.new(selected_attributes),
|
11
|
+
Hash::Indifferent.new(excluded_attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge(relations:[], selected_attributes:{}, excluded_attributes:{})
|
15
|
+
Graph.new environment,
|
16
|
+
collection_class,
|
17
|
+
(flat_relations | relations),
|
18
|
+
flat_selected_attributes.merge(selected_attributes),
|
19
|
+
flat_excluded_attributes.merge(excluded_attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_all_attributes_for(relations)
|
23
|
+
relations_with_all_attributes = relations.map { |r| [r, nil] }.to_h
|
24
|
+
|
25
|
+
merge selected_attributes: relations_with_all_attributes,
|
26
|
+
excluded_attributes: relations_with_all_attributes
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply_to(query)
|
30
|
+
query.graph(*flat_relations)
|
31
|
+
.select_graph_attributes(flat_selected_attributes)
|
32
|
+
.exclude_graph_attributes(flat_excluded_attributes)
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch_graph(rows)
|
36
|
+
return if rows.empty?
|
37
|
+
|
38
|
+
graph.roots.each do |node|
|
39
|
+
relation_of(node).fetch_graph environment,
|
40
|
+
rows,
|
41
|
+
node[:selected_attributes],
|
42
|
+
node[:excluded_attributes] ,
|
43
|
+
subgraph_of(node)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_joins(dataset, prefix=nil)
|
48
|
+
graph.roots.inject(dataset) do |ds, node|
|
49
|
+
relation = relation_of node
|
50
|
+
dataset_with_relation = relation.add_join environment, ds, prefix
|
51
|
+
subgraph_of(node).add_joins dataset_with_relation, relation.join_relation_name(prefix)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :environment, :collection_class, :graph
|
58
|
+
|
59
|
+
def relation_of(node)
|
60
|
+
collection_class.relations.fetch(node[:name])
|
61
|
+
end
|
62
|
+
|
63
|
+
def flat_relations
|
64
|
+
graph.map(&:id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def flat_selected_attributes
|
68
|
+
graph.each_with_object(Hash::Indifferent.new) do |node, hash|
|
69
|
+
hash[node.id] = node[:selected_attributes]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def flat_excluded_attributes
|
74
|
+
graph.each_with_object(Hash::Indifferent.new) do |node, hash|
|
75
|
+
hash[node.id] = node[:excluded_attributes]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def subgraph_of(node)
|
80
|
+
relations = []
|
81
|
+
selected = Hash::Indifferent.new
|
82
|
+
excluded = Hash::Indifferent.new
|
83
|
+
|
84
|
+
node.descendants.each do |descendant|
|
85
|
+
id = descendant.id[node[:name].length+1..-1]
|
86
|
+
relations << id
|
87
|
+
selected[id] = descendant[:selected_attributes]
|
88
|
+
excluded[id] = descendant[:excluded_attributes]
|
89
|
+
end
|
90
|
+
|
91
|
+
Graph.new environment,
|
92
|
+
relation_of(node).target_collection_class,
|
93
|
+
relations,
|
94
|
+
selected,
|
95
|
+
excluded
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_graph(relations, selected_attributes, excluded_attributes)
|
99
|
+
HierarchicalGraph.new.tap do |graph|
|
100
|
+
flatten(relations).each do |relation|
|
101
|
+
sections = relation.split('.')
|
102
|
+
|
103
|
+
graph.add_node relation, name: sections.last.to_sym,
|
104
|
+
selected_attributes: selected_attributes[relation],
|
105
|
+
excluded_attributes: excluded_attributes[relation]
|
106
|
+
|
107
|
+
if sections.count > 1
|
108
|
+
parent_id = sections[0..-2].join('.')
|
109
|
+
graph.add_relation parent_id: parent_id,
|
110
|
+
child_id: relation
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def flatten(relations)
|
117
|
+
relations.flat_map do |relation|
|
118
|
+
parents = []
|
119
|
+
relation.to_s.split('.').map do |section|
|
120
|
+
parents << section
|
121
|
+
parents.compact.join('.')
|
122
|
+
end
|
123
|
+
end.uniq.sort
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|