dm-adapter-simpledb 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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