sequel 3.29.0 → 3.30.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.
Files changed (106) hide show
  1. data/CHANGELOG +35 -3
  2. data/Rakefile +2 -1
  3. data/doc/association_basics.rdoc +11 -0
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.30.0.txt +135 -0
  6. data/doc/testing.rdoc +17 -3
  7. data/lib/sequel/adapters/amalgalite.rb +2 -2
  8. data/lib/sequel/adapters/do/mysql.rb +5 -2
  9. data/lib/sequel/adapters/ibmdb.rb +2 -2
  10. data/lib/sequel/adapters/jdbc.rb +126 -43
  11. data/lib/sequel/adapters/jdbc/as400.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/db2.rb +2 -1
  13. data/lib/sequel/adapters/jdbc/derby.rb +44 -19
  14. data/lib/sequel/adapters/jdbc/h2.rb +32 -19
  15. data/lib/sequel/adapters/jdbc/hsqldb.rb +21 -17
  16. data/lib/sequel/adapters/jdbc/jtds.rb +9 -4
  17. data/lib/sequel/adapters/jdbc/mssql.rb +3 -1
  18. data/lib/sequel/adapters/jdbc/mysql.rb +2 -1
  19. data/lib/sequel/adapters/jdbc/oracle.rb +21 -7
  20. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -2
  21. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -1
  22. data/lib/sequel/adapters/jdbc/sqlserver.rb +48 -18
  23. data/lib/sequel/adapters/mock.rb +2 -1
  24. data/lib/sequel/adapters/mysql.rb +4 -2
  25. data/lib/sequel/adapters/mysql2.rb +2 -2
  26. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  27. data/lib/sequel/adapters/openbase.rb +1 -1
  28. data/lib/sequel/adapters/oracle.rb +6 -6
  29. data/lib/sequel/adapters/postgres.rb +25 -12
  30. data/lib/sequel/adapters/shared/access.rb +14 -6
  31. data/lib/sequel/adapters/shared/db2.rb +36 -13
  32. data/lib/sequel/adapters/shared/firebird.rb +12 -5
  33. data/lib/sequel/adapters/shared/informix.rb +11 -3
  34. data/lib/sequel/adapters/shared/mssql.rb +94 -47
  35. data/lib/sequel/adapters/shared/mysql.rb +107 -49
  36. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  37. data/lib/sequel/adapters/shared/oracle.rb +54 -27
  38. data/lib/sequel/adapters/shared/postgres.rb +65 -26
  39. data/lib/sequel/adapters/shared/progress.rb +4 -1
  40. data/lib/sequel/adapters/shared/sqlite.rb +36 -20
  41. data/lib/sequel/adapters/sqlite.rb +2 -3
  42. data/lib/sequel/adapters/swift/mysql.rb +3 -2
  43. data/lib/sequel/adapters/swift/sqlite.rb +2 -2
  44. data/lib/sequel/adapters/tinytds.rb +14 -8
  45. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -4
  46. data/lib/sequel/database/misc.rb +6 -2
  47. data/lib/sequel/dataset/graph.rb +33 -7
  48. data/lib/sequel/dataset/prepared_statements.rb +19 -5
  49. data/lib/sequel/dataset/sql.rb +611 -201
  50. data/lib/sequel/model/associations.rb +12 -5
  51. data/lib/sequel/model/base.rb +20 -5
  52. data/lib/sequel/plugins/sharding.rb +9 -29
  53. data/lib/sequel/sql.rb +2 -1
  54. data/lib/sequel/timezones.rb +14 -4
  55. data/lib/sequel/version.rb +1 -1
  56. data/spec/adapters/mysql_spec.rb +10 -0
  57. data/spec/adapters/oracle_spec.rb +1 -1
  58. data/spec/core/core_sql_spec.rb +3 -1
  59. data/spec/core/database_spec.rb +42 -0
  60. data/spec/core/dataset_spec.rb +10 -3
  61. data/spec/core/mock_adapter_spec.rb +4 -0
  62. data/spec/core/object_graph_spec.rb +38 -0
  63. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  64. data/spec/extensions/association_dependencies_spec.rb +2 -12
  65. data/spec/extensions/association_pks_spec.rb +35 -39
  66. data/spec/extensions/caching_spec.rb +23 -50
  67. data/spec/extensions/class_table_inheritance_spec.rb +30 -82
  68. data/spec/extensions/composition_spec.rb +18 -13
  69. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  70. data/spec/extensions/identity_map_spec.rb +33 -103
  71. data/spec/extensions/instance_filters_spec.rb +10 -21
  72. data/spec/extensions/instance_hooks_spec.rb +6 -24
  73. data/spec/extensions/json_serializer_spec.rb +4 -5
  74. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  75. data/spec/extensions/list_spec.rb +17 -39
  76. data/spec/extensions/many_through_many_spec.rb +135 -277
  77. data/spec/extensions/migration_spec.rb +18 -15
  78. data/spec/extensions/named_timezones_spec.rb +1 -1
  79. data/spec/extensions/nested_attributes_spec.rb +97 -92
  80. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  81. data/spec/extensions/prepared_statements_associations_spec.rb +22 -37
  82. data/spec/extensions/prepared_statements_safe_spec.rb +9 -27
  83. data/spec/extensions/prepared_statements_spec.rb +11 -30
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +6 -13
  85. data/spec/extensions/pretty_table_spec.rb +1 -6
  86. data/spec/extensions/rcte_tree_spec.rb +41 -43
  87. data/spec/extensions/schema_dumper_spec.rb +3 -6
  88. data/spec/extensions/serialization_spec.rb +20 -32
  89. data/spec/extensions/sharding_spec.rb +66 -140
  90. data/spec/extensions/single_table_inheritance_spec.rb +14 -36
  91. data/spec/extensions/spec_helper.rb +10 -64
  92. data/spec/extensions/sql_expr_spec.rb +20 -60
  93. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  94. data/spec/extensions/timestamps_spec.rb +6 -6
  95. data/spec/extensions/to_dot_spec.rb +1 -2
  96. data/spec/extensions/touch_spec.rb +13 -14
  97. data/spec/extensions/tree_spec.rb +11 -26
  98. data/spec/extensions/update_primary_key_spec.rb +30 -24
  99. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  100. data/spec/extensions/validation_helpers_spec.rb +16 -35
  101. data/spec/integration/dataset_test.rb +16 -4
  102. data/spec/integration/prepared_statement_test.rb +4 -2
  103. data/spec/model/eager_loading_spec.rb +16 -0
  104. data/spec/model/model_spec.rb +15 -1
  105. data/spec/model/record_spec.rb +60 -0
  106. metadata +23 -40
