axiom-do-adapter 0.1.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 +7 -0
- data/.document +5 -0
- data/.gemtest +0 -0
- data/.gitignore +37 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +35 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +10 -0
- data/Gemfile.devtools +57 -0
- data/Guardfile +23 -0
- data/LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +5 -0
- data/TODO +0 -0
- data/axiom-do-adapter.gemspec +27 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +115 -0
- data/config/yardstick.yml +2 -0
- data/lib/axiom-do-adapter.rb +4 -0
- data/lib/axiom/adapter/data_objects.rb +55 -0
- data/lib/axiom/adapter/data_objects/statement.rb +109 -0
- data/lib/axiom/adapter/data_objects/version.rb +12 -0
- data/lib/axiom/relation/gateway.rb +374 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/binary_relation_method_behaviour.rb +51 -0
- data/spec/shared/unary_relation_method_behaviour.rb +21 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/config_alias.rb +3 -0
- data/spec/support/example_group_methods.rb +7 -0
- data/spec/support/ice_nine_config.rb +6 -0
- data/spec/unit/axiom/adapter/data_objects/class_methods/new_spec.rb +15 -0
- data/spec/unit/axiom/adapter/data_objects/read_spec.rb +66 -0
- data/spec/unit/axiom/adapter/data_objects/statement/class_methods/new_spec.rb +28 -0
- data/spec/unit/axiom/adapter/data_objects/statement/each_spec.rb +63 -0
- data/spec/unit/axiom/adapter/data_objects/statement/to_s_spec.rb +53 -0
- data/spec/unit/axiom/relation/gateway/class_methods/new_spec.rb +16 -0
- data/spec/unit/axiom/relation/gateway/difference_spec.rb +17 -0
- data/spec/unit/axiom/relation/gateway/drop_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/each_spec.rb +86 -0
- data/spec/unit/axiom/relation/gateway/extend_spec.rb +29 -0
- data/spec/unit/axiom/relation/gateway/intersect_spec.rb +17 -0
- data/spec/unit/axiom/relation/gateway/join_spec.rb +44 -0
- data/spec/unit/axiom/relation/gateway/materialize_spec.rb +27 -0
- data/spec/unit/axiom/relation/gateway/optimize_spec.rb +23 -0
- data/spec/unit/axiom/relation/gateway/product_spec.rb +17 -0
- data/spec/unit/axiom/relation/gateway/project_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/remove_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/rename_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/respond_to_spec.rb +29 -0
- data/spec/unit/axiom/relation/gateway/restrict_spec.rb +29 -0
- data/spec/unit/axiom/relation/gateway/reverse_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/sort_by_spec.rb +29 -0
- data/spec/unit/axiom/relation/gateway/summarize_spec.rb +154 -0
- data/spec/unit/axiom/relation/gateway/take_spec.rb +21 -0
- data/spec/unit/axiom/relation/gateway/union_spec.rb +17 -0
- metadata +214 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Axiom
|
4
|
+
module Adapter
|
5
|
+
class DataObjects
|
6
|
+
|
7
|
+
# Executes generated SQL statements
|
8
|
+
class Statement
|
9
|
+
include Enumerable, Adamantium
|
10
|
+
|
11
|
+
# Initialize a statement
|
12
|
+
#
|
13
|
+
# @param [::DataObjects::Connection] connection
|
14
|
+
# the database connection
|
15
|
+
# @param [Relation] relation
|
16
|
+
# the relation to generate the SQL from
|
17
|
+
# @param [#visit] visitor
|
18
|
+
# optional object to visit the relation and generate SQL with
|
19
|
+
#
|
20
|
+
# @return [undefined]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def initialize(connection, relation, visitor = SQL::Generator::Relation)
|
24
|
+
@connection = connection
|
25
|
+
@relation = relation
|
26
|
+
@visitor = visitor
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
# Iterate over each row in the results
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# statement = Statement.new(connection, relation, visitor)
|
34
|
+
# statement.each { |row| ... }
|
35
|
+
#
|
36
|
+
# @yield [row]
|
37
|
+
#
|
38
|
+
# @yieldparam [Array] row
|
39
|
+
# each row in the results
|
40
|
+
#
|
41
|
+
# @return [self]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def each
|
45
|
+
return to_enum unless block_given?
|
46
|
+
each_row { |row| yield row }
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return the SQL query
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# statement.to_s # => SQL representation of the relation
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def to_s
|
59
|
+
@visitor.visit(@relation).to_sql.freeze
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Yield each row in the result
|
65
|
+
#
|
66
|
+
# @yield [row]
|
67
|
+
#
|
68
|
+
# @yieldparam [Array] row
|
69
|
+
# each row in the results
|
70
|
+
#
|
71
|
+
# @return [undefined]
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
def each_row
|
75
|
+
reader = command.execute_reader
|
76
|
+
while reader.next!
|
77
|
+
yield reader.values
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
reader.close if reader
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return the command for the SQL query and column types
|
84
|
+
#
|
85
|
+
# @return [::DataObjects::Command]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
def command
|
89
|
+
command = @connection.create_command(to_s)
|
90
|
+
command.set_types(column_types)
|
91
|
+
command
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return the list of types for each column
|
95
|
+
#
|
96
|
+
# @return [Array<Class>]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
def column_types
|
100
|
+
@relation.header.map { |attribute| attribute.class.primitive }
|
101
|
+
end
|
102
|
+
|
103
|
+
memoize :to_s
|
104
|
+
memoize :command, :freezer => :flat
|
105
|
+
|
106
|
+
end # class Statement
|
107
|
+
end # class DataObjects
|
108
|
+
end # module Adapter
|
109
|
+
end # module Axiom
|
@@ -0,0 +1,374 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Axiom
|
4
|
+
class Relation
|
5
|
+
|
6
|
+
# A relation backed by an adapter
|
7
|
+
class Gateway < Relation
|
8
|
+
|
9
|
+
DECORATED_CLASS = superclass
|
10
|
+
|
11
|
+
# remove methods so they can be proxied
|
12
|
+
undef_method *DECORATED_CLASS.public_instance_methods(false).map(&:to_s) - %w[ materialize ]
|
13
|
+
undef_method :project, :remove, :extend, :rename, :restrict, :sort_by, :reverse, :drop, :take
|
14
|
+
|
15
|
+
# The adapter the gateway will use to fetch results
|
16
|
+
#
|
17
|
+
# @return [Adapter::DataObjects]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
attr_reader :adapter
|
21
|
+
protected :adapter
|
22
|
+
|
23
|
+
# The relation the gateway will use to generate SQL
|
24
|
+
#
|
25
|
+
# @return [Relation]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
attr_reader :relation
|
29
|
+
protected :relation
|
30
|
+
|
31
|
+
# Initialize a Gateway
|
32
|
+
#
|
33
|
+
# @param [Adapter::DataObjects] adapter
|
34
|
+
#
|
35
|
+
# @param [Relation] relation
|
36
|
+
#
|
37
|
+
# @return [undefined]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def initialize(adapter, relation)
|
41
|
+
@adapter = adapter
|
42
|
+
@relation = relation
|
43
|
+
end
|
44
|
+
|
45
|
+
# Iterate over each row in the results
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# gateway = Gateway.new(adapter, relation)
|
49
|
+
# gateway.each { |tuple| ... }
|
50
|
+
#
|
51
|
+
# @yield [tuple]
|
52
|
+
#
|
53
|
+
# @yieldparam [Tuple] tuple
|
54
|
+
# each tuple in the results
|
55
|
+
#
|
56
|
+
# @return [self]
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def each
|
60
|
+
return to_enum unless block_given?
|
61
|
+
tuples.each { |tuple| yield tuple }
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return a relation that is the join of two relations
|
66
|
+
#
|
67
|
+
# @example natural join
|
68
|
+
# join = relation.join(other)
|
69
|
+
#
|
70
|
+
# @example theta-join using a block
|
71
|
+
# join = relation.join(other) { |r| r.a.gte(r.b) }
|
72
|
+
#
|
73
|
+
# @param [Relation] other
|
74
|
+
# the other relation to join
|
75
|
+
#
|
76
|
+
# @yield [relation]
|
77
|
+
# optional block to restrict the tuples with
|
78
|
+
#
|
79
|
+
# @yieldparam [Relation] relation
|
80
|
+
# the context to evaluate the restriction with
|
81
|
+
#
|
82
|
+
# @yieldreturn [Function, #call]
|
83
|
+
# predicate to restrict the tuples with
|
84
|
+
#
|
85
|
+
# @return [Gateway]
|
86
|
+
# return a gateway if the adapters are equal
|
87
|
+
# @return [Algebra::Join]
|
88
|
+
# return a normal join when the adapters are not equal
|
89
|
+
# @return [Algebra::Restriction]
|
90
|
+
# return a normal restriction when the adapters are not equal
|
91
|
+
# for a theta-join
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def join(other)
|
95
|
+
if block_given?
|
96
|
+
super
|
97
|
+
else
|
98
|
+
binary_operation(__method__, other, Algebra::Join)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return a relation that is the cartesian product of two relations
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# product = gateway.product(other)
|
106
|
+
#
|
107
|
+
# @param [Relation] other
|
108
|
+
# the other relation to find the product with
|
109
|
+
#
|
110
|
+
# @return [Gateway]
|
111
|
+
# return a gateway if the adapters are equal
|
112
|
+
# @return [Algebra::Product]
|
113
|
+
# return a normal product when the adapters are not equal
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def product(other)
|
117
|
+
binary_operation(__method__, other, Algebra::Product)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return the union between relations
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# union = gateway.union(other)
|
124
|
+
#
|
125
|
+
# @param [Relation] other
|
126
|
+
# the other relation to find the union with
|
127
|
+
#
|
128
|
+
# @return [Gateway]
|
129
|
+
# return a gateway if the adapters are equal
|
130
|
+
# @return [Algebra::Union]
|
131
|
+
# return a normal union when the adapters are not equal
|
132
|
+
#
|
133
|
+
# @api public
|
134
|
+
def union(other)
|
135
|
+
binary_operation(__method__, other, Algebra::Union)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return the intersection between relations
|
139
|
+
#
|
140
|
+
# @example
|
141
|
+
# intersect = gateway.intersect(other)
|
142
|
+
#
|
143
|
+
# @param [Relation] other
|
144
|
+
# the other relation to find the intersect with
|
145
|
+
#
|
146
|
+
# @return [Gateway]
|
147
|
+
# return a gateway if the adapters are equal
|
148
|
+
# @return [Algebra::Intersection]
|
149
|
+
# return a normal intersection when the adapters are not equal
|
150
|
+
#
|
151
|
+
# @api public
|
152
|
+
def intersect(other)
|
153
|
+
binary_operation(__method__, other, Algebra::Intersection)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Return the diferrence between relations
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# difference = gateway.difference(other)
|
160
|
+
#
|
161
|
+
# @param [Relation] other
|
162
|
+
# the other relation to find the difference with
|
163
|
+
#
|
164
|
+
# @return [Gateway]
|
165
|
+
# return a gateway if the adapters are equal
|
166
|
+
# @return [Algebra::Difference]
|
167
|
+
# return a normal dfference when the adapters are not equal
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
def difference(other)
|
171
|
+
binary_operation(__method__, other, Algebra::Difference)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return a summarized relation
|
175
|
+
#
|
176
|
+
# @example with no arguments
|
177
|
+
# summarization = gateway.summarize do |context|
|
178
|
+
# context.add(:count, context[:id].count)
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# @example with a relation
|
182
|
+
# summarization = gateway.summarize(relation) do |context|
|
183
|
+
# context.add(:count, context[:id].count)
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# @example with a header
|
187
|
+
# summarization = gateway.summarize([ :name ]) do |context|
|
188
|
+
# context.add(:count, context[:id].count)
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# @example with another gateway
|
192
|
+
# summarization = gateway.summarize(other_gateway) do |context|
|
193
|
+
# context.add(:count, context[:id].count)
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# @param [Gateway, Relation, Header, #to_ary] summarize_with
|
197
|
+
#
|
198
|
+
# @yield [function]
|
199
|
+
# Evaluate a summarization function
|
200
|
+
#
|
201
|
+
# @yieldparam [Evaluator::Context] context
|
202
|
+
# the context to evaluate the function within
|
203
|
+
#
|
204
|
+
# @return [Gateway]
|
205
|
+
# return a gateway if the adapters are equal, or there is no adapter
|
206
|
+
# @return [Algebra::Summarization]
|
207
|
+
# return a normal summarization when the adapters are not equal
|
208
|
+
#
|
209
|
+
# @api public
|
210
|
+
def summarize(summarize_with = TABLE_DEE, &block)
|
211
|
+
if summarize_merge?(summarize_with)
|
212
|
+
summarize_merge(summarize_with, &block)
|
213
|
+
else
|
214
|
+
summarize_split(summarize_with, &block)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Test if the method is supported on this object
|
219
|
+
#
|
220
|
+
# @param [Symbol] method
|
221
|
+
#
|
222
|
+
# @return [Boolean]
|
223
|
+
#
|
224
|
+
# @api private
|
225
|
+
def respond_to?(method, *)
|
226
|
+
super || forwardable?(method)
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
# Proxy the message to the relation
|
232
|
+
#
|
233
|
+
# @param [Symbol] method
|
234
|
+
#
|
235
|
+
# @param [Array] args
|
236
|
+
#
|
237
|
+
# @return [self]
|
238
|
+
# return self for all command methods
|
239
|
+
# @return [Object]
|
240
|
+
# return response from all query methods
|
241
|
+
#
|
242
|
+
# @api private
|
243
|
+
def method_missing(method, *args, &block)
|
244
|
+
forwardable?(method) ? forward(method, *args, &block) : super
|
245
|
+
end
|
246
|
+
|
247
|
+
# Test if the method can be forwarded to the relation
|
248
|
+
#
|
249
|
+
# @param [Symbol] method
|
250
|
+
#
|
251
|
+
# @return [Boolean]
|
252
|
+
#
|
253
|
+
# @api private
|
254
|
+
def forwardable?(method)
|
255
|
+
relation.respond_to?(method)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Forward the message to the relation
|
259
|
+
#
|
260
|
+
# @param [Array] args
|
261
|
+
#
|
262
|
+
# @return [self]
|
263
|
+
# return self for all command methods
|
264
|
+
# @return [Object]
|
265
|
+
# return response from all query methods
|
266
|
+
#
|
267
|
+
# @api private
|
268
|
+
def forward(*args, &block)
|
269
|
+
relation = self.relation
|
270
|
+
response = relation.public_send(*args, &block)
|
271
|
+
if response.equal?(relation)
|
272
|
+
self
|
273
|
+
elsif response.kind_of?(DECORATED_CLASS)
|
274
|
+
self.class.new(adapter, response)
|
275
|
+
else
|
276
|
+
response
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Return a list of tuples to iterate over
|
281
|
+
#
|
282
|
+
# @return [#each]
|
283
|
+
#
|
284
|
+
# @api private
|
285
|
+
def tuples
|
286
|
+
relation = self.relation
|
287
|
+
if materialized?
|
288
|
+
relation
|
289
|
+
else
|
290
|
+
DECORATED_CLASS.new(header, adapter.read(relation))
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Return a binary relation
|
295
|
+
#
|
296
|
+
# @param [Relation] other
|
297
|
+
#
|
298
|
+
# @return [Gateway]
|
299
|
+
# return a gateway if the adapters are equal
|
300
|
+
# @return [Relation]
|
301
|
+
# return a binary relation when the adapters are not equal
|
302
|
+
#
|
303
|
+
# @api private
|
304
|
+
def binary_operation(method, other, factory)
|
305
|
+
if same_adapter?(other)
|
306
|
+
forward(method, other.relation)
|
307
|
+
else
|
308
|
+
factory.new(self, other)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Test if the other object is a Gateway
|
313
|
+
#
|
314
|
+
# @param [Gateway, Relation] other
|
315
|
+
#
|
316
|
+
# @return [Boolean]
|
317
|
+
#
|
318
|
+
# @api private
|
319
|
+
def gateway?(other)
|
320
|
+
other.kind_of?(Gateway)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Test if the other object uses the same adapter
|
324
|
+
#
|
325
|
+
# @param [Gateway, Relation] other
|
326
|
+
#
|
327
|
+
# @return [Boolean]
|
328
|
+
#
|
329
|
+
# @api private
|
330
|
+
def same_adapter?(other)
|
331
|
+
gateway?(other) && adapter.eql?(other.adapter)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Test if the summarize_with object can be merged into the summarization
|
335
|
+
#
|
336
|
+
# @param [Gateway, Relation, Header] summarize_with
|
337
|
+
#
|
338
|
+
# @return [Boolean]
|
339
|
+
#
|
340
|
+
# @api private
|
341
|
+
def summarize_merge?(summarize_with)
|
342
|
+
!summarize_with.respond_to?(:header) ||
|
343
|
+
summarize_with.equal?(TABLE_DEE) ||
|
344
|
+
same_adapter?(summarize_with)
|
345
|
+
end
|
346
|
+
|
347
|
+
# Merge the summarize_with into the summarization
|
348
|
+
#
|
349
|
+
# @param [Gateway, Relation, Header] summarize_with
|
350
|
+
#
|
351
|
+
# @return [Gateway]
|
352
|
+
#
|
353
|
+
# @api private
|
354
|
+
def summarize_merge(summarize_with, &block)
|
355
|
+
summarize_with = summarize_with.relation if gateway?(summarize_with)
|
356
|
+
forward(:summarize, summarize_with, &block)
|
357
|
+
end
|
358
|
+
|
359
|
+
# Split the summarize_with into a separate relation, wrapped in a summarization
|
360
|
+
#
|
361
|
+
# @param [Gateway, Relation, Header] summarize_with
|
362
|
+
#
|
363
|
+
# @return [Algebra::Summarization]
|
364
|
+
#
|
365
|
+
# @api private
|
366
|
+
def summarize_split(summarize_with, &block)
|
367
|
+
# evaluate the gateway, then summarize with the provided relation
|
368
|
+
context = Evaluator::Context.new(header - summarize_with.header, &block)
|
369
|
+
Algebra::Summarization.new(self, summarize_with, context.functions)
|
370
|
+
end
|
371
|
+
|
372
|
+
end # class Gateway
|
373
|
+
end # class Relation
|
374
|
+
end # module Axiom
|