simple_record 1.5.4 → 1.5.6
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.
- data/lib/simple_record.rb +81 -66
- data/lib/simple_record/active_sdb.rb +2 -2
- data/lib/simple_record/results_array.rb +5 -1
- data/lib/simple_record/sharding.rb +273 -0
- data/test/my_sharded_model.rb +41 -0
- data/test/test_base.rb +10 -0
- data/test/test_shards.rb +127 -0
- metadata +8 -3
data/lib/simple_record.rb
CHANGED
@@ -41,24 +41,25 @@ require File.expand_path(File.dirname(__FILE__) + "/simple_record/rails2")
|
|
41
41
|
require File.expand_path(File.dirname(__FILE__) + "/simple_record/results_array")
|
42
42
|
require File.expand_path(File.dirname(__FILE__) + "/simple_record/stats")
|
43
43
|
require File.expand_path(File.dirname(__FILE__) + "/simple_record/translations")
|
44
|
+
require_relative 'simple_record/sharding'
|
44
45
|
|
45
46
|
|
46
47
|
module SimpleRecord
|
47
48
|
|
48
|
-
@@options
|
49
|
-
@@stats
|
50
|
-
@@logging
|
51
|
-
@@s3
|
49
|
+
@@options = {}
|
50
|
+
@@stats = SimpleRecord::Stats.new
|
51
|
+
@@logging = false
|
52
|
+
@@s3 = nil
|
52
53
|
@@auto_close_s3 = false
|
53
|
-
@@logger
|
54
|
-
@@logger.level
|
54
|
+
@@logger = Logger.new(STDOUT)
|
55
|
+
@@logger.level = Logger::INFO
|
55
56
|
|
56
57
|
class << self;
|
57
58
|
attr_accessor :aws_access_key, :aws_secret_key
|
58
59
|
|
59
60
|
# Deprecated
|
60
61
|
def enable_logging
|
61
|
-
@@logging
|
62
|
+
@@logging = true
|
62
63
|
@@logger.level = Logger::DEBUG
|
63
64
|
end
|
64
65
|
|
@@ -127,7 +128,7 @@ module SimpleRecord
|
|
127
128
|
if options[:connection_mode] == :per_thread
|
128
129
|
@@auto_close_s3 = true
|
129
130
|
# todo: should we init this only when needed?
|
130
|
-
@@s3
|
131
|
+
@@s3 = Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key, {:connection_mode=>:per_thread})
|
131
132
|
end
|
132
133
|
end
|
133
134
|
|
@@ -139,7 +140,7 @@ module SimpleRecord
|
|
139
140
|
@@s3.close_connection if @@auto_close_s3
|
140
141
|
end
|
141
142
|
|
142
|
-
|
143
|
+
# If you'd like to specify the s3 connection to use for LOBs, you can pass it in here.
|
143
144
|
# We recommend that this connection matches the type of connection you're using for SimpleDB,
|
144
145
|
# at least if you're using per_thread connection mode.
|
145
146
|
def s3=(s3)
|
@@ -172,6 +173,8 @@ module SimpleRecord
|
|
172
173
|
include SimpleRecord::Translations
|
173
174
|
# include SimpleRecord::Attributes
|
174
175
|
extend SimpleRecord::Attributes
|
176
|
+
extend SimpleRecord::Sharding::ClassMethods
|
177
|
+
include SimpleRecord::Sharding
|
175
178
|
include SimpleRecord::Callbacks
|
176
179
|
include SimpleRecord::Json
|
177
180
|
include SimpleRecord::Logging
|
@@ -197,13 +200,13 @@ module SimpleRecord
|
|
197
200
|
#we have to handle the virtuals.
|
198
201
|
Attributes.handle_virtuals(attrs)
|
199
202
|
|
200
|
-
@errors=SimpleRecord_errors.new
|
201
|
-
@dirty
|
203
|
+
@errors=SimpleRecord_errors.new if not (defined?(ActiveModel))
|
204
|
+
@dirty = {}
|
202
205
|
|
203
|
-
@attributes
|
206
|
+
@attributes = {} # sdb values
|
204
207
|
@attributes_rb = {} # ruby values
|
205
|
-
@lobs
|
206
|
-
@new_record
|
208
|
+
@lobs = {}
|
209
|
+
@new_record = true
|
207
210
|
|
208
211
|
end
|
209
212
|
|
@@ -232,7 +235,6 @@ module SimpleRecord
|
|
232
235
|
end
|
233
236
|
|
234
237
|
|
235
|
-
|
236
238
|
def defined_attributes_local
|
237
239
|
# todo: store this somewhere so it doesn't keep going through this
|
238
240
|
ret = self.class.defined_attributes
|
@@ -240,8 +242,6 @@ module SimpleRecord
|
|
240
242
|
end
|
241
243
|
|
242
244
|
|
243
|
-
|
244
|
-
|
245
245
|
class << self;
|
246
246
|
attr_accessor :domain_prefix
|
247
247
|
end
|
@@ -297,7 +297,7 @@ module SimpleRecord
|
|
297
297
|
|
298
298
|
def get_attribute_sdb(name)
|
299
299
|
name = name.to_sym
|
300
|
-
ret
|
300
|
+
ret = strip_array(@attributes[sdb_att_name(name)])
|
301
301
|
return ret
|
302
302
|
end
|
303
303
|
|
@@ -307,7 +307,7 @@ module SimpleRecord
|
|
307
307
|
end
|
308
308
|
|
309
309
|
def get_att_meta(name)
|
310
|
-
name_s
|
310
|
+
name_s = name.to_s
|
311
311
|
att_meta = defined_attributes_local[name.to_sym]
|
312
312
|
if att_meta.nil? && has_id_on_end(name_s)
|
313
313
|
att_meta = defined_attributes_local[name_s[0..-4].to_sym]
|
@@ -339,7 +339,7 @@ module SimpleRecord
|
|
339
339
|
|
340
340
|
def make_dirty(arg, value)
|
341
341
|
sdb_att_name = sdb_att_name(arg)
|
342
|
-
arg
|
342
|
+
arg = arg.to_s
|
343
343
|
|
344
344
|
# puts "Marking #{arg} dirty with #{value}" if SimpleRecord.logging?
|
345
345
|
if @dirty.include?(sdb_att_name)
|
@@ -422,7 +422,7 @@ module SimpleRecord
|
|
422
422
|
return true if @dirty.size == 0 # Nothing to save so skip it
|
423
423
|
end
|
424
424
|
is_create = self[:id].nil?
|
425
|
-
ok
|
425
|
+
ok = pre_save(options) # Validates and sets ID
|
426
426
|
if ok
|
427
427
|
begin
|
428
428
|
dirty = @dirty
|
@@ -432,13 +432,14 @@ module SimpleRecord
|
|
432
432
|
return true if @dirty.size == 0 # This should probably never happen because after pre_save, created/updated dates are changed
|
433
433
|
options[:dirty_atts] = @dirty
|
434
434
|
end
|
435
|
-
to_delete
|
435
|
+
to_delete = get_atts_to_delete
|
436
436
|
SimpleRecord.stats.saves += 1
|
437
|
-
|
438
|
-
|
437
|
+
|
438
|
+
if self.class.is_sharded?
|
439
|
+
options[:domain] = sharded_domain
|
440
|
+
end
|
441
|
+
|
439
442
|
if super(options)
|
440
|
-
# puts 'dirty super=' + @dirty.inspect
|
441
|
-
# puts 'SELF AFTER super=' + self.inspect
|
442
443
|
self.class.cache_results(self)
|
443
444
|
delete_niled(to_delete)
|
444
445
|
save_lobs(dirty)
|
@@ -564,7 +565,7 @@ module SimpleRecord
|
|
564
565
|
def pre_save(options)
|
565
566
|
|
566
567
|
is_create = self[:id].nil?
|
567
|
-
ok
|
568
|
+
ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
|
568
569
|
return false unless ok
|
569
570
|
|
570
571
|
validate()
|
@@ -594,6 +595,7 @@ module SimpleRecord
|
|
594
595
|
# Now translate all fields into SimpleDB friendly strings
|
595
596
|
# convert_all_atts_to_sdb()
|
596
597
|
end
|
598
|
+
prepare_for_update
|
597
599
|
ok
|
598
600
|
end
|
599
601
|
|
@@ -652,7 +654,7 @@ module SimpleRecord
|
|
652
654
|
def self.delete_all(*params)
|
653
655
|
# could make this quicker by just getting item_names and deleting attributes rather than creating objects
|
654
656
|
obs = self.find(params)
|
655
|
-
i
|
657
|
+
i = 0
|
656
658
|
obs.each do |a|
|
657
659
|
a.delete
|
658
660
|
i+=1
|
@@ -662,7 +664,7 @@ module SimpleRecord
|
|
662
664
|
|
663
665
|
def self.destroy_all(*params)
|
664
666
|
obs = self.find(params)
|
665
|
-
i
|
667
|
+
i = 0
|
666
668
|
obs.each do |a|
|
667
669
|
a.destroy
|
668
670
|
i+=1
|
@@ -672,7 +674,11 @@ module SimpleRecord
|
|
672
674
|
|
673
675
|
def delete()
|
674
676
|
# TODO: DELETE CLOBS, etc from s3
|
675
|
-
|
677
|
+
options = {}
|
678
|
+
if self.class.is_sharded?
|
679
|
+
options[:domain] = sharded_domain
|
680
|
+
end
|
681
|
+
super(options)
|
676
682
|
end
|
677
683
|
|
678
684
|
def destroy
|
@@ -685,8 +691,8 @@ module SimpleRecord
|
|
685
691
|
def get_attribute(name)
|
686
692
|
# puts "GET #{arg}"
|
687
693
|
# Check if this arg is already converted
|
688
|
-
name_s
|
689
|
-
name
|
694
|
+
name_s = name.to_s
|
695
|
+
name = name.to_sym
|
690
696
|
att_meta = get_att_meta(name)
|
691
697
|
# puts "att_meta for #{name}: " + att_meta.inspect
|
692
698
|
if att_meta && att_meta.type == :clob
|
@@ -702,7 +708,7 @@ module SimpleRecord
|
|
702
708
|
# get it from s3
|
703
709
|
unless new_record?
|
704
710
|
begin
|
705
|
-
ret
|
711
|
+
ret = s3_bucket.get(s3_lob_id(name))
|
706
712
|
# puts 'got from s3 ' + ret.inspect
|
707
713
|
SimpleRecord.stats.s3_gets += 1
|
708
714
|
rescue Aws::AwsError => ex
|
@@ -725,8 +731,8 @@ module SimpleRecord
|
|
725
731
|
ret = @attributes_rb[name_s] # instance_variable_get(instance_var)
|
726
732
|
return ret unless ret.nil?
|
727
733
|
return nil if ret.is_a? RemoteNil
|
728
|
-
ret
|
729
|
-
ret
|
734
|
+
ret = get_attribute_sdb(name)
|
735
|
+
ret = sdb_to_ruby(name, ret)
|
730
736
|
@attributes_rb[name_s] = ret
|
731
737
|
return ret
|
732
738
|
end
|
@@ -736,22 +742,22 @@ module SimpleRecord
|
|
736
742
|
def set(name, value, dirtify=true)
|
737
743
|
# puts "SET #{name}=#{value.inspect}" if SimpleRecord.logging?
|
738
744
|
# puts "self=" + self.inspect
|
739
|
-
attname
|
740
|
-
name
|
741
|
-
att_meta
|
745
|
+
attname = name.to_s # default attname
|
746
|
+
name = name.to_sym
|
747
|
+
att_meta = get_att_meta(name)
|
742
748
|
store_rb_val = false
|
743
749
|
if att_meta.nil?
|
744
750
|
# check if it ends with id and see if att_meta is there
|
745
751
|
ends_with = name.to_s[-3, 3]
|
746
752
|
if ends_with == "_id"
|
747
753
|
# puts 'ends with id'
|
748
|
-
n2
|
754
|
+
n2 = name.to_s[0, name.length-3]
|
749
755
|
# puts 'n2=' + n2
|
750
756
|
att_meta = defined_attributes_local[n2.to_sym]
|
751
757
|
# puts 'defined_attributes_local=' + defined_attributes_local.inspect
|
752
|
-
attname
|
758
|
+
attname = name.to_s
|
753
759
|
attvalue = value
|
754
|
-
name
|
760
|
+
name = n2.to_sym
|
755
761
|
end
|
756
762
|
return if att_meta.nil?
|
757
763
|
else
|
@@ -761,8 +767,8 @@ module SimpleRecord
|
|
761
767
|
att_name = name.to_s
|
762
768
|
attvalue = value
|
763
769
|
else
|
764
|
-
attname
|
765
|
-
attvalue
|
770
|
+
attname = name.to_s + '_id'
|
771
|
+
attvalue = value.nil? ? nil : value.id
|
766
772
|
store_rb_val = true
|
767
773
|
end
|
768
774
|
elsif att_meta.type == :clob
|
@@ -770,7 +776,7 @@ module SimpleRecord
|
|
770
776
|
@lobs[name] = value
|
771
777
|
return
|
772
778
|
else
|
773
|
-
attname
|
779
|
+
attname = name.to_s
|
774
780
|
attvalue = att_meta.init_value(value)
|
775
781
|
# attvalue = value
|
776
782
|
#puts 'converted ' + value.inspect + ' to ' + attvalue.inspect
|
@@ -779,7 +785,7 @@ module SimpleRecord
|
|
779
785
|
attvalue = strip_array(attvalue)
|
780
786
|
make_dirty(name, attvalue) if dirtify
|
781
787
|
# puts "ARG=#{attname.to_s} setting to #{attvalue}"
|
782
|
-
sdb_val
|
788
|
+
sdb_val = ruby_to_sdb(name, attvalue)
|
783
789
|
# puts "sdb_val=" + sdb_val.to_s
|
784
790
|
@attributes[attname] = sdb_val
|
785
791
|
# attvalue = wrap_if_required(name, attvalue, sdb_val)
|
@@ -834,7 +840,7 @@ module SimpleRecord
|
|
834
840
|
if $&
|
835
841
|
before=$`
|
836
842
|
middle=$&
|
837
|
-
after=$'
|
843
|
+
after =$'
|
838
844
|
|
839
845
|
before =~ /'$/ #is there already a quote immediately before the match?
|
840
846
|
unless $&
|
@@ -868,34 +874,43 @@ module SimpleRecord
|
|
868
874
|
# :consistent_read => true/false -- as per http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572
|
869
875
|
def self.find(*params)
|
870
876
|
#puts 'params=' + params.inspect
|
871
|
-
q_type = :all
|
872
|
-
select_attributes=[]
|
873
877
|
|
878
|
+
q_type = :all
|
879
|
+
select_attributes=[]
|
874
880
|
if params.size > 0
|
875
881
|
q_type = params[0]
|
876
882
|
end
|
883
|
+
options = {}
|
884
|
+
if params.size > 1
|
885
|
+
options = params[1]
|
886
|
+
end
|
887
|
+
|
888
|
+
if !options[:shard_find] && is_sharded?
|
889
|
+
# then break off and get results across all shards
|
890
|
+
return find_sharded(*params)
|
891
|
+
end
|
877
892
|
|
878
893
|
# Pad and Offset number attributes
|
879
|
-
options = {}
|
880
894
|
params_dup = params.dup
|
881
895
|
if params.size > 1
|
882
896
|
options = params[1]
|
883
897
|
#puts 'options=' + options.inspect
|
884
898
|
#puts 'after collect=' + options.inspect
|
885
899
|
convert_condition_params(options)
|
886
|
-
per_token
|
900
|
+
per_token = options[:per_token]
|
887
901
|
consistent_read = options[:consistent_read]
|
888
902
|
if per_token || consistent_read then
|
889
|
-
op_dup
|
890
|
-
op_dup[:limit]
|
903
|
+
op_dup = options.dup
|
904
|
+
op_dup[:limit] = per_token # simpledb uses Limit as a paging thing, not what is normal
|
891
905
|
op_dup[:consistent_read] = consistent_read
|
892
|
-
params_dup[1]
|
906
|
+
params_dup[1] = op_dup
|
893
907
|
end
|
894
908
|
end
|
895
909
|
# puts 'params2=' + params.inspect
|
896
910
|
|
897
|
-
|
911
|
+
ret = q_type == :all ? [] : nil
|
898
912
|
begin
|
913
|
+
|
899
914
|
results=find_with_metadata(*params_dup)
|
900
915
|
# puts "RESULT=" + results.inspect
|
901
916
|
write_usage(:select, domain, q_type, options, results)
|
@@ -951,14 +966,14 @@ module SimpleRecord
|
|
951
966
|
def self.paginate(options={})
|
952
967
|
# options = args.pop
|
953
968
|
# puts 'paginate options=' + options.inspect if SimpleRecord.logging?
|
954
|
-
page
|
955
|
-
per_page
|
969
|
+
page = options[:page] || 1
|
970
|
+
per_page = options[:per_page] || 50
|
956
971
|
# total = options[:total_entries].to_i
|
957
|
-
options[:page]
|
972
|
+
options[:page] = page.to_i # makes sure it's to_i
|
958
973
|
options[:per_page] = per_page.to_i
|
959
|
-
options[:limit]
|
974
|
+
options[:limit] = options[:page] * options[:per_page]
|
960
975
|
# puts 'paging options=' + options.inspect
|
961
|
-
fr
|
976
|
+
fr = find(:all, options)
|
962
977
|
return fr
|
963
978
|
|
964
979
|
end
|
@@ -982,15 +997,15 @@ module SimpleRecord
|
|
982
997
|
# todo: cache each result
|
983
998
|
results.each do |item|
|
984
999
|
class_name = item.class.name
|
985
|
-
id
|
986
|
-
cache_key
|
1000
|
+
id = item.id
|
1001
|
+
cache_key = self.cache_key(class_name, id)
|
987
1002
|
#puts 'caching result at ' + cache_key + ': ' + results.inspect
|
988
1003
|
cache_store.write(cache_key, item, :expires_in =>30)
|
989
1004
|
end
|
990
1005
|
else
|
991
1006
|
class_name = results.class.name
|
992
|
-
id
|
993
|
-
cache_key
|
1007
|
+
id = results.id
|
1008
|
+
cache_key = self.cache_key(class_name, id)
|
994
1009
|
#puts 'caching result at ' + cache_key + ': ' + results.inspect
|
995
1010
|
cache_store.write(cache_key, results, :expires_in =>30)
|
996
1011
|
end
|
@@ -1051,8 +1066,8 @@ module SimpleRecord
|
|
1051
1066
|
|
1052
1067
|
class Activerecordtosdb_subrecord_array
|
1053
1068
|
def initialize(subname, referencename, referencevalue)
|
1054
|
-
@subname=subname.classify
|
1055
|
-
@referencename=referencename.tableize.singularize + "_id"
|
1069
|
+
@subname =subname.classify
|
1070
|
+
@referencename =referencename.tableize.singularize + "_id"
|
1056
1071
|
@referencevalue=referencevalue
|
1057
1072
|
end
|
1058
1073
|
|
@@ -1104,7 +1119,7 @@ module SimpleRecord
|
|
1104
1119
|
|
1105
1120
|
def create(*params)
|
1106
1121
|
params[0][@referencename]=@referencevalue
|
1107
|
-
record
|
1122
|
+
record = eval(@subname).new(*params)
|
1108
1123
|
record.save
|
1109
1124
|
end
|
1110
1125
|
|
@@ -966,9 +966,9 @@ module SimpleRecord
|
|
966
966
|
# sandy.reload
|
967
967
|
# puts sandy.inspect #=> #<Client:0xb7761d28 @attributes={}, @new_record=false>
|
968
968
|
#
|
969
|
-
def delete
|
969
|
+
def delete(options={})
|
970
970
|
raise_on_id_absence
|
971
|
-
connection.delete_attributes(domain, id)
|
971
|
+
connection.delete_attributes(options[:domain] || domain, id)
|
972
972
|
end
|
973
973
|
|
974
974
|
# Item ID
|
@@ -5,7 +5,7 @@ module SimpleRecord
|
|
5
5
|
class ResultsArray
|
6
6
|
include Enumerable
|
7
7
|
|
8
|
-
attr_reader :next_token, :clz, :params, :items, :
|
8
|
+
attr_reader :next_token, :clz, :params, :items, :index, :box_usage, :request_id
|
9
9
|
|
10
10
|
|
11
11
|
def initialize(clz=nil, params=[], results=nil, next_token=nil)
|
@@ -27,6 +27,7 @@ module SimpleRecord
|
|
27
27
|
load_to(@options[:per_page] * @options[:page])
|
28
28
|
@start_at = @options[:per_page] * (@options[:page] - 1)
|
29
29
|
end
|
30
|
+
@index = 0
|
30
31
|
# puts 'RESULTS_ARRAY=' + self.inspect
|
31
32
|
end
|
32
33
|
|
@@ -96,6 +97,7 @@ module SimpleRecord
|
|
96
97
|
params_for_count[1] = params_for_count[1].dup # for deep clone
|
97
98
|
params_for_count[1].delete(:limit)
|
98
99
|
params_for_count[1].delete(:per_token)
|
100
|
+
params_for_count[1][:called_by] = :results_array
|
99
101
|
|
100
102
|
# puts '@params2=' + @params.inspect
|
101
103
|
# puts 'params_for_count=' + params_for_count.inspect
|
@@ -127,6 +129,7 @@ module SimpleRecord
|
|
127
129
|
# puts "i=" + i.to_s
|
128
130
|
yield v
|
129
131
|
i += 1
|
132
|
+
@index += 1
|
130
133
|
if !limit.nil? && i >= limit
|
131
134
|
return
|
132
135
|
end
|
@@ -194,6 +197,7 @@ module SimpleRecord
|
|
194
197
|
def load_next_token_set
|
195
198
|
options = @params[1]
|
196
199
|
options[:next_token] = @next_token
|
200
|
+
options[:called_by] = :results_array
|
197
201
|
res = @clz.find(*@params)
|
198
202
|
@currentset_items = res.items # get the real items array from the ResultsArray
|
199
203
|
@currentset_items.each do |item|
|
@@ -0,0 +1,273 @@
|
|
1
|
+
module SimpleRecord
|
2
|
+
|
3
|
+
module Sharding
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
# base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def shard(options=nil)
|
12
|
+
@sharding_options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def sharding_options
|
16
|
+
@sharding_options
|
17
|
+
end
|
18
|
+
|
19
|
+
def is_sharded?
|
20
|
+
@sharding_options
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_sharded(*params)
|
24
|
+
puts 'find_sharded ' + params.inspect
|
25
|
+
|
26
|
+
options = params.size > 1 ? params[1] : {}
|
27
|
+
|
28
|
+
domains = sharded_domains
|
29
|
+
puts "sharded_domains=" + domains.inspect
|
30
|
+
|
31
|
+
single = false
|
32
|
+
case params.first
|
33
|
+
when nil then
|
34
|
+
raise "Invalid parameters passed to find: nil."
|
35
|
+
when :all, :first, :count
|
36
|
+
# nada
|
37
|
+
else # single id
|
38
|
+
unless params.first.is_a?(Array)
|
39
|
+
single = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
results = ShardedResults.new(params)
|
44
|
+
domains.each do |d|
|
45
|
+
p2 = params.dup
|
46
|
+
op2 = options.dup
|
47
|
+
op2[:from] = d
|
48
|
+
op2[:shard_find] = true
|
49
|
+
p2[1] = op2
|
50
|
+
rs = find(*p2)
|
51
|
+
if params.first == :first || single
|
52
|
+
return rs if rs
|
53
|
+
else
|
54
|
+
results.add_results rs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
puts 'results=' + results.inspect
|
58
|
+
if params.first == :first || single
|
59
|
+
# Then we found nothing by this point so return nil
|
60
|
+
return nil
|
61
|
+
elsif params.first == :count
|
62
|
+
return results.sum_count
|
63
|
+
end
|
64
|
+
results
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def shards
|
69
|
+
send(sharding_options[:shards])
|
70
|
+
end
|
71
|
+
|
72
|
+
def sharded_domains
|
73
|
+
sharded_domains = []
|
74
|
+
shard_names = shards
|
75
|
+
shard_names.each do |s|
|
76
|
+
sharded_domains << "#{domain}_#{s}"
|
77
|
+
end
|
78
|
+
sharded_domains
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def sharded_domain
|
84
|
+
# puts 'getting sharded_domain'
|
85
|
+
options = self.class.sharding_options
|
86
|
+
# val = self.send(options[:on])
|
87
|
+
# puts "val=" + val.inspect
|
88
|
+
# shards = options[:shards] # is user passed in static array of shards
|
89
|
+
# if options[:shards].is_a?(Symbol)
|
90
|
+
# shards = self.send(shards)
|
91
|
+
# end
|
92
|
+
sharded_domain = "#{domain}_#{self.send(options[:map])}"
|
93
|
+
puts "sharded_domain=" + sharded_domain.inspect
|
94
|
+
sharded_domain
|
95
|
+
end
|
96
|
+
|
97
|
+
class ShardedResults
|
98
|
+
include Enumerable
|
99
|
+
|
100
|
+
def initialize(params)
|
101
|
+
@params = params
|
102
|
+
@options = params.size > 1 ? params[1] : {}
|
103
|
+
@results_arrays = []
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_results(rs)
|
107
|
+
# puts 'adding results=' + rs.inspect
|
108
|
+
@results_arrays << rs
|
109
|
+
end
|
110
|
+
|
111
|
+
# only used for count queries
|
112
|
+
def sum_count
|
113
|
+
x = 0
|
114
|
+
@results_arrays.each do |rs|
|
115
|
+
x += rs if rs
|
116
|
+
end
|
117
|
+
x
|
118
|
+
end
|
119
|
+
|
120
|
+
def <<(val)
|
121
|
+
raise "Not supported."
|
122
|
+
end
|
123
|
+
|
124
|
+
def element_at(index)
|
125
|
+
@results_arrays.each do |rs|
|
126
|
+
if rs.size > index
|
127
|
+
return rs[index]
|
128
|
+
end
|
129
|
+
index -= rs.size
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def [](*i)
|
134
|
+
if i.size == 1
|
135
|
+
# puts '[] i=' + i.to_s
|
136
|
+
index = i[0]
|
137
|
+
return element_at(index)
|
138
|
+
else
|
139
|
+
offset = i[0]
|
140
|
+
rows = i[1]
|
141
|
+
ret = []
|
142
|
+
x = offset
|
143
|
+
while x < (offset+rows)
|
144
|
+
ret << element_at(x)
|
145
|
+
x+=1
|
146
|
+
end
|
147
|
+
ret
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def first
|
152
|
+
@results_arrays.first.first
|
153
|
+
end
|
154
|
+
|
155
|
+
def last
|
156
|
+
@results_arrays.last.last
|
157
|
+
end
|
158
|
+
|
159
|
+
def empty?
|
160
|
+
@results_arrays.each do |rs|
|
161
|
+
return false if !rs.empty?
|
162
|
+
end
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def include?(obj)
|
167
|
+
@results_arrays.each do |rs|
|
168
|
+
x = rs.include?(obj)
|
169
|
+
return true if x
|
170
|
+
end
|
171
|
+
false
|
172
|
+
end
|
173
|
+
|
174
|
+
def size
|
175
|
+
return @size if @size
|
176
|
+
s = 0
|
177
|
+
@results_arrays.each do |rs|
|
178
|
+
# puts 'rs=' + rs.inspect
|
179
|
+
# puts 'rs.size=' + rs.size.inspect
|
180
|
+
s += rs.size
|
181
|
+
end
|
182
|
+
@size = s
|
183
|
+
s
|
184
|
+
end
|
185
|
+
|
186
|
+
def length
|
187
|
+
return size
|
188
|
+
end
|
189
|
+
|
190
|
+
def each(&blk)
|
191
|
+
i = 0
|
192
|
+
@results_arrays.each do |rs|
|
193
|
+
rs.each(&blk)
|
194
|
+
i+=1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# for will_paginate support
|
199
|
+
def total_pages
|
200
|
+
# puts 'total_pages'
|
201
|
+
# puts @params[1][:per_page].to_s
|
202
|
+
return 1 if @params[1][:per_page].nil?
|
203
|
+
ret = (size / @params[1][:per_page].to_f).ceil
|
204
|
+
#puts 'ret=' + ret.to_s
|
205
|
+
ret
|
206
|
+
end
|
207
|
+
|
208
|
+
def current_page
|
209
|
+
return query_options[:page] || 1
|
210
|
+
end
|
211
|
+
|
212
|
+
def query_options
|
213
|
+
return @options
|
214
|
+
end
|
215
|
+
|
216
|
+
def total_entries
|
217
|
+
return size
|
218
|
+
end
|
219
|
+
|
220
|
+
# Helper method that is true when someone tries to fetch a page with a
|
221
|
+
# larger number than the last page. Can be used in combination with flashes
|
222
|
+
# and redirecting.
|
223
|
+
def out_of_bounds?
|
224
|
+
current_page > total_pages
|
225
|
+
end
|
226
|
+
|
227
|
+
# Current offset of the paginated collection. If we're on the first page,
|
228
|
+
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
|
229
|
+
# the offset is 30. This property is useful if you want to render ordinals
|
230
|
+
# side by side with records in the view: simply start with offset + 1.
|
231
|
+
def offset
|
232
|
+
(current_page - 1) * per_page
|
233
|
+
end
|
234
|
+
|
235
|
+
# current_page - 1 or nil if there is no previous page
|
236
|
+
def previous_page
|
237
|
+
current_page > 1 ? (current_page - 1) : nil
|
238
|
+
end
|
239
|
+
|
240
|
+
# current_page + 1 or nil if there is no next page
|
241
|
+
def next_page
|
242
|
+
current_page < total_pages ? (current_page + 1) : nil
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
def delete(item)
|
247
|
+
raise "Not supported"
|
248
|
+
end
|
249
|
+
|
250
|
+
def delete_at(index)
|
251
|
+
raise "Not supported"
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
# Some hashing algorithms
|
257
|
+
module Hashing
|
258
|
+
def self.sdbm_hash(str, len=str.length)
|
259
|
+
# puts 'sdbm_hash ' + str.inspect
|
260
|
+
hash = 0
|
261
|
+
len.times { |i|
|
262
|
+
c = str[i]
|
263
|
+
# puts "c=" + c.class.name + "--" + c.inspect + " -- " + c.ord.inspect
|
264
|
+
c = c.ord
|
265
|
+
hash = c + (hash << 6) + (hash << 16) - hash
|
266
|
+
}
|
267
|
+
# puts "hash=" + hash.inspect
|
268
|
+
return hash
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/simple_record")
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/simple_record")
|
4
|
+
|
5
|
+
class MyShardedModel < SimpleRecord::Base
|
6
|
+
|
7
|
+
shard :shards=>:my_shards, :map=>:my_mapping_function
|
8
|
+
|
9
|
+
has_strings :name
|
10
|
+
|
11
|
+
def self.my_shards
|
12
|
+
Array(0...4)
|
13
|
+
end
|
14
|
+
|
15
|
+
def my_mapping_function
|
16
|
+
shard_num = SimpleRecord::Sharding::Hashing.sdbm_hash(self.id) % 4
|
17
|
+
puts "shard_num=" + shard_num.inspect
|
18
|
+
shard_num
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
class MyShardedByFieldModel < SimpleRecord::Base
|
27
|
+
|
28
|
+
shard :shards=>:my_shards, :map=>:my_mapping_function
|
29
|
+
|
30
|
+
has_strings :name, :state
|
31
|
+
|
32
|
+
def self.my_shards
|
33
|
+
['AL', 'CA', 'FL', 'NY']
|
34
|
+
end
|
35
|
+
|
36
|
+
def my_mapping_function
|
37
|
+
state
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
data/test/test_base.rb
CHANGED
@@ -19,6 +19,16 @@ class TestBase < Test::Unit::TestCase
|
|
19
19
|
SimpleRecord.close_connection
|
20
20
|
end
|
21
21
|
|
22
|
+
def delete_all(clz)
|
23
|
+
puts 'delete_all ' + clz.name
|
24
|
+
obs = clz.find(:all)
|
25
|
+
obs.each do |o|
|
26
|
+
o.delete
|
27
|
+
end
|
28
|
+
# @@sdb.select("select * from #{domain}") do |o|
|
29
|
+
# o.delete
|
30
|
+
# end
|
31
|
+
end
|
22
32
|
|
23
33
|
def reset_connection
|
24
34
|
puts 'reset_connection'
|
data/test/test_shards.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.join(File.dirname(__FILE__), "/../lib/simple_record")
|
3
|
+
require File.join(File.dirname(__FILE__), "./test_helpers")
|
4
|
+
require File.join(File.dirname(__FILE__), "./test_base")
|
5
|
+
require "yaml"
|
6
|
+
require 'aws'
|
7
|
+
require_relative 'my_sharded_model'
|
8
|
+
|
9
|
+
# Tests for SimpleRecord
|
10
|
+
#
|
11
|
+
class TestShards < TestBase
|
12
|
+
|
13
|
+
def setup
|
14
|
+
super
|
15
|
+
delete_all MyShardedModel
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
super
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
# We'll want to shard based on ID's, user decides how many shards and some mapping function will
|
24
|
+
# be used to select the shard.
|
25
|
+
def test_id_sharding
|
26
|
+
|
27
|
+
mm = MyShardedModel.new(:name=>"single")
|
28
|
+
mm.save
|
29
|
+
sleep 1
|
30
|
+
puts 'finding by id'
|
31
|
+
mm2 = MyShardedModel.find(mm.id)
|
32
|
+
p mm2
|
33
|
+
assert_equal mm.id, mm2.id
|
34
|
+
puts 'deleting'
|
35
|
+
mm2.delete
|
36
|
+
sleep 1
|
37
|
+
mm3 = MyShardedModel.find(mm.id)
|
38
|
+
assert_nil mm3
|
39
|
+
|
40
|
+
puts "saving 20 now"
|
41
|
+
saved = []
|
42
|
+
20.times do |i|
|
43
|
+
mm = MyShardedModel.new(:name=>"name #{i}")
|
44
|
+
mm.save
|
45
|
+
saved << mm
|
46
|
+
end
|
47
|
+
|
48
|
+
# todo: assert that we're actually sharding
|
49
|
+
|
50
|
+
puts "finding them all"
|
51
|
+
found = []
|
52
|
+
rs = MyShardedModel.find(:all)
|
53
|
+
rs.each do |m|
|
54
|
+
p m
|
55
|
+
found << m
|
56
|
+
end
|
57
|
+
saved.each do |so|
|
58
|
+
assert(found.find { |m1| m1.id == so.id })
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "deleting all of them"
|
62
|
+
found.each do |fo|
|
63
|
+
fo.delete
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "Now ensure that all are deleted"
|
67
|
+
rs = MyShardedModel.find(:all)
|
68
|
+
assert rs.size == 0
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_field_sharding
|
73
|
+
|
74
|
+
states = MyShardedByFieldModel.shards
|
75
|
+
puts "states=" + states.inspect
|
76
|
+
|
77
|
+
mm = MyShardedByFieldModel.new(:name=>"single", :state=>"CA")
|
78
|
+
mm.save
|
79
|
+
sleep 1
|
80
|
+
puts 'finding by id'
|
81
|
+
mm2 = MyShardedByFieldModel.find(mm.id)
|
82
|
+
p mm2
|
83
|
+
assert_equal mm.id, mm2.id
|
84
|
+
puts 'deleting'
|
85
|
+
mm2.delete
|
86
|
+
sleep 1
|
87
|
+
mm3 = MyShardedByFieldModel.find(mm.id)
|
88
|
+
assert_nil mm3
|
89
|
+
|
90
|
+
puts "saving 20 now"
|
91
|
+
saved = []
|
92
|
+
20.times do |i|
|
93
|
+
mm = MyShardedByFieldModel.new(:name=>"name #{i}", :state=>states[i % states.size])
|
94
|
+
mm.save
|
95
|
+
p mm
|
96
|
+
saved << mm
|
97
|
+
end
|
98
|
+
|
99
|
+
# todo: assert that we're actually sharding
|
100
|
+
|
101
|
+
puts "finding them all"
|
102
|
+
found = []
|
103
|
+
rs = MyShardedByFieldModel.find(:all)
|
104
|
+
rs.each do |m|
|
105
|
+
p m
|
106
|
+
found << m
|
107
|
+
end
|
108
|
+
saved.each do |so|
|
109
|
+
assert(found.find { |m1| m1.id == so.id })
|
110
|
+
end
|
111
|
+
|
112
|
+
puts "deleting all of them"
|
113
|
+
found.each do |fo|
|
114
|
+
fo.delete
|
115
|
+
end
|
116
|
+
sleep 1
|
117
|
+
|
118
|
+
puts "Now ensure that all are deleted"
|
119
|
+
rs = MyShardedByFieldModel.find(:all)
|
120
|
+
assert rs.size == 0
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_time_sharding
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 5
|
8
|
-
-
|
9
|
-
version: 1.5.
|
8
|
+
- 6
|
9
|
+
version: 1.5.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Travis Reeder
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-12-01 00:00:00 -08:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -53,6 +53,7 @@ files:
|
|
53
53
|
- lib/simple_record/password.rb
|
54
54
|
- lib/simple_record/rails2.rb
|
55
55
|
- lib/simple_record/results_array.rb
|
56
|
+
- lib/simple_record/sharding.rb
|
56
57
|
- lib/simple_record/stats.rb
|
57
58
|
- lib/simple_record/translations.rb
|
58
59
|
- README.markdown
|
@@ -61,6 +62,7 @@ files:
|
|
61
62
|
- test/my_base_model.rb
|
62
63
|
- test/my_child_model.rb
|
63
64
|
- test/my_model.rb
|
65
|
+
- test/my_sharded_model.rb
|
64
66
|
- test/paging_array_test.rb
|
65
67
|
- test/simple_record_test.rb
|
66
68
|
- test/temp.rb
|
@@ -76,6 +78,7 @@ files:
|
|
76
78
|
- test/test_pagination.rb
|
77
79
|
- test/test_rails3.rb
|
78
80
|
- test/test_results_array.rb
|
81
|
+
- test/test_shards.rb
|
79
82
|
- test/test_usage.rb
|
80
83
|
has_rdoc: true
|
81
84
|
homepage: http://github.com/appoxy/simple_record/
|
@@ -115,6 +118,7 @@ test_files:
|
|
115
118
|
- test/my_base_model.rb
|
116
119
|
- test/my_child_model.rb
|
117
120
|
- test/my_model.rb
|
121
|
+
- test/my_sharded_model.rb
|
118
122
|
- test/paging_array_test.rb
|
119
123
|
- test/simple_record_test.rb
|
120
124
|
- test/temp.rb
|
@@ -130,4 +134,5 @@ test_files:
|
|
130
134
|
- test/test_pagination.rb
|
131
135
|
- test/test_rails3.rb
|
132
136
|
- test/test_results_array.rb
|
137
|
+
- test/test_shards.rb
|
133
138
|
- test/test_usage.rb
|