dynamoid-edge 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,181 @@
1
+ # require only 'concurrent/atom' once this issue is resolved:
2
+ # https://github.com/ruby-concurrency/concurrent-ruby/pull/377
3
+ require 'concurrent'
4
+
5
+ # encoding: utf-8
6
+ module Dynamoid
7
+
8
+ # Adapter's value-add:
9
+ # 1) For the rest of Dynamoid, the gateway to DynamoDB.
10
+ # 2) Allows switching `config.adapter` to ease development of a new adapter.
11
+ # 3) Caches the list of tables Dynamoid knows about.
12
+ class Adapter
13
+ def initialize
14
+ @adapter_ = Concurrent::Atom.new(nil)
15
+ @tables_ = Concurrent::Atom.new(nil)
16
+ end
17
+
18
+ def tables
19
+ if !@tables_.value
20
+ @tables_.swap{|value, args| benchmark('Cache Tables') {list_tables}}
21
+ end
22
+ @tables_.value
23
+ end
24
+
25
+ # The actual adapter currently in use.
26
+ #
27
+ # @since 0.2.0
28
+ def adapter
29
+ if !@adapter_.value
30
+ adapter = self.class.adapter_plugin_class.new
31
+ adapter.connect! if adapter.respond_to?(:connect!)
32
+ @adapter_.compare_and_set(nil, adapter)
33
+ clear_cache!
34
+ end
35
+ @adapter_.value
36
+ end
37
+
38
+ def clear_cache!
39
+ @tables_.swap{|value, args| nil}
40
+ end
41
+
42
+ # Shows how long it takes a method to run on the adapter. Useful for generating logged output.
43
+ #
44
+ # @param [Symbol] method the name of the method to appear in the log
45
+ # @param [Array] args the arguments to the method to appear in the log
46
+ # @yield the actual code to benchmark
47
+ #
48
+ # @return the result of the yield
49
+ #
50
+ # @since 0.2.0
51
+ def benchmark(method, *args)
52
+ start = Time.now
53
+ result = yield
54
+ Dynamoid.logger.info "(#{((Time.now - start) * 1000.0).round(2)} ms) #{method.to_s.split('_').collect(&:upcase).join(' ')}#{ " - #{args.inspect}" unless args.nil? || args.empty? }"
55
+ return result
56
+ end
57
+
58
+ # Write an object to the adapter.
59
+ #
60
+ # @param [String] table the name of the table to write the object to
61
+ # @param [Object] object the object itself
62
+ # @param [Hash] options Options that are passed to the put_item call
63
+ #
64
+ # @return [Object] the persisted object
65
+ #
66
+ # @since 0.2.0
67
+ def write(table, object, options = nil)
68
+ put_item(table, object, options)
69
+ end
70
+
71
+ # Read one or many keys from the selected table.
72
+ # This method intelligently calls batch_get or get on the underlying adapter
73
+ # depending on whether ids is a range or a single key.
74
+ # If a range key is present, it will also interpolate that into the ids so
75
+ # that the batch get will acquire the correct record.
76
+ #
77
+ # @param [String] table the name of the table to write the object to
78
+ # @param [Array] ids to fetch, can also be a string of just one id
79
+ # @param [Hash] options: Passed to the underlying query. The :range_key option is required whenever the table has a range key,
80
+ # unless multiple ids are passed in.
81
+ #
82
+ # @since 0.2.0
83
+ def read(table, ids, options = {})
84
+ range_key = options.delete(:range_key)
85
+
86
+ if ids.respond_to?(:each)
87
+ ids = ids.collect{|id| range_key ? [id, range_key] : id}
88
+ batch_get_item({table => ids}, options)
89
+ else
90
+ options[:range_key] = range_key if range_key
91
+ get_item(table, ids, options)
92
+ end
93
+ end
94
+
95
+ # Delete an item from a table.
96
+ #
97
+ # @param [String] table the name of the table to write the object to
98
+ # @param [Array] ids to delete, can also be a string of just one id
99
+ # @param [Array] range_key of the record to delete, can also be a string of just one range_key
100
+ #
101
+ def delete(table, ids, options = {})
102
+ range_key = options[:range_key] #array of range keys that matches the ids passed in
103
+ if ids.respond_to?(:each)
104
+ if range_key.respond_to?(:each)
105
+ #turn ids into array of arrays each element being hash_key, range_key
106
+ ids = ids.each_with_index.map{|id,i| [id,range_key[i]]}
107
+ else
108
+ ids = range_key ? [[ids, range_key]] : ids
109
+ end
110
+
111
+ batch_delete_item(table => ids)
112
+ else
113
+ delete_item(table, ids, options)
114
+ end
115
+ end
116
+
117
+ # Scans a table. Generally quite slow; try to avoid using scan if at all possible.
118
+ #
119
+ # @param [String] table the name of the table to write the object to
120
+ # @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
121
+ #
122
+ # @since 0.2.0
123
+ def scan(table, query, opts = {})
124
+ benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
125
+ end
126
+
127
+ def create_table(table_name, key, options = {})
128
+ if !tables.include?(table_name)
129
+ benchmark('Create Table') { adapter.create_table(table_name, key, options) }
130
+ tables << table_name
131
+ end
132
+ end
133
+
134
+ [:batch_get_item, :delete_item, :delete_table, :get_item, :list_tables, :put_item].each do |m|
135
+ # Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
136
+ #
137
+ # @since 0.2.0
138
+ define_method(m) do |*args|
139
+ benchmark("#{m.to_s}", args) {adapter.send(m, *args)}
140
+ end
141
+ end
142
+
143
+ # Delegate all methods that aren't defind here to the underlying adapter.
144
+ #
145
+ # @since 0.2.0
146
+ def method_missing(method, *args, &block)
147
+ return benchmark(method, *args) {adapter.send(method, *args, &block)} if adapter.respond_to?(method)
148
+ super
149
+ end
150
+
151
+ # Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
152
+ # only really useful for range queries, since it can only find by one hash key at once. Only provide
153
+ # one range key to the hash.
154
+ #
155
+ # @param [String] table_name the name of the table
156
+ # @param [Hash] opts the options to query the table with
157
+ # @option opts [String] :hash_value the value of the hash key to find
158
+ # @option opts [Range] :range_value find the range key within this range
159
+ # @option opts [Number] :range_greater_than find range keys greater than this
160
+ # @option opts [Number] :range_less_than find range keys less than this
161
+ # @option opts [Number] :range_gte find range keys greater than or equal to this
162
+ # @option opts [Number] :range_lte find range keys less than or equal to this
163
+ #
164
+ # @return [Array] an array of all matching items
165
+ #
166
+ def query(table_name, opts = {})
167
+ adapter.query(table_name, opts)
168
+ end
169
+
170
+ private
171
+
172
+ def self.adapter_plugin_class
173
+ unless Dynamoid.const_defined?(:AdapterPlugin) && Dynamoid::AdapterPlugin.const_defined?(Dynamoid::Config.adapter.camelcase)
174
+ require "dynamoid/adapter_plugin/#{Dynamoid::Config.adapter}"
175
+ end
176
+
177
+ Dynamoid::AdapterPlugin.const_get(Dynamoid::Config.adapter.camelcase)
178
+ end
179
+
180
+ end
181
+ end