dm-adapter-simpledb 1.3.0 → 1.4.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.
@@ -1,43 +1,2 @@
1
1
  namespace :simpledb do
2
- desc "Migrate records to be compatable with current DM/SimpleDB adapter"
3
- task :migrate, :domain do |t, args|
4
- raise "THIS IS A WORK IN PROGRESS AND WILL DESTROY YOUR DATA"
5
- require 'progressbar'
6
- require 'right_aws'
7
- require 'dm-adapter-simpledb/record'
8
-
9
- puts "Initializing connection..."
10
- domain = args.domain
11
- sdb = RightAws::SdbInterface.new
12
- puts "Counting records..."
13
- num_legacy_records = 0
14
- query = "select count(*) from #{domain} where (simpledb_type is not null) and (__dm_metadata is null)"
15
- next_token = nil
16
- while(results = sdb.select(query, next_token)) do
17
- next_token = results[:next_token]
18
- count = results[:items].first["Domain"]["Count"].first.to_i
19
- num_legacy_records += count
20
- break if next_token.nil?
21
- end
22
- puts "Found #{num_legacy_records} to migrate"
23
-
24
- pbar = ProgressBar.new("migrate", num_legacy_records)
25
- query = "select * from #{domain} where (simpledb_type is not null) and (__dm_metadata is null)"
26
- while(results = sdb.select(query, next_token)) do
27
- next_token = results[:next_token]
28
- items = results[:items]
29
- items.each do |item|
30
- legacy_record = DmAdapterSimpledb::Record.from_simpledb_hash(item)
31
- new_record = legacy_record.migrate
32
- updates = new_record.writable_attributes
33
- deletes = new_record.deletable_attributes
34
- sdb.put_attributes(domain, new_record.item_name, updates)
35
- sdb.delete_attributes(domain, new_record.item_name, deletes)
36
- pbar.inc
37
- end
38
- break if next_token.nil?
39
- end
40
- pbar.finish
41
-
42
- end
43
2
  end
@@ -315,7 +315,7 @@ module DmAdapterSimpledb
315
315
  private
316
316
 
317
317
  def replace_newline_placeholders(value)
318
- value.gsub("[[[NEWLINE]]]", "\n")
318
+ value && value.gsub("[[[NEWLINE]]]", "\n")
319
319
  end
320
320
  end
321
321
 
@@ -20,8 +20,9 @@ module DmAdapterSimpledb
20
20
 
21
21
  # Returns a string so we know what type of
22
22
  def simpledb_type
23
- model.storage_name(repository_name)
23
+ model.storage_name(DataMapper.repository.name)
24
24
  end
25
+ alias_method :storage_name, :simpledb_type
25
26
 
26
27
  def repository_name
27
28
  # TODO this should probably take into account the adapter
@@ -1,5 +1,49 @@
1
1
  module DmAdapterSimpledb
2
2
  module Utils
3
+ class NullObject
4
+ def method_missing(*args, &block)
5
+ self
6
+ end
7
+ end
8
+
9
+ class NullSdbInterface
10
+ def initialize(logger=NullObject.new)
11
+ @logger = logger
12
+ end
13
+
14
+ def select(*args, &block)
15
+ @logger.debug "[SELECT] #{args.inspect}"
16
+ {
17
+ :items => []
18
+ }
19
+ end
20
+
21
+ def get_attributes(*args, &block)
22
+ @logger.debug "[GET_ATTRIBUTES] #{args.inspect}"
23
+ {}
24
+ end
25
+
26
+ def list_domains(*args, &block)
27
+ @logger.debug "[LIST_DOMAINS] #{args.inspect}"
28
+ {}
29
+ end
30
+
31
+ def put_attributes(*args, &block)
32
+ @logger.debug "[PUT_ATTRIBUTES] #{args.inspect}"
33
+ {}
34
+ end
35
+
36
+ def delete_attributes(*args, &block)
37
+ @logger.debug "[DELETE_ATTRIBUTES] #{args.inspect}"
38
+ {}
39
+ end
40
+
41
+ def create_domain(*args, &block)
42
+ @logger.debug "[CREATE_DOMAIN] #{args.inspect}"
43
+ {}
44
+ end
45
+ end
46
+
3
47
  def transform_hash(original, options={}, &block)
