sequel 3.13.0 → 3.14.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 (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