dm-adapter-simpledb 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,20 @@
1
+ == 1.3.0 2010-01-19
2
+
3
+ * 1 major enhancement:
4
+ * Now supports :offset in queries. Combined with improved :limit support, this
5
+ makes it possible to efficiently paginate result sets.
6
+
7
+ * 1 minor enhancement:
8
+ * Better quoting of select statements using the 'sdbtools' library.
9
+
10
+ == 1.2.0 2010-01-13
11
+
12
+ * 2 minor enhancements:
13
+ * Better support for String attributes with multiple values which were not
14
+ created by the String chunking system.
15
+ * Now tries to take model and repository into account when determining storage
16
+ name for a table.
17
+
1
18
  == 1.1.0 2009-11-24
2
19
 
3
20
  * 2 major enhancements:
data/README CHANGED
@@ -16,6 +16,7 @@ Features:
16
16
  * Array properties
17
17
  * Basic aggregation support (Model.count("..."))
18
18
  * String "chunking" permits attributes to exceed the 1024-byte limit
19
+ * Support for efficient :limit and :offset, for result set paging
19
20
 
20
21
  Note: as of version 1.0.0, this gem supports supports the DataMapper 0.10.*
21
22
  series and breaks backwards compatibility with DataMapper 0.9.*.
@@ -39,6 +40,7 @@ http://github.com/devver/dm-adapter-simpledb/
39
40
  == TODO
40
41
 
41
42
  * More complete handling of NOT conditions in queries
43
+ * Support for ORs, parens, and nested queries in general
42
44
  * Robust quoting in SELECT calls
43
45
  * Handle exclusive ranges natively
44
46
  Implement as inclusive range + filter step
@@ -48,15 +50,6 @@ http://github.com/devver/dm-adapter-simpledb/
48
50
  - Store floats using exponential notation
49
51
  * Option to store Date/Time/DateTime as ISO8601
50
52
  * Full aggregate support (min/max/etc)
51
- * Offset support
52
- Note, from the SimpleDB documentation:
53
-
54
- "The next token returned by count(*) and select are interchangeable as long
55
- as the where and order by clauses match. For example, if you want to return
56
- the 200 items after the first 10,000 (similar to an offset), you can perform
57
- a count with a limit clause of 10,000 and use the next token to return the
58
- next 200 items with select."
59
-
60
53
  * Option to use libxml if available
61
54
  * Parallelized queries for increased throughput
62
55
  * Support of normalized 1:1 table:domain schemes that works with associations
@@ -64,6 +57,7 @@ http://github.com/devver/dm-adapter-simpledb/
64
57
  * Support BatchPutAttributes
65
58
  * Silence SSL warnings
66
59
  See http://pivotallabs.com/users/carl/blog/articles/1079-standup-blog-11-24-2009-model-validations-without-backing-store-associations-to-array-and-ssl-with-aws
60
+ * Token cache for reduced requests when given an offset
67
61
 
68
62
  == Usage
69
63
 
data/Rakefile CHANGED
@@ -81,7 +81,7 @@ END
81
81
  gem.add_dependency('dm-migrations', '~> 0.10.0')
82
82
  gem.add_dependency('dm-types', '~> 0.10.0')
83
83
  gem.add_dependency('uuidtools', '~> 2.0')
84
- gem.add_dependency('right_aws', '~> 1.10')
84
+ gem.add_dependency('sdbtools', '~> 0.2')
85
85
  end
86
86
  Jeweler::GemcutterTasks.new
87
87
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.3.0
@@ -8,6 +8,7 @@ require 'dm-aggregates'
8
8
  require 'digest/sha1'
9
9
  require 'right_aws'
10
10
  require 'uuidtools'
11
+ require 'sdbtools'
11
12
 
12
13
  require 'dm-adapter-simpledb/sdb_array'
13
14
  require 'dm-adapter-simpledb/utils'
@@ -92,7 +92,16 @@ module DataMapper
92
92
  query.conditions.operands.reject!{ |op|
93
93
  !unsupported_conditions.include?(op)
94
94
  }
95
- records = query.filter_records(proto_resources)
95
+
96
+ # This used to be a simple call to Query#filter_records(), but that
97
+ # caused the result limit to be re-imposed on an already limited result
98
+ # set, with the upshot that too few records were returned. So here we do
99
+ # everything filter_records() does EXCEPT limiting.
100
+ records = proto_resources
101
+ records = records.uniq if query.unique?
102
+ records = query.match_records(records)
103
+ records = query.sort_records(records)
104
+
96
105
 
97
106
  records
98
107
  end
@@ -123,7 +132,10 @@ module DataMapper
123
132
  end
124
133
 
125
134
  def query(query_call, query_limit = 999999999)
126
- select(query_call, query_limit).collect{|x| x.values[0]}
135
+ SDBTools::Operation.new(sdb, :select, query_call).inject([]){
136
+ |a, results|
137
+ a.concat(results[:items].map{|i| i.values.first})
138
+ }[0...query_limit]
127
139
  end
128
140
 
129
141
  def aggregate(query)
@@ -147,7 +159,7 @@ module DataMapper
147
159
  token = :none
148
160
  begin
149
161
  results = sdb.get_attributes(domain, '__dm_consistency_token', '__dm_consistency_token')
150
- tokens = results[:attributes]['__dm_consistency_token']
162
+ tokens = Array(results[:attributes]['__dm_consistency_token'])
151
163
  end until tokens.include?(@current_consistency_token)