@@ -43,19 +43,17 @@ describe "Sequel::Schema::Generator dump methods" do
43
43
  index [:b, :c], :unique=>true
44
44
  end
45
45
 
46
- t = <<END_CODE
46
+ g.dump_indexes(:add_index=>:t).should == (<<END_CODE).strip
47
47
  add_index :t, [:a]
48
48
  add_index :t, [:c, :e], :name=>:blah
49
49
  add_index :t, [:b, :c], :unique=>true
50
50
  END_CODE
51
- g.dump_indexes(:add_index=>:t).should == t.strip
52
51
 
53
- t = <<END_CODE
52
+ g.dump_indexes(:drop_index=>:t).should == (<<END_CODE).strip
54
53
  drop_index :t, [:a]
55
54
  drop_index :t, [:c, :e], :name=>:blah
56
55
  drop_index :t, [:b, :c], :unique=>true
57
56
  END_CODE
58
- g.dump_indexes(:drop_index=>:t).should == t.strip
59
57
  end
60
58
 
61
59
  it "should raise an error if you try to dump a Generator that uses a constraint with a proc" do
@@ -296,7 +294,7 @@ END_MIG
296
294
  i = 0
297
295
  types.map{|x| [:"c#{i+=1}", {:db_type=>x, :allow_null=>true}]}
298
296
  end
299
- table = <<END_MIG
297
+ @d.dump_table_schema(:x).should == (<<END_MIG).chomp
300
298
  create_table(:x) do
301
299
  Integer :c1
302
300
  Integer :c2
@@ -365,6 +363,5 @@ create_table(:x) do
365
363
  Bignum :c65
366
364
  end
367
365
  END_MIG
