sequel 3.13.0 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGELOG +36 -0
  2. data/doc/release_notes/3.14.0.txt +118 -0
  3. data/lib/sequel/adapters/oracle.rb +7 -2
  4. data/lib/sequel/adapters/shared/mssql.rb +9 -3
  5. data/lib/sequel/connection_pool/sharded_threaded.rb +1 -1
  6. data/lib/sequel/connection_pool/threaded.rb +3 -3
  7. data/lib/sequel/database/connecting.rb +47 -11
  8. data/lib/sequel/database/dataset.rb +17 -6
  9. data/lib/sequel/database/dataset_defaults.rb +15 -3
  10. data/lib/sequel/database/logging.rb +4 -3
  11. data/lib/sequel/database/misc.rb +33 -21
  12. data/lib/sequel/database/query.rb +61 -22
  13. data/lib/sequel/database/schema_generator.rb +108 -45
  14. data/lib/sequel/database/schema_methods.rb +8 -5
  15. data/lib/sequel/dataset/actions.rb +194 -45
  16. data/lib/sequel/dataset/features.rb +1 -1
  17. data/lib/sequel/dataset/graph.rb +51 -43
  18. data/lib/sequel/dataset/misc.rb +29 -5
  19. data/lib/sequel/dataset/mutation.rb +0 -1
  20. data/lib/sequel/dataset/prepared_statements.rb +14 -2
  21. data/lib/sequel/dataset/query.rb +268 -125
  22. data/lib/sequel/dataset/sql.rb +33 -44
  23. data/lib/sequel/extensions/migration.rb +3 -2
  24. data/lib/sequel/extensions/pagination.rb +1 -1
  25. data/lib/sequel/model/associations.rb +89 -87
  26. data/lib/sequel/model/base.rb +386 -109
  27. data/lib/sequel/model/errors.rb +15 -1
  28. data/lib/sequel/model/exceptions.rb +3 -3
  29. data/lib/sequel/model/inflections.rb +2 -2
  30. data/lib/sequel/model/plugins.rb +9 -5
  31. data/lib/sequel/plugins/rcte_tree.rb +43 -15
  32. data/lib/sequel/plugins/schema.rb +6 -5
  33. data/lib/sequel/plugins/serialization.rb +1 -1
  34. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  35. data/lib/sequel/plugins/tree.rb +33 -1
  36. data/lib/sequel/timezones.rb +16 -10
  37. data/lib/sequel/version.rb +1 -1
  38. data/spec/adapters/mssql_spec.rb +36 -2
  39. data/spec/adapters/mysql_spec.rb +4 -4
  40. data/spec/adapters/postgres_spec.rb +1 -1
  41. data/spec/adapters/spec_helper.rb +2 -2
  42. data/spec/core/database_spec.rb +8 -1
  43. data/spec/core/dataset_spec.rb +36 -1
  44. data/spec/extensions/pagination_spec.rb +1 -1
  45. data/spec/extensions/rcte_tree_spec.rb +40 -8
  46. data/spec/extensions/schema_spec.rb +5 -0
  47. data/spec/extensions/serialization_spec.rb +4 -4
  48. data/spec/extensions/single_table_inheritance_spec.rb +7 -0
  49. data/spec/extensions/tree_spec.rb +36 -0
  50. data/spec/integration/dataset_test.rb +19 -0
  51. data/spec/integration/prepared_statement_test.rb +2 -2
  52. data/spec/integration/schema_test.rb +1 -1
  53. data/spec/integration/spec_helper.rb +4 -4
  54. data/spec/integration/timezone_test.rb +27 -21
  55. data/spec/model/associations_spec.rb +5 -5
  56. data/spec/model/dataset_methods_spec.rb +13 -0
  57. data/spec/model/hooks_spec.rb +31 -0
  58. data/spec/model/record_spec.rb +24 -7
  59. data/spec/model/validations_spec.rb +9 -4
  60. metadata +6 -4
@@ -89,7 +89,7 @@ context "A MySQL database" do
89
89
  end
90
90
  end
91
91
 
92
- if MYSQL_DB.class.adapter_scheme == :mysql
92
+ if MYSQL_DB.adapter_scheme == :mysql
93
93
  context "Sequel::MySQL.convert_tinyint_to_bool" do
94
94
  before do
95
95
  @db = MYSQL_DB
@@ -536,7 +536,7 @@ context "A MySQL database" do
536
536
  end
