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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gemtest +0 -0
  4. data/.gitignore +37 -0
  5. data/.rspec +4 -0
  6. data/.rvmrc +1 -0
  7. data/.travis.yml +35 -0
  8. data/CONTRIBUTING.md +11 -0
  9. data/Gemfile +10 -0
  10. data/Gemfile.devtools +57 -0
  11. data/Guardfile +23 -0
  12. data/LICENSE +20 -0
  13. data/README.md +26 -0
  14. data/Rakefile +5 -0
  15. data/TODO +0 -0
  16. data/axiom-do-adapter.gemspec +27 -0
  17. data/config/flay.yml +3 -0
  18. data/config/flog.yml +2 -0
  19. data/config/mutant.yml +3 -0
  20. data/config/reek.yml +115 -0
  21. data/config/yardstick.yml +2 -0
  22. data/lib/axiom-do-adapter.rb +4 -0
  23. data/lib/axiom/adapter/data_objects.rb +55 -0
  24. data/lib/axiom/adapter/data_objects/statement.rb +109 -0
  25. data/lib/axiom/adapter/data_objects/version.rb +12 -0
  26. data/lib/axiom/relation/gateway.rb +374 -0
  27. data/spec/rcov.opts +7 -0
  28. data/spec/shared/binary_relation_method_behaviour.rb +51 -0
  29. data/spec/shared/unary_relation_method_behaviour.rb +21 -0
  30. data/spec/spec_helper.rb +46 -0
  31. data/spec/support/config_alias.rb +3 -0
  32. data/spec/support/example_group_methods.rb +7 -0
  33. data/spec/support/ice_nine_config.rb +6 -0
  34. data/spec/unit/axiom/adapter/data_objects/class_methods/new_spec.rb +15 -0
  35. data/spec/unit/axiom/adapter/data_objects/read_spec.rb +66 -0
  36. data/spec/unit/axiom/adapter/data_objects/statement/class_methods/new_spec.rb +28 -0
  37. data/spec/unit/axiom/adapter/data_objects/statement/each_spec.rb +63 -0
  38. data/spec/unit/axiom/adapter/data_objects/statement/to_s_spec.rb +53 -0
  39. data/spec/unit/axiom/relation/gateway/class_methods/new_spec.rb +16 -0
  40. data/spec/unit/axiom/relation/gateway/difference_spec.rb +17 -0
  41. data/spec/unit/axiom/relation/gateway/drop_spec.rb +21 -0
  42. data/spec/unit/axiom/relation/gateway/each_spec.rb +86 -0
  43. data/spec/unit/axiom/relation/gateway/extend_spec.rb +29 -0
  44. data/spec/unit/axiom/relation/gateway/intersect_spec.rb +17 -0
  45. data/spec/unit/axiom/relation/gateway/join_spec.rb +44 -0
  46. data/spec/unit/axiom/relation/gateway/materialize_spec.rb +27 -0
  47. data/spec/unit/axiom/relation/gateway/optimize_spec.rb +23 -0
  48. data/spec/unit/axiom/relation/gateway/product_spec.rb +17 -0
  49. data/spec/unit/axiom/relation/gateway/project_spec.rb +21 -0
  50. data/spec/unit/axiom/relation/gateway/remove_spec.rb +21 -0
  51. data/spec/unit/axiom/relation/gateway/rename_spec.rb +21 -0
  52. data/spec/unit/axiom/relation/gateway/respond_to_spec.rb +29 -0
  53. data/spec/unit/axiom/relation/gateway/restrict_spec.rb +29 -0
  54. data/spec/unit/axiom/relation/gateway/reverse_spec.rb +21 -0
  55. data/spec/unit/axiom/relation/gateway/sort_by_spec.rb +29 -0
  56. data/spec/unit/axiom/relation/gateway/summarize_spec.rb +154 -0
  57. data/spec/unit/axiom/relation/gateway/take_spec.rb +21 -0
  58. data/spec/unit/axiom/relation/gateway/union_spec.rb +17 -0
  59. 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,12 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Adapter
5
+ class DataObjects
6
+
7
+ # Gem version
8
+ VERSION = '0.1.0'.freeze
9
+
10
+ end # class DataObjects
11
+ end # module Adapter
12
+ 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