4
48
  original.inject({}){|result, (key,value)|
5
49
  value = if (options[:deep] && Hash === value)
@@ -0,0 +1,180 @@
1
+ module DmAdapterSimpledb
2
+ class WhereExpression
3
+ include DataMapper::Query::Conditions
4
+
5
+ attr_reader :conditions
6
+ attr_accessor :logger
7
+
8
+ def initialize(conditions, options={})
9
+ @conditions = conditions
10
+ @logger = options.fetch(:logger){ DataMapper.logger }
11
+ end
12
+
13
+ def to_s
14
+ node_to_expression(conditions)
15
+ end
16
+
17
+ def unsupported_conditions(node=conditions, top=true)
18
+ case node
19
+ when InclusionComparison
20
+ if node.value.is_a?(Range) && node.value.exclude_end?
21
+ logger.warn "Exclusive ranges are not supported natively by the SimpleDB adapter"
22
+ node.dup
23
+ end
24
+ when RegexpComparison
25
+ logger.warn "Regexp comparisons are not supported natively by the SimpleDB adapter"
26
+ node.dup
27
+ when AbstractOperation
28
+ op_copy = node.dup
29
+ op_copy.clear
30
+ operands_copy =
31
+ node.operands.map{|o| unsupported_conditions(o,false)}.compact
32
+ if operands_copy.size > 0
33
+ op_copy.operands.merge(operands_copy)
34
+ op_copy
35
+ else
36
+ top ? Operation.new(:and) : nil
37
+ end
38
+ else top ? Operation.new(:and) : nil
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def node_to_expression(node)
45
+ case node
46
+ when AbstractOperation then operation_to_expression(node)
47
+ when AbstractComparison then comparison_to_expression(node)
48
+ when Array then array_to_expression(node)
49
+ when nil then nil
50
+ else raise NotImplementedError, "Unrecognized node: #{node.inspect}"
51
+ end
52
+ end
53
+
54
+ def operation_to_expression(operation)
55
+ case operation
56
+ when NotOperation
57
+ operand = operation.sorted_operands.first # NOT only allowed to have 1 operand
58
+ if operand.is_a?(EqualToComparison)
59
+ if operand.value.nil?
60
+ comparison_to_expression(operand, "IS NOT")
61
+ else
62
+ comparison_to_expression(operand, "!=")
63
+ end
64
+ elsif empty_inclusion?(operand)
65
+ nil
66
+ else
67
+ "NOT #{node_to_expression(operand)}"
68
+ end
69
+ when AndOperation then
70
+ join_operands(operation, "AND")
71
+ when OrOperation then
72
+ join_operands(operation, "OR")
73
+ when nil then nil
74
+ else raise NotImplementedError, "Unhandled operation: #{operation.inspect}"
75
+ end
76
+ end
77
+
78
+ def join_operands(operation, delimiter)
79
+ operands = operation.sorted_operands
80
+ joined_operands = Array(operands).map{|o|
81
+ catch(:unsupported) { node_to_expression(o) }
82
+ }.compact.join(" #{delimiter} ")
83
+ if operation.parent
84
+ "( #{joined_operands} )"
85
+ else
86
+ joined_operands
87
+ end
88
+ end
89
+
90
+ def comparison_to_expression(
91
+ comparison,
92
+ operator=comparison_operator(comparison),
93
+ values = value_to_expression(comparison.value))
94
+ field = SDBTools::Selection.quote_name(comparison.subject.field)
95
+ if empty_inclusion?(comparison)
96
+ "#{field} IS NULL"
97
+ else
98
+ "#{field} #{operator} #{values}"
99
+ end
100
+ end
101
+
102
+ def array_to_expression(array)
103
+ template, *replacements = *array.dup
104
+ if replacements.size == 1 && replacements.first.is_a?(Array)
105
+ replacements = replacements.first
106
+ end
107
+ if replacements.size == 1 && replacements[0].is_a?(Hash)
108
+ fill_template_from_hash(template, replacements[0])
109
+ else
110
+ fill_template_from_array(template, replacements)
111
+ end
112
+ end
113
+
114
+ def fill_template_from_array(template, replacements)
115
+ template.to_s.gsub("?") {|match| quote_value(replacements.shift.to_s) }
116
+ end
117
+
118
+ def fill_template_from_hash(template, replacements)
119
+ template.to_s.gsub(/:\w+/) { |match|
120
+ quote_value(replacements.fetch(match[1..-1].to_sym){match})
121
+ }
122
+ end
123
+
124
+ def value_to_expression(value)
125
+ case value
126
+ when nil then "NULL"
127
+ when Range then
128
+ "#{quote_value(value.begin)} AND #{quote_value(value.end)}"
129
+ when Array then
130
+ value = value.map{|v| value_to_expression(v) }.join(", ")
131
+ value = "(#{value})"
132
+ else quote_value(value)
133
+ end
134
+ end
135
+
136
+ def quote_value(value)
137
+ SDBTools::Selection.quote_value(value)
138
+ end
139
+
140
+ def comparison_operator(comparison)
141
+ case comparison
142
+ when EqualToComparison then
143
+ case comparison.value
144
+ when nil then "IS"
145
+ else "="
146
+ end
147
+ when GreaterThanComparison then ">"
148
+ when LessThanComparison then "<"
149
+ when GreaterThanOrEqualToComparison then ">="
150
+ when LessThanOrEqualToComparison then "<="
151
+ when LikeComparison then "LIKE"
152
+ when InclusionComparison then
153
+ case comparison.value
154
+ when Range then
155
+ if comparison.value.exclude_end?
156
+ # We have tried to support exclusive ranges by simply adding
157
+ # the range to the unsupported_conditions list so that excluded
158
+ # values will be caught in post-filtering. However, DataMapper
159
+ # is apparently casting the range begin/end values to Strings, even
160
+ # when the property type is an Integer.
161
+
162
+ # Or it may be that something else is going wrong. But the upshot
163
+ # is that the post-filtering step doesn't work.
164
+ raise NotImplementedError,
165
+ "Exclusive ranges are not supported by the SimpleDB adapter"
166
+ else
167
+ "BETWEEN"
168
+ end
169
+ else "IN"
170
+ end
171
+ when RegexpComparison then throw :unsupported
172
+ else raise NotImplementedError, "Unhandled comparison: #{comparison.inspect}"
173
+ end
174
+ end
175
+
176
+ def empty_inclusion?(node)
177
+ node.is_a?(InclusionComparison) && Array(node.value).empty?
178
+ end
179
+ end
180
+ end
data/scripts/console ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require File.expand_path('../lib/dm-adapter-simpledb', File.dirname(__FILE__))
4
+ require 'logger'
5
+ require 'irb'
6
+
7
+ include DataMapper
8
+
9
+ class Post
10
+ include Resource
11
+
12
+ property :title, String, :key => true
13
+ property :body, Text
14
+ end
15
+
16
+ DataMapper.setup(
17
+ :default,
18
+ :domain => "example",
19
+ :adapter => 'simpledb',
20
+ :null => true,
21
+ :logger => ::Logger.new($stderr, ::Logger::DEBUG))
22
+
23
+ IRB.start
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require File.expand_path('../lib/dm-adapter-simpledb', File.dirname(__FILE__))
4
+ require 'logger'
5
+
6
+ include DataMapper
7
+
8
+ class Post
9
+ include Resource
10
+
11
+ property :title, String, :key => true
12
+ property :body, Text
13
+ end
14
+
15
+ logger = ::Logger.new($stderr, ::Logger::DEBUG)
16
+
17
+ DataMapper.setup(
18
+ :default,
19
+ :domain => "dm_simpledb_adapter_benchmark",
20
+ :adapter => 'simpledb',
21
+ :create_domain => true,
22
+ :logger => logger,
23
+ :batch_limit => 10)
24
+
25
+ logger.info "Writing records"
26
+ 100.times do |i|
27
+ Post.create(:title => "title#{i}", :body => "body#{i}")
28
+ end
29
+
30
+ logger.info "Waiting a bit for consistency"
31
+ sleep 2
32
+
33
+ SDBTools::Transaction.on_close =
34
+ SDBTools::Transaction.log_transaction_close(logger)
35
+ SDBTools::Transaction.open("benchmark limited query") do |t|
36
+ # With a batch limit of 10, this should result in two queries of LIMIT 10 and
37
+ # one with LIMIT 5
38
+ query = Post.all(:limit => 25)
39
+ item_count = query.to_a.size
40
+ logger.info "Found #{item_count} items"
41
+ end
42
+
43
+ SDBTools::Transaction.on_close =
44
+ SDBTools::Transaction.log_transaction_close(logger)
45
+ SDBTools::Transaction.open("benchmark query with limit and offset") do |t|
46
+ # With a batch limit of 10, this should result in two queries of LIMIT 10 and
47
+ # one with LIMIT 5
48
+ query = Post.all(:limit => 25, :order => :title.asc, :offset => 25)
49
+ item_count = query.to_a.size
50
+ logger.info "Found #{item_count} items"
51
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require File.expand_path('../lib/dm-adapter-simpledb', File.dirname(__FILE__))
4
+ require 'logger'
5
+
6
+ include DataMapper
7
+
8
+ class Post
9
+ include Resource
10
+
11
+ property :title, String, :key => true
12
+ property :body, Text
13
+ end
14
+
15
+ logger = ::Logger.new($stderr, ::Logger::DEBUG)
16
+
17
+ DataMapper.setup(
18
+ :default,
19
+ :domain => "dm_simpledb_adapter_benchmark",
20
+ :adapter => 'simpledb',
21
+ :create_domain => true,
22
+ :logger => logger)
23
+
24
+ logger.info "Writing records"
25
+ 100.times do |i|
26
+ Post.create(:title => "title#{i}", :body => "body#{i}")
27
+ end
28
+
29
+ logger.info "Waiting a bit for consistency"
30
+ sleep 2
31
+
32
+ logger.info "benchmarking OR"
33
+ SDBTools::Transaction.on_close =
34
+ SDBTools::Transaction.log_transaction_close(logger)
35
+ SDBTools::Transaction.open("benchmark OR") do |t|
36
+ query = Post.all(:title.like => "%2%") | Post.all(:title.like => "%5%")
37
+ item_count = query.to_a.size
38
+ logger.info "Found #{item_count} items"
39
+ end
@@ -8,6 +8,7 @@ describe DataMapper::Adapters::SimpleDBAdapter do
8
8
  @adapter = DataMapper::Repository.adapters[:default]
9
9
  @old_consistency_policy = @adapter.consistency_policy
10
10
  @adapter.consistency_policy = :automatic
11
+ # @adapter.logger = ::Logger.new($stderr)
11
12
  end
12
13
 
13
14
  after :all do
@@ -40,7 +40,7 @@ describe 'with multiple records saved' do
40
40
 
41
41
  it 'should handle max item if limit is large case' do
42
42
  persons = Hero.all(:limit => 150)
43
- persons.length.should ==3
43
+ persons.length.should == 3
44
44
  end
45
45
 
46
46
  it 'should handle ordering asc results with a limit' do
@@ -138,33 +138,15 @@ EOF
138
138
  persons.length.should == 0
139
139
  end
140
140
 
141
- describe '#query' do
142
- before(:each) do
143
- @domain = Friend.repository(:default).adapter.sdb_options[:domain]
144
- end
145
- it "should return an array of records" do
146
- records = Friend.repository(:default).adapter.query("SELECT age, wealth from #{@domain} where age = '25'")
147
- records.should == [{"wealth"=>["25.0"], "age"=>["25"]}]
148
- end
149
- it "should return empty array if no matches" do
150
- records = Friend.repository(:default).adapter.query("SELECT age, wealth from #{@domain} where age = '15'")
151
- records.should be_empty
152
- end
153
- it "should raise an error if query is invalid" do
154
- lambda do
155
- records = Friend.repository(:default).adapter.query("SELECT gaga")
156
- end.should raise_error(RightAws::AwsError)
157
- end
158
- end
159
141
  describe 'aggregate' do
160
142
  it "should respond to count(*)" do
161
143
  Friend.count.should == 1
162
144
  end
163
145
  it "should not respond to any other aggregates" do
164
- lambda { Friend.min(:age) }.should raise_error(ArgumentError)
165
- lambda { Friend.max(:age) }.should raise_error(ArgumentError)
166
- lambda { Friend.avg(:age) }.should raise_error(ArgumentError)
167
- lambda { Friend.sum(:age) }.should raise_error(ArgumentError)
146
+ lambda { Friend.min(:age) }.should raise_error(NotImplementedError)
147
+ lambda { Friend.max(:age) }.should raise_error(NotImplementedError)
148
+ lambda { Friend.avg(:age) }.should raise_error(NotImplementedError)
149
+ lambda { Friend.sum(:age) }.should raise_error(NotImplementedError)
168
150
  end
169
151
  end
170
152
  end