537
537
 
538
538
  # Socket tests should only be run if the MySQL server is on localhost
539
- if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.class.adapter_scheme == :mysql
539
+ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.adapter_scheme == :mysql
540
540
  context "A MySQL database" do
541
541
  specify "should accept a socket option" do
542
542
  db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
@@ -895,7 +895,7 @@ context "MySQL::Dataset#complex_expression_sql" do
895
895
  end
896
896
  end
897
897
 
898
- unless MYSQL_DB.class.adapter_scheme == :do
898
+ unless MYSQL_DB.adapter_scheme == :do
899
899
  context "MySQL Stored Procedures" do
900
900
  before do
901
901
  MYSQL_DB.create_table(:items){Integer :id; Integer :value}
@@ -940,7 +940,7 @@ unless MYSQL_DB.class.adapter_scheme == :do
940
940
  end
941
941
  end
942
942
 
943
- if MYSQL_DB.class.adapter_scheme == :mysql
943
+ if MYSQL_DB.adapter_scheme == :mysql
944
944
  context "MySQL bad date/time conversions" do
945
945
  after do
946
946
  Sequel::MySQL.convert_invalid_date_time = false
@@ -962,7 +962,7 @@ context "Postgres::Database functions, languages, and triggers" do
962
962
  end
963
963
  end
964
964
 
965
- if POSTGRES_DB.class.adapter_scheme == :postgres
965
+ if POSTGRES_DB.adapter_scheme == :postgres
966
966
  context "Postgres::Dataset #use_cursor" do
967
967
  before(:all) do
968
968
  @db = POSTGRES_DB
@@ -34,12 +34,12 @@ class Spec::Example::ExampleGroup
34
34
  pending = false
35
35
  checked.each do |c|
36
36
  case c
37
- when INTEGRATION_DB.class.adapter_scheme
37
+ when INTEGRATION_DB.adapter_scheme
38
38
  pending = c
39
39
  when Proc
40
40
  pending = c if c.first.call(INTEGRATION_DB)
41
41
  when Array
42
- pending = c if c.first == INTEGRATION_DB.class.adapter_scheme && c.last == INTEGRATION_DB.call(INTEGRATION_DB)
42
+ pending = c if c.first == INTEGRATION_DB.adapter_scheme && c.last == INTEGRATION_DB.call(INTEGRATION_DB)
43
43
  end
44
44
  end
45
45
  if pending
@@ -186,6 +186,11 @@ context "A new Database" do
186
186
  db.should be_a_kind_of(Sequel::Database)
187
187
  db.opts[:uri].should == 'do:test://host/db_name'
188
188
  end
189
+
190
+ specify "should populate :adapter option when using connection string" do
191
+ Sequel::Database.should_receive(:adapter_class).once.with(:do).and_return(Sequel::Database)
192
+ Sequel.connect('do:test://host/db_name').opts[:adapter] == :do
193
+ end
189
194
  end
190
195
 
191
196
  context "Database#disconnect" do
@@ -311,7 +316,7 @@ context "Database#uri" do
311
316
  end
312
317
  end
313
318
 
314
- context "Database.adapter_scheme" do
319
+ context "Database.adapter_scheme and #adapter_scheme" do
315
320
  specify "should return the database schema" do
316
321
  Sequel::Database.adapter_scheme.should be_nil
317
322
 
@@ -320,6 +325,7 @@ context "Database.adapter_scheme" do
320
325
  end
321
326
 
322
327
  @c.adapter_scheme.should == :mau
328
+ @c.new({}).adapter_scheme.should == :mau
323
329
  end
324
330
  end
325
331
 
@@ -1493,6 +1499,7 @@ context "Database#each_server" do
1493
1499
  @db.each_server do |db|
1494
1500
  db.should be_a_kind_of(Sequel::Database)
1495
1501
  db.should_not == @db
1502
+ db.opts[:adapter].should == :mock
1496
1503
  db.opts[:database].should == 2
1497
1504
  hosts << db.opts[:host]
1498
1505
  end
@@ -3089,6 +3089,41 @@ context "Dataset#grep" do
3089
3089
  "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3090
3090
  end
3091
3091
 