368
- @d.dump_table_schema(:x).should == table.chomp
369
366
  end
370
367
  end
@@ -9,10 +9,6 @@ else
9
9
  describe "Serialization plugin" do
10
10
  before do
11
11
  @c = Class.new(Sequel::Model(:items)) do
12
- include(Module.new do
13
- def before_save
14
- end
15
- end)
16
12
  no_primary_key
17
13
  columns :id, :abc, :def, :ghi
18
14
  end
@@ -79,11 +75,7 @@ describe "Serialization plugin" do
79
75
  @c.set_primary_key :id
80
76
  @c.plugin :serialization, :yaml, :abc, :def
81
77
  vals = nil
82
-
83
- ds = @c.dataset
84
- def ds.fetch_rows(sql, &block)
85
- block.call(:id => 1, :abc => "--- 1\n", :def => "--- hello\n")
86
- end
78
+ @c.dataset._fetch = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
87
79
 
88
80
  o = @c.first
89
81
  o.id.should == 1
@@ -94,18 +86,16 @@ describe "Serialization plugin" do
94
86
 
95
87
  o.update(:abc => 23)
96
88
  @c.create(:abc => [1, 2, 3])
97
- MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)",
98
- "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')"]
89
+ MODEL_DB.sqls.should == ["SELECT * FROM items LIMIT 1",
90
+ "UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)",
91
+ "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')",
92
+ "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
99
93
  end
100
94
 
101
95
  it "should translate values to and from marshal serialization format using accessor methods" do
102
96
  @c.set_primary_key :id
103
97
  @c.plugin :serialization, :marshal, :abc, :def
104
-
105
- ds = @c.dataset
106
- def ds.fetch_rows(sql, &block)
107
- block.call(:id => 1, :abc =>[Marshal.dump(1)].pack('m'), :def =>[Marshal.dump('hello')].pack('m'))
108
- end
98
+ @c.dataset._fetch = [:id => 1, :abc =>[Marshal.dump(1)].pack('m'), :def =>[Marshal.dump('hello')].pack('m')]
109
99
 
110
100
  o = @c.first
111
101
  o.id.should == 1
@@ -116,18 +106,16 @@ describe "Serialization plugin" do
116
106
 
117
107
  o.update(:abc => 23)
118
108
  @c.create(:abc => [1, 2, 3])
119
- MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{[Marshal.dump(23)].pack('m')}' WHERE (id = 1)",
120
- "INSERT INTO items (abc) VALUES ('#{[Marshal.dump([1, 2, 3])].pack('m')}')"]
109
+ MODEL_DB.sqls.should == ["SELECT * FROM items LIMIT 1",
110
+ "UPDATE items SET abc = '#{[Marshal.dump(23)].pack('m')}' WHERE (id = 1)",
111
+ "INSERT INTO items (abc) VALUES ('#{[Marshal.dump([1, 2, 3])].pack('m')}')",
112
+ "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
121
113
  end
122
114
 
123
115
  it "should translate values to and from json serialization format using accessor methods" do
124
116
  @c.set_primary_key :id
125
117
  @c.plugin :serialization, :json, :abc, :def
126
-
127
- ds = @c.dataset
128
- def ds.fetch_rows(sql, &block)
129
- block.call(:id => 1, :abc => [1].to_json, :def => ["hello"].to_json)
130
- end
118
+ @c.dataset._fetch = {:id => 1, :abc => [1].to_json, :def => ["hello"].to_json}
131
119
 
132
120
  o = @c.first
133
121
  o.id.should == 1
@@ -139,18 +127,16 @@ describe "Serialization plugin" do
139
127
  o.update(:abc => [23])
140
128
  @c.create(:abc => [1,2,3])
141
129
 
142
- MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{[23].to_json}' WHERE (id = 1)",
143
- "INSERT INTO items (abc) VALUES ('#{[1,2,3].to_json}')"]
130
+ MODEL_DB.sqls.should == ["SELECT * FROM items LIMIT 1",
131
+ "UPDATE items SET abc = '#{[23].to_json}' WHERE (id = 1)",
132
+ "INSERT INTO items (abc) VALUES ('#{[1,2,3].to_json}')",
133
+ "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
144
134
  end
145
135
 
146
136
  it "should copy serialization formats and columns to subclasses" do
147
137
  @c.set_primary_key :id
148
138
  @c.plugin :serialization, :yaml, :abc, :def
149
-
150
- ds = @c.dataset
151
- def ds.fetch_rows(sql, &block)
152
- block.call(:id => 1, :abc => "--- 1\n", :def => "--- hello\n")
153
- end
139
+ @c.dataset._fetch = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
154
140
 
155
141
  o = Class.new(@c).first
156
142
  o.id.should == 1
@@ -161,8 +147,10 @@ describe "Serialization plugin" do
161
147
 
162
148
  o.update(:abc => 23)
163
149
  Class.new(@c).create(:abc => [1, 2, 3])
164
- MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)",
165
- "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')"]
150
+ MODEL_DB.sqls.should == ["SELECT * FROM items LIMIT 1",
151
+ "UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)",
152
+ "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')",
153
+ "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
166
154
  end
167
155
 
168
156
  it "should clear the deserialized columns when refreshing" do
@@ -2,271 +2,197 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "sharding plugin" do
4
4
  before do
5
- @db = Sequel::Model.db.clone
5
+ @db = Sequel.mock(:numrows=>1, :autoid=>proc{1}, :servers=>{:s1=>{}, :s2=>{}, :s3=>{}, :s4=>{}})
6
6
  @Artist = Class.new(Sequel::Model(@db[:artists]))
7
7
  @Artist.class_eval do
8
+ dataset._fetch = {:id=>2, :name=>'YJM'}
8
9
  columns :id, :name
9
-
10
- def self.y
11
- {:id=>2, :name=>'YJM'}
12
- end
10
+ plugin :sharding
13
11
  end
14
12
  @Album = Class.new(Sequel::Model(@db[:albums]))
15
13
  @Album.class_eval do
14
+ dataset._fetch = {:id=>1, :name=>'RF', :artist_id=>2}
16
15
  columns :id, :artist_id, :name
17
-
18
- def self.ds_ext(m=nil)
19
- @ds_ext = m if m
20
- @ds_ext
21
- end
22
-
23
- def self.y
24
- {:id=>1, :name=>'RF', :artist_id=>2}
25
- end
26
-
27
- private
28
-
29
- def _join_table_dataset(opts)
30
- ds = super
31
- m = model
32
- ds.meta_def(:model){m}
33
- ds.extend model.ds_ext
34
- ds
35
- end
16
+ plugin :sharding
36
17
  end
37
18
  @Tag = Class.new(Sequel::Model(@db[:tags]))
38
19
  @Tag.class_eval do
20
+ dataset._fetch = {:id=>3, :name=>'M'}
39
21
  columns :id, :name
40
-
41
- def self.y
42
- {:id=>3, :name=>'M'}
43
- end
22
+ plugin :sharding
44
23
  end
45
- models = [@Artist, @Album, @Tag]
46
24
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
47
25
  @Album.many_to_one :artist, :class=>@Artist
48
26
  @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:albums_tags
49
- m = Module.new do
50
- def actions
51
- @actions ||= []
52
- end
53
- end
54
- models.each do |model|
55
- model.extend m
56
- model.plugin :sharding
57
- model.dataset.extend(ds_ext = Module.new do
58
- def insert(h={})
59
- model.actions << [:insert, h.dup, opts[:server]]
60
- 1
61
- end
62
- def delete
63
- model.actions << [:delete,(literal(opts[:where]) if opts[:where]), opts[:server]]
64
- 1
65
- end
66
- def update(h={})
67
- model.actions << [:update, h.dup, (literal(opts[:where]) if opts[:where]), opts[:server]]
68
- 1
69
- end
70
- def fetch_rows(sql)
71
- model.actions << [:fetch, (literal(opts[:where] || opts[:join]) if opts[:where] || opts[:join]), opts[:server]]
72
- yield(model.y)
73
- end
74
- end)
75
- @Album.ds_ext(ds_ext)
76
- end
77
- def @db.actions; @actions ||= []; end
78
- def @db.transaction(opts)
79
- actions << [:transaction, opts[:server]]
80
- super
81
- end
27
+ @db.sqls
82
28
  end
83
29
 
84
30
  specify "should allow you to instantiate a new object for a specified shard" do
85
31
  @Album.new_using_server(:s1, :name=>'RF').save
86
- @Album.actions.should == [[:insert, {:name=>"RF"}, :s1], [:fetch, "(id = 1)", :s1]]
32
+ @db.sqls.should == ["INSERT INTO albums (name) VALUES ('RF') -- s1", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s1"]
87
33
 
88
- @Album.actions.clear
89
34
  @Album.new_using_server(:s2){|o| o.name = 'MO'}.save
90
- @Album.actions.should == [[:insert, {:name=>"MO"}, :s2], [:fetch, "(id = 1)", :s2]]
35
+ @db.sqls.should == ["INSERT INTO albums (name) VALUES ('MO') -- s2", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s2"]
91
36
  end
92
37
 
93
38
  specify "should allow you to create and save a new object for a specified shard" do
94
39
  @Album.create_using_server(:s1, :name=>'RF')
95
- @Album.actions.should == [[:insert, {:name=>"RF"}, :s1], [:fetch, "(id = 1)", :s1]]
96
-
97
- @Album.actions.clear
40
+ @db.sqls.should == ["INSERT INTO albums (name) VALUES ('RF') -- s1", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s1"]
41
+
98
42
  @Album.create_using_server(:s2){|o| o.name = 'MO'}
99
- @Album.actions.should == [[:insert, {:name=>"MO"}, :s2], [:fetch, "(id = 1)", :s2]]
43
+ @db.sqls.should == ["INSERT INTO albums (name) VALUES ('MO') -- s2", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s2"]
100
44
  end
101
45
 
102
46
  specify "should have objects retrieved from a specific shard update that shard" do
103
47
  @Album.server(:s1).first.update(:name=>'MO')
104
- @Album.actions.should == [[:fetch, nil, :s1], [:update, {:name=>"MO"}, "(id = 1)", :s1]]
48
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "UPDATE albums SET name = 'MO' WHERE (id = 1) -- s1"]
105
49
  end
106
50
 
107
51
  specify "should have objects retrieved from a specific shard delete from that shard" do
108
52
  @Album.server(:s1).first.delete
109
- @Album.actions.should == [[:fetch, nil, :s1], [:delete, "(id = 1)", :s1]]
53
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "DELETE FROM albums WHERE (id = 1) -- s1"]
110
54
  end
111
55
 
112
56
  specify "should have objects retrieved from a specific shard reload from that shard" do
113
57
  @Album.server(:s1).first.reload
114
- @Album.actions.should == [[:fetch, nil, :s1], [:fetch, "(id = 1)", :s1]]
58
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s1"]
115
59
  end
116
60
 
117
61
  specify "should use current dataset's shard when eager loading if eagerly loaded dataset doesn't have its own shard" do
118
62
  albums = @Album.server(:s1).eager(:artist).all
119
- @Album.actions.should == [[:fetch, nil, :s1]]
120
- @Artist.actions.should == [[:fetch, "(artists.id IN (2))", :s1]]
121
- @Artist.actions.clear
122
- albums.length == 1
63
+ @db.sqls.should == ["SELECT * FROM albums -- s1", "SELECT * FROM artists WHERE (artists.id IN (2)) -- s1"]
64
+ albums.length.should == 1
123
65
  albums.first.artist.save
124
- @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s1]]
66
+ @db.sqls.should == ["UPDATE artists SET name = 'YJM' WHERE (id = 2) -- s1"]
125
67
  end
126
68
 
127
69
  specify "should not use current dataset's shard when eager loading if eagerly loaded dataset has its own shard" do
128
70
  @Artist.dataset.opts[:server] = :s2
129
71
  albums = @Album.server(:s1).eager(:artist).all
130
- @Album.actions.should == [[:fetch, nil, :s1]]
131
- @Artist.actions.should == [[:fetch, "(artists.id IN (2))", :s2]]
132
- @Artist.actions.clear
133
- albums.length == 1
72
+ @db.sqls.should == ["SELECT * FROM albums -- s1", "SELECT * FROM artists WHERE (artists.id IN (2)) -- s2"]
73
+ albums.length.should == 1
134
74
  albums.first.artist.save
135
- @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
75
+ @db.sqls.should == ["UPDATE artists SET name = 'YJM' WHERE (id = 2) -- s2"]
136
76
  end
137
77
 
138
78
  specify "should use current dataset's shard when eager graphing if eagerly graphed dataset doesn't have its own shard" do
139
79
  ds = @Album.server(:s1).eager_graph(:artist)
140
- def ds.fetch_rows(sql)
141
- super(sql)
142
- yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
143
- end
80
+ ds._fetch = {:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'}
144
81
  albums = ds.all
145
- @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", :s1]]
146
- albums.length == 1
82
+ @db.sqls.should == ["SELECT albums.id, albums.artist_id, albums.name, artist.id AS artist_id_0, artist.name AS artist_name FROM albums LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id) -- s1"]
83
+ albums.length.should == 1
147
84
  albums.first.artist.save
148
- @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s1]]
85
+ @db.sqls.should == ["UPDATE artists SET name = 'YJM' WHERE (id = 2) -- s1"]
149
86
  end