152
164
  end
153
165
 
@@ -227,43 +239,59 @@ module DataMapper
227
239
  else
228
240
  raise ArgumentError, "Unsupported inclusion op: #{op.value.inspect}"
229
241
  end
242
+ when :or
243
+ # TODO There's no reason not to support OR
244
+ unsupported_conditions << op
230
245
  else raise "Invalid query op: #{op.inspect}"
231
246
  end
232
247
  end
233
248
  [conditions,order,unsupported_conditions]
234
249
  end
235
250
 
236
- def select(query_call, query_limit)
237
- items = []
238
- time = Benchmark.realtime do
239
- sdb_continuation_key = nil
240
- while (results = sdb.select(query_call, sdb_continuation_key)) do
241
- sdb_continuation_key = results[:next_token]
242
- items += results[:items]
243
- break if items.length > query_limit
244
- break if sdb_continuation_key.nil?
245
- end
246
- end; DataMapper.logger.debug(format_log_entry(query_call, time))
247
- items[0...query_limit]
248
- end
249
-
250
251
  #gets all results or proper number of results depending on the :limit
251
252
  def get_results(query, conditions, order)
252
253
  fields_to_request = query.fields.map{|f| f.field}
253
254
  fields_to_request << DmAdapterSimpledb::Record::METADATA_KEY
254
- output_list = fields_to_request.join(', ')
255
- query_call = "SELECT #{output_list} FROM #{domain} "
256
- query_call << "WHERE #{conditions.compact.join(' AND ')}" if conditions.length > 0
257
- query_call << " #{order}"
258
- if query.limit!=nil
259
- query_limit = query.limit
260
- query_call << " LIMIT #{query.limit}"
255
+
256
+ selection = SDBTools::Selection.new(
257
+ sdb,
258
+ domain,
259
+ :attributes => fields_to_request)
260
+
261
+ if query.order && query.order.length > 0
262
+ query_object = query.order[0]
263
+ #anything sorted on must be a condition for SDB
264
+ conditions << "#{query_object.target.name} IS NOT NULL"
265
+ selection.order_by = query_object.target.name
266
+ selection.order = case query_object.operator
267
+ when :asc then :ascending
268
+ when :desc then :descending
269
+ else raise "Unrecognized sort direction"
270
+ end
271
+ end
272
+ selection.conditions = conditions.compact.inject([]){|conds, cond|
273
+ conds << "AND" unless conds.empty?
274
+ conds << cond
275
+ }
276
+ if query.limit.nil?
277
+ selection.limit = :none
261
278
  else
262
- #on large items force the max limit
263
- query_limit = 999999999 #TODO hack for query.limit being nil
264
- #query_call << " limit 2500" #this doesn't work with continuation keys as it halts at the limit passed not just a limit per query.
279
+ selection.limit = query.limit
280
+ end
281
+ unless query.offset.nil?
282
+ selection.offset = query.offset
265
283
  end
266
- records = select(query_call, query_limit)
284
+
285
+ items = []
286
+ time = Benchmark.realtime do
287
+ # TODO update Record to be created from name/attributes pair
288
+ selection.each do |name, value|
289
+ items << {name => value}
290
+ end
291
+ end
292
+ DataMapper.logger.debug(format_log_entry(selection.to_s, time))
293
+
294
+ items
267
295
  end
268
296
 
269
297
  # Creates an item name for a query
@@ -105,6 +105,12 @@ describe 'with multiple records saved' do
105
105
  results = Hero.all(:limit => 110)
106
106
  results.should have(110).entries
107
107
  end
108
+
109
+ it "should be able to page through results" do
110
+ results1 = Hero.all(:limit => 10, :order => [:id.asc])
111
+ results2 = Hero.all(:offset => 9, :limit => 10, :order => [:id.asc])
112
+ results1.to_a.last.name.should be == results2.to_a.first.name
113
+ end
108
114
  end
109
115
 
110
116
  end
@@ -74,7 +74,20 @@ describe DataMapper::Adapters::SimpleDBAdapter do
74
74
  ])
75
75
  @record = Product.first
76
76
  end
77
-
78
77
  end
79
78
 
79
+ # it "should be able to request items with an offset" do
80
+ # @sdb.should_receive(:select).
81
+ # with(/count(\*).*LIMIT 10000/, anything).
82
+ # exactly(1).times.
83
+ # ordered.
84
+ # and_return(:next_token => "TOKEN")
85
+ # @sdb.should_receive(:select).
86
+ # with(anything, "TOKEN").
87
+ # exactly(1).times.
88
+ # ordered.
89
+ # and_return(:items => [])
90
+ # @record = Product.all(:offset => 10000, :limit => 10)
91
+ # end
92
+
80
93
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-adapter-simpledb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Boles
@@ -13,7 +13,7 @@ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
15
 
16
- date: 2010-01-13 00:00:00 -05:00
16
+ date: 2010-01-19 00:00:00 -05:00
17
17
  default_executable:
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
@@ -67,14 +67,14 @@ dependencies:
67
67
  version: "2.0"
68
68
  version:
69
69
  - !ruby/object:Gem::Dependency
70
- name: right_aws
70
+ name: sdbtools
71
71
  type: :runtime
72
72
  version_requirement:
73
73
  version_requirements: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: "1.10"
77
+ version: "0.2"
78
78
  version:
79
79
  description: |
80
80
  A DataMapper adapter for Amazon's SimpleDB service.