3092
+ specify "should support the :all_patterns option" do
3093
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true).sql.should ==
3094
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (body LIKE 'abc')) AND ((title LIKE 'def') OR (body LIKE 'def')))"
3095
+ end
3096
+
3097
+ specify "should support the :all_columns option" do
3098
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true).sql.should ==
3099
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) AND ((body LIKE 'abc') OR (body LIKE 'def')))"
3100
+ end
3101
+
3102
+ specify "should support the :case_insensitive option" do
3103
+ @ds.grep([:title, :body], ['abc', 'def'], :case_insensitive=>true).sql.should ==
3104
+ "SELECT * FROM posts WHERE ((title ILIKE 'abc') OR (title ILIKE 'def') OR (body ILIKE 'abc') OR (body ILIKE 'def'))"
3105
+ end
3106
+
3107
+ specify "should support the :all_patterns and :all_columns options together" do
3108
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true).sql.should ==
3109
+ "SELECT * FROM posts WHERE ((title LIKE 'abc') AND (body LIKE 'abc') AND (title LIKE 'def') AND (body LIKE 'def'))"
3110
+ end
3111
+
3112
+ specify "should support the :all_patterns and :case_insensitive options together" do
3113
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :case_insensitive=>true).sql.should ==
3114
+ "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (body ILIKE 'abc')) AND ((title ILIKE 'def') OR (body ILIKE 'def')))"
3115
+ end
3116
+
3117
+ specify "should support the :all_columns and :case_insensitive options together" do
3118
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true, :case_insensitive=>true).sql.should ==
3119
+ "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (title ILIKE 'def')) AND ((body ILIKE 'abc') OR (body ILIKE 'def')))"
3120
+ end
3121
+
3122
+ specify "should support the :all_patterns, :all_columns, and :case_insensitive options together" do
3123
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true, :case_insensitive=>true).sql.should ==
3124
+ "SELECT * FROM posts WHERE ((title ILIKE 'abc') AND (body ILIKE 'abc') AND (title ILIKE 'def') AND (body ILIKE 'def'))"
3125
+ end
3126
+
3092
3127
  specify "should support regexps though the database may not support it" do
3093
3128
  @ds.grep(:title, /ruby/).sql.should ==
3094
3129
  "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
@@ -3596,7 +3631,7 @@ describe "Sequel timezone support" do
3596
3631
  @dataset.literal(t).should == "#{s}#{@offset}'"
3597
3632
 
3598
3633
  t = DateTime.now.new_offset(0)
3599
- s = t.new_offset(Sequel::LOCAL_DATETIME_OFFSET).strftime("'%Y-%m-%d %H:%M:%S")
3634
+ s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3600
3635
  @dataset.literal(t).should == "#{s}#{@offset}'"
3601
3636
  end
3602
3637
 
@@ -33,7 +33,7 @@ context "A paginated dataset" do
33
33
  @d.paginate(4, 50).next_page.should be_nil
34
34
  end
35
35
 
36
- specify "should return the previous page number or nil if we're on the last" do
36
+ specify "should return the previous page number or nil if we're on the first" do
37
37
  @paginated.prev_page.should be_nil
38
38
  @d.paginate(4, 50).prev_page.should == 3
39
39
  end
@@ -33,16 +33,24 @@ describe Sequel::Model, "rcte_tree" do
33
33
  @c.plugin :rcte_tree
34
34
  @o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.id = 1) LIMIT 1'
35
35
  @o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.parent_id = 2)'
36
- @o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (id = 1) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t'
37
- @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t'
36
+ @o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (id = 1) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t AS nodes'
37
+ @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t AS nodes'
38
38
  end
39
39
 
40
40
  it "should use the correct SQL for lazy associations when giving options" do
41
41
  @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :cte_name=>:cte, :order=>:name, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
42
42
  @o.p_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.i = 4) ORDER BY name LIMIT 1'
43
43
  @o.cs_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.pi = 3) ORDER BY name'
44
- @o.as_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (i = 4) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.pi = nodes.i)) SELECT * FROM cte ORDER BY name'
45
- @o.ds_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (pi = 3) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.i = nodes.pi)) SELECT * FROM cte ORDER BY name'
44
+ @o.as_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (i = 4) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.pi = nodes.i)) SELECT * FROM cte AS nodes ORDER BY name'
45
+ @o.ds_dataset.sql.should == 'WITH cte AS (SELECT * FROM nodes WHERE (pi = 3) UNION ALL SELECT nodes.* FROM nodes INNER JOIN cte ON (cte.i = nodes.pi)) SELECT * FROM cte AS nodes ORDER BY name'
46
+ end
47
+
48
+ it "should use the correct SQL for lazy associations with :conditions option" do
49
+ @c.plugin :rcte_tree, :conditions => {:i => 1}
50
+ @o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.id = 1) AND (i = 1)) LIMIT 1'
51
+ @o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE ((nodes.parent_id = 2) AND (i = 1))'
52
+ @o.ancestors_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE ((id = 1) AND (i = 1)) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.parent_id = nodes.id) WHERE (i = 1)) SELECT * FROM t AS nodes WHERE (i = 1)'
53
+ @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE ((parent_id = 2) AND (i = 1)) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id) WHERE (i = 1)) SELECT * FROM t AS nodes WHERE (i = 1)'
46
54
  end