150
87
 
151
88
  specify "should not use current dataset's shard when eager graphing if eagerly graphed dataset has its own shard" do
152
89
  @Artist.dataset.opts[:server] = :s2
153
90
  ds = @Album.server(:s1).eager_graph(:artist)
154
- def ds.fetch_rows(sql)
155
- super(sql)
156
- yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
157
- end
91
+ ds._fetch = {:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'}
158
92
  albums = ds.all
159
- @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", :s1]]
160
- albums.length == 1
93
+ @db.sqls.should == ["SELECT albums.id, albums.artist_id, albums.name, artist.id AS artist_id_0, artist.name AS artist_name FROM albums LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id) -- s1"]
94
+ albums.length.should == 1
161
95
  albums.first.artist.save
162
- @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
96
+ @db.sqls.should == ["UPDATE artists SET name = 'YJM' WHERE (id = 2) -- s2"]
163
97
  end
164
98
 
165
99
  specify "should use eagerly graphed dataset shard for eagerly graphed objects even if current dataset does not have a shard" do
166
100
  @Artist.dataset.opts[:server] = :s2
167
101
  ds = @Album.eager_graph(:artist)
168
- def ds.fetch_rows(sql)
169
- super(sql)
170
- yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
171
- end
102
+ ds._fetch = {:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'}
172
103
  albums = ds.all
173
- @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", nil]]
174
- albums.length == 1
104
+ @db.sqls.should == ["SELECT albums.id, albums.artist_id, albums.name, artist.id AS artist_id_0, artist.name AS artist_name FROM albums LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id)"]
105
+ albums.length.should == 1
175
106
  albums.first.artist.save
176
- @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
107
+ @db.sqls.should == ["UPDATE artists SET name = 'YJM' WHERE (id = 2) -- s2"]
177
108
  end
178
109
 
179
110
  specify "should have objects retrieved from a specific shard use associated objects from that shard, with modifications to the associated objects using that shard" do
180
111
  album = @Album.server(:s1).first
181
- @Album.actions.should == [[:fetch, nil, :s1]]
112
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1"]
182
113
  album.artist.update(:name=>'AS')
183
- @Artist.actions.should == [[:fetch, "(artists.id = 2)", :s1], [:update, {:name=>"AS"}, "(id = 2)", :s1]]
114
+ @db.sqls.should == ["SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1 -- s1", "UPDATE artists SET name = 'AS' WHERE (id = 2) -- s1"]
184
115
  album.tags.map{|a| a.update(:name=>'SR')}
185
- @Tag.actions.should == [[:fetch, "( INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1)))", :s1], [:update, {:name=>"SR"}, "(id = 3)", :s1]]
186
-
187
- @Album.actions.clear
188
- @Artist.actions.clear
116
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1)) -- s1", "UPDATE tags SET name = 'SR' WHERE (id = 3) -- s1"]
189
117
  @Artist.server(:s2).first.albums.map{|a| a.update(:name=>'MO')}