47
55
 
48
56
  it "should add all parent associations when lazily loading ancestors" do
@@ -93,7 +101,7 @@ describe Sequel::Model, "rcte_tree" do
93
101
  {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>2}, {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>1}]]
94
102
  os = @ds.eager(:ancestors).all
95
103
  MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
96
- MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x, nodes\.\* FROM nodes WHERE \(id IN \([12], [12]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.parent_id = nodes\.id\)\) SELECT \* FROM t/
104
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x, nodes\.\* FROM nodes WHERE \(id IN \([12], [12]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.parent_id = nodes\.id\)\) SELECT \* FROM t AS nodes/
97
105
  os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D'), @c.load(:id=>9, :parent_id=>nil, :name=>'E')]
98
106
  os.map{|o| o.ancestors}.should == [[@c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
99
107
  [@c.load(:id=>2, :name=>'AA', :parent_id=>1), @c.load(:id=>1, :name=>'00', :parent_id=>8), @c.load(:id=>8, :name=>'?', :parent_id=>nil)],
@@ -126,7 +134,19 @@ describe Sequel::Model, "rcte_tree" do
126
134
  os.map{|o| o.p.p.p.p if o.p and o.p.p and o.p.p.p}.should == [nil, nil, nil, nil]
127
135
  MODEL_DB.new_sqls.should == []
128
136
  end
129
-
137
+
138
+ it "should eagerly load ancestors respecting association option :conditions" do
139
+ @c.plugin :rcte_tree, :conditions => {:i => 1}
140
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}, {:id=>9, :parent_id=>nil, :name=>'E'}],
141
+ [{:id=>2, :name=>'AA', :parent_id=>1, :x_root_x=>2},
142
+ {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>1}, {:id=>1, :name=>'00', :parent_id=>8, :x_root_x=>2},
143
+ {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>2}, {:id=>8, :name=>'?', :parent_id=>nil, :x_root_x=>1}]]
144
+ os = @ds.eager(:ancestors).all
145
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
146
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT id AS x_root_x, nodes\.\* FROM nodes WHERE \(\(id IN \([12], [12]\)\) AND \(i = 1\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.parent_id = nodes\.id\) WHERE \(i = 1\)\) SELECT \* FROM t AS nodes WHERE \(i = 1\)/
147
+ MODEL_DB.new_sqls.should == []
148
+ end
149
+
130
150
  it "should eagerly load descendants" do
131
151
  @c.plugin :rcte_tree
132
152
  @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
@@ -135,7 +155,7 @@ describe Sequel::Model, "rcte_tree" do
135
155
  {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7}]]
136
156
  os = @ds.eager(:descendants).all
137
157
  MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
138
- MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\)\) SELECT \* FROM t/
158
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\)\) SELECT \* FROM t AS nodes/
139
159
  os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
140
160
  os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
141
161
  [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
@@ -173,7 +193,7 @@ describe Sequel::Model, "rcte_tree" do
173
193
  {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7, :x_level_x=>0}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7, :x_level_x=>1}]]
174
194
  os = @ds.eager(:descendants=>2).all
175
195
  MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
176
- MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\*, 0 AS x_level_x FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\*, \(t\.x_level_x \+ 1\) AS x_level_x FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(t\.x_level_x < 1\)\) SELECT \* FROM t/
196
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\*, 0 AS x_level_x FROM nodes WHERE \(parent_id IN \([267], [267], [267]\)\) UNION ALL SELECT t\.x_root_x, nodes\.\*, \(t\.x_level_x \+ 1\) AS x_level_x FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(t\.x_level_x < 1\)\) SELECT \* FROM t AS nodes/
177
197
  os.should == [@c.load(:id=>2, :parent_id=>1, :name=>'AA'), @c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>7, :parent_id=>1, :name=>'D')]
178
198
  os.map{|o| o.descendants}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E'), @c.load(:id=>3, :name=>'00', :parent_id=>6)],
179
199
  [@c.load(:id=>3, :name=>'00', :parent_id=>6)],
@@ -202,4 +222,16 @@ describe Sequel::Model, "rcte_tree" do
202
222
  os.map{|o1| o1.associations[:cs].map{|o2| o2.associations[:cs].map{|o3| o3.associations[:cs]}}}.should == [[[[]], []], [[]], [[nil]]]
203
223
  MODEL_DB.new_sqls.should == []
204
224
  end
225
+
226
+ it "should eagerly load descendants respecting association option :conditions" do
227
+ @c.plugin :rcte_tree, :conditions => {:i => 1}
228
+ @ds.row_sets = [[{:id=>2, :parent_id=>1, :name=>'AA'}, {:id=>6, :parent_id=>2, :name=>'C'}, {:id=>7, :parent_id=>1, :name=>'D'}],
229
+ [{:id=>6, :parent_id=>2, :name=>'C', :x_root_x=>2}, {:id=>9, :parent_id=>2, :name=>'E', :x_root_x=>2},
230
+ {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>6}, {:id=>3, :name=>'00', :parent_id=>6, :x_root_x=>2},
231
+ {:id=>4, :name=>'?', :parent_id=>7, :x_root_x=>7}, {:id=>5, :name=>'?', :parent_id=>4, :x_root_x=>7}]]
232
+ os = @ds.eager(:descendants).all
233
+ MODEL_DB.sqls.first.should == "SELECT * FROM nodes"
234
+ MODEL_DB.new_sqls.last.should =~ /WITH t AS \(SELECT parent_id AS x_root_x, nodes\.\* FROM nodes WHERE \(\(parent_id IN \([267], [267], [267]\)\) AND \(i = 1\)\) UNION ALL SELECT t\.x_root_x, nodes\.\* FROM nodes INNER JOIN t ON \(t\.id = nodes\.parent_id\) WHERE \(i = 1\)\) SELECT \* FROM t AS nodes WHERE \(i = 1\)/
235
+ MODEL_DB.new_sqls.should == []
236
+ end
205
237
  end
@@ -53,6 +53,11 @@ describe Sequel::Model, "create_table and schema" do
53
53
  MODEL_DB.sqls.should == ['CREATE TABLE items (name text, price float NOT NULL)']
54
54
  end
55
55
 
56
+ it "should allow setting schema and creating the table in one call" do
57
+ @model.create_table { text :name }
58
+ MODEL_DB.sqls.should == ['CREATE TABLE items (name text)']
59
+ end
60
+
56
61
  it "should reload the schema from the database" do
57
62
  schem = {:name=>{:type=>:string}, :price=>{:type=>:float}}
58
63
  @model.db.should_receive(:schema).with(:items, :reload=>true).and_return(schem.to_a.sort_by{|x| x[0].to_s})
@@ -71,7 +71,7 @@ describe "Serialization plugin" do
71
71
  @c.create(:ghi => [1])
72
72
  @c.create(:ghi => ["hello"])
73
73
 
74
- x = JSON.generate ["hello"]
74
+ x = ["hello"].to_json
75
75
  MODEL_DB.sqls.should == [ \
76
76
  "INSERT INTO items (ghi) VALUES ('[1]')", \
77
77
  "INSERT INTO items (ghi) VALUES ('#{x}')", \
@@ -129,7 +129,7 @@ describe "Serialization plugin" do
129
129
 
130
130
  ds = @c.dataset
131
131
  def ds.fetch_rows(sql, &block)
132
- block.call(:id => 1, :abc => JSON.generate([1]), :def => JSON.generate(["hello"]))
132
+ block.call(:id => 1, :abc => [1].to_json, :def => ["hello"].to_json)
133
133
  end
134
134
 
135
135
  o = @c.first
@@ -142,8 +142,8 @@ describe "Serialization plugin" do
142
142
  o.update(:abc => [23])
143
143
  @c.create(:abc => [1,2,3])
144
144
 
145
- MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{JSON.generate([23])}' WHERE (id = 1)",
146
- "INSERT INTO items (abc) VALUES ('#{JSON.generate([1,2,3])}')"]
145
+ MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{[23].to_json}' WHERE (id = 1)",
146
+ "INSERT INTO items (abc) VALUES ('#{[1,2,3].to_json}')"]
147
147
  end