190
- @Artist.actions.should == [[:fetch, nil, :s2]]
191
- @Album.actions.should == [[:fetch, "(albums.artist_id = 2)", :s2], [:update, {:name=>"MO"}, "(id = 1)", :s2]]
118
+ @db.sqls.should == ["SELECT * FROM artists LIMIT 1 -- s2", "SELECT * FROM albums WHERE (albums.artist_id = 2) -- s2", "UPDATE albums SET name = 'MO' WHERE (id = 1) -- s2"]
192
119
  end
193
120
 
194
121
  specify "should have objects retrieved from a specific shard add associated objects to that shard" do
195
122
  album = @Album.server(:s1).first
196
123
  artist = @Artist.server(:s2).first
197
- @Album.actions.clear
198
- @Artist.actions.clear
124
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM artists LIMIT 1 -- s2"]
199
125
 
200
126
  artist.add_album(:name=>'MO')
201
- @Album.actions.should == [[:insert, {:name=>"MO", :artist_id=>2}, :s2], [:fetch, "(id = 1)", :s2]]
202
- @Album.actions.clear
127
+ sqls = @db.sqls
128
+ ["INSERT INTO albums (artist_id, name) VALUES (2, 'MO') -- s2", "INSERT INTO albums (name, artist_id) VALUES ('MO', 2) -- s2"].should include(sqls.shift)
129
+ sqls.should == ["SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s2"]
203
130
 