148
148
 
149
149
  it "should copy serialization formats and columns to subclasses" do
@@ -88,6 +88,13 @@ describe Sequel::Model, "#sti_key" do
88
88
  MODEL_DB.sqls.should == ["INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')"]
89
89
  end
90
90
 
91
+ it "should have the before_create hook handle columns with the same name as existing method names" do
92
+ StiTest.plugin :single_table_inheritance, :type
93
+ StiTest.columns :id, :type
94
+ StiTest.create
95
+ MODEL_DB.sqls.should == ["INSERT INTO sti_tests (type) VALUES ('StiTest')"]
96
+ end
97
+
91
98
  it "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
92
99
  StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
93
100
  StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1'))"
@@ -103,6 +103,15 @@ describe Sequel::Model, "tree plugin" do
103
103
  "SELECT * FROM nodes WHERE (nodes.id = 5) LIMIT 1"]
104
104
  end
105
105
 
106
+ it "should have root? return true for a root node and false for a child node" do
107
+ @c.load(:parent_id => nil).root?.should be_true
108
+ @c.load(:parent_id => 1).root?.should be_false
109
+ end
110
+
111
+ it "should have root? return false for an new node" do
112
+ @c.new.root?.should be_false
113
+ end
114
+
106
115
  it "should have self_and_siblings return the children of the current node's parent" do
107
116
  y(@c, [{:id=>1, :parent_id=>3, :name=>'r'}], [{:id=>7, :parent_id=>1, :name=>'r2'}, @o.values.dup])
108
117
  @o.self_and_siblings.should == [@c.load(:id=>7, :parent_id=>1, :name=>'r2'), @o]
@@ -116,4 +125,31 @@ describe Sequel::Model, "tree plugin" do
116
125
  @db.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 1) LIMIT 1",
117
126
  "SELECT * FROM nodes WHERE (nodes.parent_id = 1)"]
118
127
  end
128
+
129
+ describe ":single_root option" do
130
+ before do
131
+ @c = klass(:single_root => true)
132
+ end
133
+
134
+ it "should have root class method return the root" do
135
+ y(@c, [{:id=>1, :parent_id=>nil, :name=>'r'}])
136
+ @c.root.should == @c.load(:id=>1, :parent_id=>nil, :name=>'r')
137
+ end
138
+
139
+ it "prevents creating a second root" do
140
+ y(@c, [{:id=>1, :parent_id=>nil, :name=>'r'}])
141
+ lambda { @c.create }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
142
+ end
143
+
144
+ it "errors when promoting an existing record to a second root" do
145
+ y(@c, [{:id=>1, :parent_id=>nil, :name=>'r'}])
146
+ n = @c.load(:id => 2, :parent_id => 1)
147
+ lambda { n.update(:parent_id => nil) }.should raise_error(Sequel::Plugins::Tree::TreeMultipleRootError)
148
+ end
149
+
150
+ it "allows updating existing root" do
151
+ y(@c, [{:id=>1, :parent_id=>nil, :name=>'r'}])
152
+ lambda { @c.root.update(:name => 'fdsa') }.should_not raise_error
153
+ end
154
+ end
119
155
  end
@@ -869,6 +869,25 @@ describe "Dataset string methods" do
869
869
  @ds.grep([:a, :b], %w'boo far').all.should == []
870
870
  end
871
871
 
872
+ it "#grep should work with :all_patterns and :all_columns options" do
873
+ @ds.insert('foo bar', '')
874
+ @ds.insert('foo d', 'bar')
875
+ @ds.insert('foo e', '')
876
+ @ds.insert('', 'bar')
877
+ @ds.insert('foo f', 'baz')
878
+ @ds.insert('foo baz', 'bar baz')
879
+ @ds.insert('foo boo', 'boo foo')
880
+
881
+ @ds.grep([:a, :b], %w'%foo% %bar%', :all_patterns=>true).all.should == [{:a=>'foo bar', :b=>''}, {:a=>'foo baz', :b=>'bar baz'}, {:a=>'foo d', :b=>'bar'}]
882
+ @ds.grep([:a, :b], %w'%foo% %bar% %blob%', :all_patterns=>true).all.should == []
883
+
884
+ @ds.grep([:a, :b], %w'%bar% %foo%', :all_columns=>true).all.should == [{:a=>"foo baz", :b=>"bar baz"}, {:a=>"foo boo", :b=>"boo foo"}, {:a=>"foo d", :b=>"bar"}]
885
+ @ds.grep([:a, :b], %w'%baz%', :all_columns=>true).all.should == [{:a=>'foo baz', :b=>'bar baz'}]
886
+
887
+ @ds.grep([:a, :b], %w'%baz% %foo%', :all_columns=>true, :all_patterns=>true).all.should == []
888
+ @ds.grep([:a, :b], %w'%boo% %foo%', :all_columns=>true, :all_patterns=>true).all.should == [{:a=>'foo boo', :b=>'boo foo'}]
889
+ end
890
+
872
891
  it "#like should return matching rows" do