204
131
  album.add_tag(:name=>'SR')
205
- @Tag.actions.should == [[:insert, {:name=>"SR"}, :s1], [:fetch, "(id = 1)", :s1]]
206
- @Album.actions.should == [[:insert, {:album_id=>1, :tag_id=>3}, :s1]]
132
+ sqls = @db.sqls
133
+ ["INSERT INTO albums_tags (album_id, tag_id) VALUES (1, 3) -- s1", "INSERT INTO albums_tags (tag_id, album_id) VALUES (3, 1) -- s1"].should include(sqls.pop)
134
+ sqls.should == ["INSERT INTO tags (name) VALUES ('SR') -- s1", "SELECT * FROM tags WHERE (id = 1) LIMIT 1 -- s1", ]
207
135
  end
208
136
 
209
137
  specify "should have objects retrieved from a specific shard remove associated objects from that shard" do
210
138
  album = @Album.server(:s1).first
211
139
  artist = @Artist.server(:s2).first
212
- @Album.actions.clear
213
- @Artist.actions.clear
140
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM artists LIMIT 1 -- s2"]
214
141
 
215
142
  artist.remove_album(1)
216
- @Album.actions.should == [[:fetch, "((albums.artist_id = 2) AND (albums.id = 1))", :s2], [:update, {:name=>"RF", :artist_id=>nil}, "(id = 1)", :s2]]
217
- @Album.actions.clear
143
+ sqls = @db.sqls
144
+ ["UPDATE albums SET artist_id = NULL, name = 'RF' WHERE (id = 1) -- s2", "UPDATE albums SET name = 'RF', artist_id = NULL WHERE (id = 1) -- s2"].should include(sqls.pop)
145
+ sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 2) AND (albums.id = 1)) LIMIT 1 -- s2"]
218
146
 
219
147
  album.remove_tag(3)
220
- @Tag.actions.should == [[:fetch, "(tags.id = 3)", :s1]]
221
- @Album.actions.should == [[:delete, "((album_id = 1) AND (tag_id = 3))", :s1]]
148
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1)) WHERE (tags.id = 3) LIMIT 1 -- s1", "DELETE FROM albums_tags WHERE ((album_id = 1) AND (tag_id = 3)) -- s1"]
222
149
  end
223
150
 
224
151
  specify "should have objects retrieved from a specific shard remove all associated objects from that shard" do
225
152
  album = @Album.server(:s1).first
226
153
  artist = @Artist.server(:s2).first
227
- @Album.actions.clear
228
- @Artist.actions.clear
154
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM artists LIMIT 1 -- s2"]
229
155
 
230
156
  artist.remove_all_albums
231
- @Album.actions.should == [[:update, {:artist_id=>nil}, "(artist_id = 2)", :s2]]
232
- @Album.actions.clear
157
+ @db.sqls.should == ["UPDATE albums SET artist_id = NULL WHERE (artist_id = 2) -- s2"]
233
158
 
234
159
  album.remove_all_tags
235
- @Album.actions.should == [[:delete, "(album_id = 1)", :s1]]
160
+ @db.sqls.should == ["DELETE FROM albums_tags WHERE (album_id = 1) -- s1"]
236
161
  end