873
892
  @ds.insert('foo', 'bar')
874
893
  @ds.filter(:a.like('foo')).all.should == [{:a=>'foo', :b=>'bar'}]
@@ -118,7 +118,7 @@ describe "Prepared Statements and Bound Arguments" do
118
118
  INTEGRATION_DB.call(:select_n, :n=>10).should == [{:id=>1, :number=>10}]
119
119
  @ds.filter(:number=>@ds.ba(:$n)).prepare(:first, :select_n)
120
120
  INTEGRATION_DB.call(:select_n, :n=>10).should == {:id=>1, :number=>10}
121
- if INTEGRATION_DB.class.adapter_scheme == :jdbc and INTEGRATION_DB.database_type == :sqlite
121
+ if INTEGRATION_DB.adapter_scheme == :jdbc and INTEGRATION_DB.database_type == :sqlite
122
122
  # Work around for open prepared statements on a table not allowing the
123
123
  # dropping of a table when using SQLite over JDBC
124
124
  INTEGRATION_DB.synchronize{|c| c.prepared_statements[:select_n][1].close}
@@ -204,7 +204,7 @@ describe "Prepared Statements and Bound Arguments" do
204
204
  INTEGRATION_DB.call(:select_n, :n=>10).should == [@c.load(:id=>1, :number=>10)]
205
205
  @c.filter(:number=>@ds.ba(:$n)).prepare(:first, :select_n)
206
206
  INTEGRATION_DB.call(:select_n, :n=>10).should == @c.load(:id=>1, :number=>10)
207
- if INTEGRATION_DB.class.adapter_scheme == :jdbc and INTEGRATION_DB.database_type == :sqlite
207
+ if INTEGRATION_DB.adapter_scheme == :jdbc and INTEGRATION_DB.database_type == :sqlite
208
208
  # Work around for open prepared statements on a table not allowing the
209
209
  # dropping of a table when using SQLite over JDBC
210
210
  INTEGRATION_DB.synchronize{|c| c.prepared_statements[:select_n][1].close}
@@ -53,7 +53,7 @@ describe "Database schema parser" do
53
53
  INTEGRATION_DB.schema(:items)
54
54
  end
55
55
 
56
- cspecify "should parse primary keys from the schema properly", [proc{|db| db.class.adapter_scheme != :jdbc}, :mssql] do
56
+ cspecify "should parse primary keys from the schema properly", [proc{|db| db.adapter_scheme != :jdbc}, :mssql] do
57
57
  INTEGRATION_DB.create_table!(:items){Integer :number}
58
58
  INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
59
59
  INTEGRATION_DB.create_table!(:items){primary_key :number}
@@ -39,17 +39,17 @@ class Spec::Example::ExampleGroup
39
39
  when Array
40
40
  case c.length
41
41
  when 1
42
- pending = c if c.first == INTEGRATION_DB.class.adapter_scheme
42
+ pending = c if c.first == INTEGRATION_DB.adapter_scheme
43
43
  when 2
44
44
  if c.first.is_a?(Proc)
45
45
  pending = c if c.first.call(INTEGRATION_DB) && c.last == INTEGRATION_DB.database_type
46
46
  elsif c.last.is_a?(Proc)
47
- pending = c if c.first == INTEGRATION_DB.class.adapter_scheme && c.last.call(INTEGRATION_DB)
47
+ pending = c if c.first == INTEGRATION_DB.adapter_scheme && c.last.call(INTEGRATION_DB)
48
48
  else
49
- pending = c if c.first == INTEGRATION_DB.class.adapter_scheme && c.last == INTEGRATION_DB.database_type
49
+ pending = c if c.first == INTEGRATION_DB.adapter_scheme && c.last == INTEGRATION_DB.database_type
50
50
  end
51
51
  when 3
52
- pending = c if c[0] == INTEGRATION_DB.class.adapter_scheme && c[1] == INTEGRATION_DB.database_type && c[2].call(INTEGRATION_DB)
52
+ pending = c if c[0] == INTEGRATION_DB.adapter_scheme && c[1] == INTEGRATION_DB.database_type && c[2].call(INTEGRATION_DB)
53
53
  end
54
54
  end
55
55
  break if pending
@@ -2,25 +2,31 @@ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
2
 
3
3
  describe "Sequel timezone support" do
4
4
  def test_timezone
5
- t = Time.now
6
- @db[:t].insert(t)
7
- t2 = @db[:t].single_value
8
- t2 = Sequel.database_to_application_timestamp(t2.to_s) unless t2.is_a?(Time)
9
- (t2 - t).should be_close(0, 2)
10
- t2.utc_offset.should == 0 if Sequel.application_timezone == :utc
11
- t2.utc_offset.should == t.getlocal.utc_offset if Sequel.application_timezone == :local
12
- @db[:t].delete
13
-
14
- dt = DateTime.now
5
+ # Tests should cover both DST and non-DST times.
6
+ [Time.now, Time.local(2010,1,1,12), Time.local(2010,6,1,12)].each do |t|
7
+ @db[:t].insert(t)
8
+ t2 = @db[:t].single_value
9
+ t2 = Sequel.database_to_application_timestamp(t2.to_s) unless t2.is_a?(Time)
10
+ (t2 - t).should be_close(0, 2)
11
+ t2.utc_offset.should == 0 if Sequel.application_timezone == :utc
12
+ t2.utc_offset.should == t.getlocal.utc_offset if Sequel.application_timezone == :local
13
+ @db[:t].delete
14
+ end
15
+
15
16
  Sequel.datetime_class = DateTime
16
- @db[:t].insert(dt)
17
- dt2 = @db[:t].single_value
18
- dt2 = Sequel.database_to_application_timestamp(dt2.to_s) unless dt2.is_a?(DateTime)
19
- (dt2 - dt).should be_close(0, 0.00002)
20
- dt2.offset.should == 0 if Sequel.application_timezone == :utc
21
- dt2.offset.should == dt.offset if Sequel.application_timezone == :local
17
+ local_dst_offset = Rational(Time.local(2010, 6).utc_offset, 60*60*24)
18
+ local_std_offset = Rational(Time.local(2010, 1).utc_offset, 60*60*24)
19
+ [DateTime.now, DateTime.civil(2010,1,1,12,0,0,local_std_offset), DateTime.civil(2010,6,1,12,0,0,local_dst_offset)].each do |dt|
20
+ @db[:t].insert(dt)
21
+ dt2 = @db[:t].single_value
22
+ dt2 = Sequel.database_to_application_timestamp(dt2.to_s) unless dt2.is_a?(DateTime)
23
+ (dt2 - dt).should be_close(0, 0.00002)
24
+ dt2.offset.should == 0 if Sequel.application_timezone == :utc
25
+ dt2.offset.should == dt.offset if Sequel.application_timezone == :local
26
+ @db[:t].delete
27
+ end
22
28
  end
23
-
29
+
24
30
  before do
25
31
  @db = INTEGRATION_DB
26
32
  @db.create_table!(:t){DateTime :t}
@@ -36,19 +42,19 @@ describe "Sequel timezone support" do
36
42
  Sequel.application_timezone = :local
37
43
  test_timezone
38
44
  end
39
-
45
+
40
46
  cspecify "should support using local time for database storage and UTC for the application", [:do, proc{|db| db.database_type != :sqlite}] do
41
47
  Sequel.database_timezone = :local
42
48
  Sequel.application_timezone = :utc
43
49
  test_timezone
44
50
  end
45
-
51
+
46
52
  cspecify "should support using UTC for both database storage and for application", [:do, proc{|db| db.database_type != :sqlite}] do
47
53
  Sequel.default_timezone = :utc
48
54
  test_timezone
49
55
  end
50
-
51
- specify "should support using local time for both database storage and for application" do
56
+
57
+ cspecify "should support using local time for both database storage and for application", [:do, proc{|db| db.database_type != :sqlite}] do
52
58
  Sequel.default_timezone = :local
53
59
  test_timezone
54
60
  end