237
162
 
238
163
  specify "should not override a server already set on an associated object" do
239
164
  album = @Album.server(:s1).first
240
165
  artist = @Artist.server(:s2).first
241
- @Album.actions.clear
242
- @Artist.actions.clear
166
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM artists LIMIT 1 -- s2"]
243
167
 
244
168
  artist.add_album(@Album.load(:id=>4, :name=>'MO').set_server(:s3))
245
- @Album.actions.should == [[:update, {:name=>"MO", :artist_id=>2}, "(id = 4)", :s3]]
246
- @Album.actions.clear
169
+ ["UPDATE albums SET artist_id = 2, name = 'MO' WHERE (id = 4) -- s3", "UPDATE albums SET name = 'MO', artist_id = 2 WHERE (id = 4) -- s3"].should include(@db.sqls.pop)
247
170
 
248
171
  artist.remove_album(@Album.load(:id=>5, :name=>'T', :artist_id=>2).set_server(:s4))
249
172
  # Should select from current object's shard to check existing association, but update associated object's shard
250
- @Album.actions.should == [[:fetch, "((albums.artist_id = 2) AND (id = 5))", :s2], [:update, {:name=>"T", :artist_id=>nil}, "(id = 5)", :s4]]
251
- @Album.actions.clear
173
+ sqls = @db.sqls
174
+ ["UPDATE albums SET artist_id = NULL, name = 'T' WHERE (id = 5) -- s4", "UPDATE albums SET name = 'T', artist_id = NULL WHERE (id = 5) -- s4"].should include(sqls.pop)
175
+ sqls.should == ["SELECT 1 FROM albums WHERE ((albums.artist_id = 2) AND (id = 5)) LIMIT 1 -- s2"]
252
176
  end
253
177
 
254
178
  specify "should be able to set a shard to use for any object using set_server" do
255
179
  @Album.server(:s1).first.set_server(:s2).reload
256
- @Album.actions.should == [[:fetch, nil, :s1], [:fetch, "(id = 1)", :s2]]
180
+ @db.sqls.should == ["SELECT * FROM albums LIMIT 1 -- s1", "SELECT * FROM albums WHERE (id = 1) LIMIT 1 -- s2"]
257
181
  end
258
182
 
259
183
  specify "should use transactions on the correct shard" do
260
184
  @Album.use_transactions = true
261
185
  @Album.server(:s2).first.save
262
- @Album.actions.should == [[:fetch, nil, :s2], [:update, {:name=>"RF", :artist_id=>2}, "(id = 1)", :s2]]
263
- @db.actions.should == [[:transaction, :s2]]
186
+ sqls = @db.sqls
187
+ ["UPDATE albums SET artist_id = 2, name = 'RF' WHERE (id = 1) -- s2", "UPDATE albums SET name = 'RF', artist_id = 2 WHERE (id = 1) -- s2"].should include(sqls.slice!(2))
188
+ sqls.should == ["SELECT * FROM albums LIMIT 1 -- s2", "BEGIN -- s2", "COMMIT -- s2"]
264
189
  end
265
190
 
266
- specify "should use not override shard given when saving" do
191
+ specify "should use override current shard when saving with given :server option" do
267
192
  @Album.use_transactions = true
268
193
  @Album.server(:s2).first.save(:server=>:s1)
269
- @Album.actions.should == [[:fetch, nil, :s2], [:update, {:name=>"RF", :artist_id=>2}, "(id = 1)", :s2]]
270
- @db.actions.should == [[:transaction, :s1]]
194
+ sqls = @db.sqls
195
+ ["UPDATE albums SET artist_id = 2, name = 'RF' WHERE (id = 1) -- s1", "UPDATE albums SET name = 'RF', artist_id = 2 WHERE (id = 1) -- s1"].should include(sqls.slice!(2))
196
+ sqls.should == ["SELECT * FROM albums LIMIT 1 -- s2", "BEGIN -- s1", "COMMIT -- s1"]
271
197
  end
272
198
  end