sequel 3.1.0 → 3.2.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 (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -1,5 +1,8 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
+ Regexp.send(:include, Sequel::SQL::StringMethods)
4
+ String.send(:include, Sequel::SQL::StringMethods)
5
+
3
6
  context "Blockless Ruby Filters" do
4
7
  before do
5
8
  db = Sequel::Database.new
@@ -48,6 +51,14 @@ context "Blockless Ruby Filters" do
48
51
  @d.l(:x => nil).should == '(x IS NULL)'
49
52
  @d.l(:x => [1,2,3]).should == '(x IN (1, 2, 3))'
50
53
  end
54
+
55
+ it "should use = 't' and != 't' OR IS NULL if IS TRUE is not supported" do
56
+ @d.meta_def(:supports_is_true?){false}
57
+ @d.l(:x => true).should == "(x = 't')"
58
+ @d.l(~{:x => true}).should == "((x != 't') OR (x IS NULL))"
59
+ @d.l(:x => false).should == "(x = 'f')"
60
+ @d.l(~{:x => false}).should == "((x != 'f') OR (x IS NULL))"
61
+ end
51
62
 
52
63
  it "should support != via Hash#~" do
53
64
  @d.l(~{:x => 100}).should == '(x != 100)'
@@ -71,6 +82,21 @@ context "Blockless Ruby Filters" do
71
82
  @d.l(:x.like('a', 'b')).should == '((x LIKE \'a\') OR (x LIKE \'b\'))'
72
83
  @d.l(:x.like(/a/, /b/i)).should == '((x ~ \'a\') OR (x ~* \'b\'))'
73
84
  @d.l(:x.like('a', /b/)).should == '((x LIKE \'a\') OR (x ~ \'b\'))'
85
+
86
+ @d.l('a'.like(:x)).should == "('a' LIKE x)"
87
+ @d.l('a'.like(:x, 'b')).should == "(('a' LIKE x) OR ('a' LIKE 'b'))"
88
+ @d.l('a'.like(:x, /b/)).should == "(('a' LIKE x) OR ('a' ~ 'b'))"
89
+ @d.l('a'.like(:x, /b/i)).should == "(('a' LIKE x) OR ('a' ~* 'b'))"
90
+
91
+ @d.l(/a/.like(:x)).should == "('a' ~ x)"
92
+ @d.l(/a/.like(:x, 'b')).should == "(('a' ~ x) OR ('a' ~ 'b'))"
93
+ @d.l(/a/.like(:x, /b/)).should == "(('a' ~ x) OR ('a' ~ 'b'))"
94
+ @d.l(/a/.like(:x, /b/i)).should == "(('a' ~ x) OR ('a' ~* 'b'))"
95
+
96
+ @d.l(/a/i.like(:x)).should == "('a' ~* x)"
97
+ @d.l(/a/i.like(:x, 'b')).should == "(('a' ~* x) OR ('a' ~* 'b'))"
98
+ @d.l(/a/i.like(:x, /b/)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
99
+ @d.l(/a/i.like(:x, /b/i)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
74
100
  end
75
101
 
76
102
  it "should support NOT LIKE via Symbol#like and Symbol#~" do
@@ -79,6 +105,21 @@ context "Blockless Ruby Filters" do
79
105
  @d.l(~:x.like('a', 'b')).should == '((x NOT LIKE \'a\') AND (x NOT LIKE \'b\'))'
80
106
  @d.l(~:x.like(/a/, /b/i)).should == '((x !~ \'a\') AND (x !~* \'b\'))'
81
107
  @d.l(~:x.like('a', /b/)).should == '((x NOT LIKE \'a\') AND (x !~ \'b\'))'
108
+
109
+ @d.l(~'a'.like(:x)).should == "('a' NOT LIKE x)"
110
+ @d.l(~'a'.like(:x, 'b')).should == "(('a' NOT LIKE x) AND ('a' NOT LIKE 'b'))"
111
+ @d.l(~'a'.like(:x, /b/)).should == "(('a' NOT LIKE x) AND ('a' !~ 'b'))"
112
+ @d.l(~'a'.like(:x, /b/i)).should == "(('a' NOT LIKE x) AND ('a' !~* 'b'))"
113
+
114
+ @d.l(~/a/.like(:x)).should == "('a' !~ x)"
115
+ @d.l(~/a/.like(:x, 'b')).should == "(('a' !~ x) AND ('a' !~ 'b'))"
116
+ @d.l(~/a/.like(:x, /b/)).should == "(('a' !~ x) AND ('a' !~ 'b'))"
117
+ @d.l(~/a/.like(:x, /b/i)).should == "(('a' !~ x) AND ('a' !~* 'b'))"
118
+
119
+ @d.l(~/a/i.like(:x)).should == "('a' !~* x)"
120
+ @d.l(~/a/i.like(:x, 'b')).should == "(('a' !~* x) AND ('a' !~* 'b'))"
121
+ @d.l(~/a/i.like(:x, /b/)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
122
+ @d.l(~/a/i.like(:x, /b/i)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
82
123
  end
83
124
 
84
125
  it "should support ILIKE via Symbol#ilike" do
@@ -87,6 +128,21 @@ context "Blockless Ruby Filters" do
87
128
  @d.l(:x.ilike('a', 'b')).should == '((x ILIKE \'a\') OR (x ILIKE \'b\'))'
88
129
  @d.l(:x.ilike(/a/, /b/i)).should == '((x ~* \'a\') OR (x ~* \'b\'))'
89
130
  @d.l(:x.ilike('a', /b/)).should == '((x ILIKE \'a\') OR (x ~* \'b\'))'
131
+
132
+ @d.l('a'.ilike(:x)).should == "('a' ILIKE x)"
133
+ @d.l('a'.ilike(:x, 'b')).should == "(('a' ILIKE x) OR ('a' ILIKE 'b'))"
134
+ @d.l('a'.ilike(:x, /b/)).should == "(('a' ILIKE x) OR ('a' ~* 'b'))"
135
+ @d.l('a'.ilike(:x, /b/i)).should == "(('a' ILIKE x) OR ('a' ~* 'b'))"
136
+
137
+ @d.l(/a/.ilike(:x)).should == "('a' ~* x)"
138
+ @d.l(/a/.ilike(:x, 'b')).should == "(('a' ~* x) OR ('a' ~* 'b'))"
139
+ @d.l(/a/.ilike(:x, /b/)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
140
+ @d.l(/a/.ilike(:x, /b/i)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
141
+
142
+ @d.l(/a/i.ilike(:x)).should == "('a' ~* x)"
143
+ @d.l(/a/i.ilike(:x, 'b')).should == "(('a' ~* x) OR ('a' ~* 'b'))"
144
+ @d.l(/a/i.ilike(:x, /b/)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
145
+ @d.l(/a/i.ilike(:x, /b/i)).should == "(('a' ~* x) OR ('a' ~* 'b'))"
90
146
  end
91
147
 
92
148
  it "should support NOT ILIKE via Symbol#ilike and Symbol#~" do
@@ -95,6 +151,21 @@ context "Blockless Ruby Filters" do
95
151
  @d.l(~:x.ilike('a', 'b')).should == '((x NOT ILIKE \'a\') AND (x NOT ILIKE \'b\'))'
96
152
  @d.l(~:x.ilike(/a/, /b/i)).should == '((x !~* \'a\') AND (x !~* \'b\'))'
97
153
  @d.l(~:x.ilike('a', /b/)).should == '((x NOT ILIKE \'a\') AND (x !~* \'b\'))'
154
+
155
+ @d.l(~'a'.ilike(:x)).should == "('a' NOT ILIKE x)"
156
+ @d.l(~'a'.ilike(:x, 'b')).should == "(('a' NOT ILIKE x) AND ('a' NOT ILIKE 'b'))"
157
+ @d.l(~'a'.ilike(:x, /b/)).should == "(('a' NOT ILIKE x) AND ('a' !~* 'b'))"
158
+ @d.l(~'a'.ilike(:x, /b/i)).should == "(('a' NOT ILIKE x) AND ('a' !~* 'b'))"
159
+
160
+ @d.l(~/a/.ilike(:x)).should == "('a' !~* x)"
161
+ @d.l(~/a/.ilike(:x, 'b')).should == "(('a' !~* x) AND ('a' !~* 'b'))"
162
+ @d.l(~/a/.ilike(:x, /b/)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
163
+ @d.l(~/a/.ilike(:x, /b/i)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
164
+
165
+ @d.l(~/a/i.ilike(:x)).should == "('a' !~* x)"
166
+ @d.l(~/a/i.ilike(:x, 'b')).should == "(('a' !~* x) AND ('a' !~* 'b'))"
167
+ @d.l(~/a/i.ilike(:x, /b/)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
168
+ @d.l(~/a/i.ilike(:x, /b/i)).should == "(('a' !~* x) AND ('a' !~* 'b'))"
98
169
  end
99
170
 
100
171
  it "should support negating ranges via Hash#~ and Range" do
@@ -373,3 +444,88 @@ context "Blockless Ruby Filters" do
373
444
  end
374
445
  end
375
446
  end
447
+
448
+ context Sequel::SQL::VirtualRow do
449
+ before do
450
+ db = Sequel::Database.new
451
+ db.quote_identifiers = true
452
+ @d = db[:items]
453
+ @d.meta_def(:supports_window_functions?){true}
454
+ def @d.l(*args, &block)
455
+ literal(filter_expr(*args, &block))
456
+ end
457
+ end
458
+
459
+ it "should treat methods without arguments as identifiers" do
460
+ @d.l{column}.should == '"column"'
461
+ end
462
+
463
+ it "should treat methods without arguments that have embedded double underscores as qualified identifiers" do
464
+ @d.l{table__column}.should == '"table"."column"'
465
+ end
466
+
467
+ it "should treat methods with arguments as functions with the arguments" do
468
+ @d.l{function(arg1, 10, 'arg3')}.should == 'function("arg1", 10, \'arg3\')'
469
+ end
470
+
471
+ it "should treat methods with a block and no arguments as a function call with no arguments" do
472
+ @d.l{version{}}.should == 'version()'
473
+ end
474
+
475
+ it "should treat methods with a block and a leading argument :* as a function call with the SQL wildcard" do
476
+ @d.l{count(:*){}}.should == 'count(*)'
477
+ end
478
+
479
+ it "should treat methods with a block and a leading argument :distinct as a function call with DISTINCT and the additional method arguments" do
480
+ @d.l{count(:distinct, column1){}}.should == 'count(DISTINCT "column1")'
481
+ @d.l{count(:distinct, column1, column2){}}.should == 'count(DISTINCT "column1", "column2")'
482
+ end
483
+
484
+ it "should raise an error if an unsupported argument is used with a block" do
485
+ proc{@d.l{count(:blah){}}}.should raise_error(Sequel::Error)
486
+ end
487
+
488
+ it "should treat methods with a block and a leading argument :over as a window function call" do
489
+ @d.l{rank(:over){}}.should == 'rank() OVER ()'
490
+ end
491
+
492
+ it "should support :partition options for window function calls" do
493
+ @d.l{rank(:over, :partition=>column1){}}.should == 'rank() OVER (PARTITION BY "column1")'
494
+ @d.l{rank(:over, :partition=>[column1, column2]){}}.should == 'rank() OVER (PARTITION BY "column1", "column2")'
495
+ end
496
+
497
+ it "should support :args options for window function calls" do
498
+ @d.l{avg(:over, :args=>column1){}}.should == 'avg("column1") OVER ()'
499
+ @d.l{avg(:over, :args=>[column1, column2]){}}.should == 'avg("column1", "column2") OVER ()'
500
+ end
501
+
502
+ it "should support :order option for window function calls" do
503
+ @d.l{rank(:over, :order=>column1){}}.should == 'rank() OVER (ORDER BY "column1")'
504
+ @d.l{rank(:over, :order=>[column1, column2]){}}.should == 'rank() OVER (ORDER BY "column1", "column2")'
505
+ end
506
+
507
+ it "should support :window option for window function calls" do
508
+ @d.l{rank(:over, :window=>:win){}}.should == 'rank() OVER ("win")'
509
+ end
510
+
511
+ it "should support :*=>true option for window function calls" do
512
+ @d.l{count(:over, :* =>true){}}.should == 'count(*) OVER ()'
513
+ end
514
+
515
+ it "should support :frame=>:all option for window function calls" do
516
+ @d.l{rank(:over, :frame=>:all){}}.should == 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)'
517
+ end
518
+
519
+ it "should support :frame=>:rows option for window function calls" do
520
+ @d.l{rank(:over, :frame=>:rows){}}.should == 'rank() OVER (ROWS UNBOUNDED PRECEDING)'
521
+ end
522
+
523
+ it "should support all these options together" do
524
+ @d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.should == 'count(*) OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS UNBOUNDED PRECEDING)'
525
+ end
526
+
527
+ it "should raise an error if window functions are not supported" do
528
+ @d.meta_def(:supports_window_functions?){false}
529
+ proc{@d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}}.should raise_error(Sequel::Error)
530
+ end
531
+ end
@@ -30,7 +30,18 @@ describe Sequel::Dataset, " graphing" do
30
30
 
31
31
  it "#graph should accept a complex dataset and pass it directly to join" do
32
32
  ds = @ds1.graph(@ds2.filter(:x=>1), {:x=>:id})
33
- ds.sql.should == 'SELECT points.id, points.x, points.y, lines.id AS lines_id, lines.x AS lines_x, lines.y AS lines_y, lines.graph_id FROM points LEFT OUTER JOIN (SELECT * FROM lines WHERE (x = 1)) AS lines ON (lines.x = points.id)'
33
+ ds.sql.should == 'SELECT points.id, points.x, points.y, t1.id AS t1_id, t1.x AS t1_x, t1.y AS t1_y, t1.graph_id FROM points LEFT OUTER JOIN (SELECT * FROM lines WHERE (x = 1)) AS t1 ON (t1.x = points.id)'
34
+ end
35
+
36
+ it "#graph should work on from_self datasets" do
37
+ ds = @ds1.from_self.graph(@ds2, :x=>:id)
38
+ ds.sql.should == 'SELECT t1.id, t1.x, t1.y, lines.id AS lines_id, lines.x AS lines_x, lines.y AS lines_y, lines.graph_id FROM (SELECT * FROM points) AS t1 LEFT OUTER JOIN lines ON (lines.x = t1.id)'
39
+ ds = @ds1.graph(@ds2.from_self, :x=>:id)
40
+ ds.sql.should == 'SELECT points.id, points.x, points.y, t1.id AS t1_id, t1.x AS t1_x, t1.y AS t1_y, t1.graph_id FROM points LEFT OUTER JOIN (SELECT * FROM (SELECT * FROM lines) AS t1) AS t1 ON (t1.x = points.id)'
41
+ ds = @ds1.from_self.from_self.graph(@ds2.from_self.from_self, :x=>:id)
42
+ ds.sql.should == 'SELECT t1.id, t1.x, t1.y, t2.id AS t2_id, t2.x AS t2_x, t2.y AS t2_y, t2.graph_id FROM (SELECT * FROM (SELECT * FROM points) AS t1) AS t1 LEFT OUTER JOIN (SELECT * FROM (SELECT * FROM (SELECT * FROM lines) AS t1) AS t1) AS t2 ON (t2.x = t1.id)'
43
+ ds = @ds1.from(@ds1, @ds3).graph(@ds2.from_self, :x=>:id)
44
+ ds.sql.should == 'SELECT t1.id, t1.x, t1.y, t3.id AS t3_id, t3.x AS t3_x, t3.y AS t3_y, t3.graph_id FROM (SELECT * FROM points) AS t1, (SELECT * FROM graphs) AS t2 LEFT OUTER JOIN (SELECT * FROM (SELECT * FROM lines) AS t1) AS t3 ON (t3.x = t1.id)'
34
45
  end
35
46
 
36
47
  it "#graph should accept a symbol table name as the dataset" do
@@ -187,6 +198,14 @@ describe Sequel::Dataset, " graphing" do
187
198
  results.first.should == {:points=>{:id=>1, :x=>2, :y=>3}, :lines=>{:id=>4, :x=>5, :y=>6, :graph_id=>7}, :graph=>{:id=>8, :x=>9, :y=>10, :graph_id=>11}}
188
199
  end
189
200
 
201
+ it "#ungraphed should remove the splitting of result sets into component tables" do
202
+ ds = @ds1.graph(@ds2, :x=>:id).ungraphed
203
+ def ds.fetch_rows(sql, &block)
204
+ yield({:id=>1,:x=>2,:y=>3,:lines_id=>4,:lines_x=>5,:lines_y=>6,:graph_id=>7})
205
+ end
206
+ ds.all.should == [{:id=>1,:x=>2,:y=>3,:lines_id=>4,:lines_x=>5,:lines_y=>6,:graph_id=>7}]
207
+ end
208
+
190
209
  it "#graph_each should give a nil value instead of a hash when all values for a table are nil" do
191
210
  ds = @ds1.graph(@ds2, :x=>:id)
192
211
  def ds.fetch_rows(sql, &block)
@@ -762,11 +762,11 @@ context "Schema Parser" do
762
762
  sqls << t
763
763
  [[:a, {:db_type=>t.to_s}]]
764
764
  end
765
- @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
765
+ @db.schema(:x).should == [[:a, {:db_type=>"x", :ruby_default=>nil}]]
766
766
  @sqls.should == ['x']
767
- @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
767
+ @db.schema(:x).should == [[:a, {:db_type=>"x", :ruby_default=>nil}]]
768
768
  @sqls.should == ['x']
769
- @db.schema(:x, :reload=>true).should == [[:a, {:db_type=>"x"}]]
769
+ @db.schema(:x, :reload=>true).should == [[:a, {:db_type=>"x", :ruby_default=>nil}]]
770
770
  @sqls.should == ['x', 'x']
771
771
  end
772
772
 
@@ -775,11 +775,11 @@ context "Schema Parser" do
775
775
  [[t, {:db_type=>t}]]
776
776
  end
777
777
  s1 = @db.schema(:x)
778
- s1.should == [['x', {:db_type=>'x'}]]
778
+ s1.should == [['x', {:db_type=>'x', :ruby_default=>nil}]]
779
779
  @db.schema(:x).object_id.should == s1.object_id
780
780
  @db.schema(:x.identifier).object_id.should == s1.object_id
781
781
  s2 = @db.schema(:x__y)
782
- s2.should == [['y', {:db_type=>'y'}]]
782
+ s2.should == [['y', {:db_type=>'y', :ruby_default=>nil}]]
783
783
  @db.schema(:x__y).object_id.should == s2.object_id
784
784
  @db.schema(:y.qualify(:x)).object_id.should == s2.object_id
785
785
  end
@@ -47,39 +47,6 @@ context "Migration#apply" do
47
47
  end
48
48
  end
49
49
 
50
- class DummyMigrationDataset
51
- attr_reader :from
52
-
53
- def initialize(x); @from = x; end
54
-
55
- @@version = nil
56
-
57
- def version; @@version; end
58
- def version=(x); @@version = x; end
59
- def first; @@version ? {:version => @@version} : nil; end
60
- def update(h); @@version = h[:version]; end
61
- def <<(h); @@version = h[:version]; end
62
- end
63
-
64
- class DummyMigrationDB
65
- attr_reader :creates, :drops, :table_created
66
-
67
- def initialize
68
- @creates = []
69
- @drops = []
70
- end
71
-
72
- def create(x); @creates << x; end
73
- def drop(x); @drops << x; end
74
-
75
- def [](x); DummyMigrationDataset.new(x); end
76
-
77
- def create_table(x); raise if @table_created == x; @table_created = x; end
78
- def table_exists?(x); @table_created == x; end
79
-
80
- def transaction; yield; end
81
- end
82
-
83
50
  MIGRATION_001 = %[
84
51
  class CreateSessions < Sequel::Migration
85
52
  def up
@@ -128,16 +95,76 @@ MIGRATION_005 = %[
128
95
  end
129
96
  ]
130
97
 
98
+ ALT_MIGRATION_001 = %[
99
+ class CreateAltBasic < Sequel::Migration
100
+ def up
101
+ create(11111)
102
+ end
103
+
104
+ def down
105
+ drop(11111)
106
+ end
107
+ end
108
+ ]
109
+
110
+ ALT_MIGRATION_003 = %[
111
+ class CreateAltAdvanced < Sequel::Migration
112
+ def up
113
+ create(33333)
114
+ end
115
+
116
+ def down
117
+ drop(33333)
118
+ end
119
+ end
120
+ ]
121
+
131
122
  context "Sequel::Migrator" do
132
123
  before do
133
- @db = DummyMigrationDB.new
124
+ dbc = Class.new(MockDatabase) do
125
+ attr_reader :creates, :drops, :tables_created, :columns_created, :versions
126
+ def initialize(*args)
127
+ super
128
+ @creates = []
129
+ @drops = []
130
+ @tables_created = []
131
+ @columns_created = []
132
+ @versions = {}
133
+ end
134
+
135
+ def create(x); @creates << x; end
136
+ def drop(x); @drops << x; end
137
+
138
+ def create_table(name, opts={}, &block)
139
+ super
140
+ @columns_created << / \(?(\w+) integer\)?\z/.match(sqls.last)[1].to_sym
141
+ @tables_created << name
142
+ end
143
+
144
+ def dataset(opts={})
145
+ ds = super
146
+ ds.extend(Module.new do
147
+ def columns; db.columns_created end
148
+ def insert(h); db.versions.merge!(h); super(h) end
149
+ def update(h); db.versions.merge!(h); super(h) end
150
+ def fetch_rows(sql); db.execute(sql); yield(db.versions) unless db.versions.empty? end
151
+ end)
152
+ ds
153
+ end
154
+
155
+ def table_exists?(name)
156
+ @tables_created.include?(name)
157
+ end
158
+ end
159
+ @db = dbc.new
134
160
 
135
161
  File.open('001_create_sessions.rb', 'w') {|f| f << MIGRATION_001}
136
162
  File.open('002_create_nodes.rb', 'w') {|f| f << MIGRATION_002}
137
163
  File.open('003_create_users.rb', 'w') {|f| f << MIGRATION_003}
138
164
  File.open('005_5_create_attributes.rb', 'w') {|f| f << MIGRATION_005}
139
-
140
- @db[:schema_info].version = nil
165
+ Dir.mkdir("alt_app")
166
+ File.open('alt_app/001_create_alt_basic.rb', 'w') {|f| f << ALT_MIGRATION_001}
167
+ File.open('alt_app/003_create_alt_advanced.rb', 'w') {|f| f << ALT_MIGRATION_003}
141
168
  end
142
169
 
143
170
  after do
@@ -145,77 +172,97 @@ context "Sequel::Migrator" do
145
172
  Object.send(:remove_const, "CreateNodes") if Object.const_defined?("CreateNodes")
146
173
  Object.send(:remove_const, "CreateUsers") if Object.const_defined?("CreateUsers")
147
174
  Object.send(:remove_const, "CreateAttributes") if Object.const_defined?("CreateAttributes")
175
+ Object.send(:remove_const, "CreateAltBasic") if Object.const_defined?("CreateAltBasic")
176
+ Object.send(:remove_const, "CreateAltAdvanced") if Object.const_defined?("CreateAltAdvanced")
148
177
 
149
178
  File.delete('001_create_sessions.rb')
150
179
  File.delete('002_create_nodes.rb')
151
180
  File.delete('003_create_users.rb')
152
181
  File.delete('005_5_create_attributes.rb')
182
+ File.delete("alt_app/001_create_alt_basic.rb")
183
+ File.delete("alt_app/003_create_alt_advanced.rb")
184
+ Dir.rmdir("alt_app")
153
185
  end
154
186
 
155
- specify "should return the list of files for a specified version range" do
156
- Sequel::Migrator.migration_files('.', 1..1).should == \
157
- ['./001_create_sessions.rb']
158
-
159
- Sequel::Migrator.migration_files('.', 1..3).should == \
160
- ['./001_create_sessions.rb', './002_create_nodes.rb', './003_create_users.rb']
161
-
162
- Sequel::Migrator.migration_files('.', 3..6).should == \
163
- ['./003_create_users.rb', './005_5_create_attributes.rb']
164
-
187
+ specify "#migration_files should return the list of files for a specified version range" do
188
+ Sequel::Migrator.migration_files('.', 1..1).should == ['./001_create_sessions.rb']
189
+ Sequel::Migrator.migration_files('.', 1..3).should == ['./001_create_sessions.rb', './002_create_nodes.rb', './003_create_users.rb']
190
+ Sequel::Migrator.migration_files('.', 3..6).should == ['./003_create_users.rb', './005_5_create_attributes.rb']
165
191
  Sequel::Migrator.migration_files('.', 7..8).should == []
192
+ Sequel::Migrator.migration_files('alt_app', 1..1).should == ['alt_app/001_create_alt_basic.rb']
193
+ Sequel::Migrator.migration_files('alt_app', 1..3).should == ['alt_app/001_create_alt_basic.rb','alt_app/003_create_alt_advanced.rb']
166
194
  end
167
195
 
168
- specify "should return the latest version available" do
196
+ specify "#latest_migration_version should return the latest version available" do
169
197
  Sequel::Migrator.latest_migration_version('.').should == 5
198
+ Sequel::Migrator.latest_migration_version('alt_app').should == 3
170
199
  end
171
200
 
172
- specify "should load the migration classes for the specified range" do
173
- Sequel::Migrator.migration_classes('.', 3, 0, :up).should == \
174
- [CreateSessions, CreateNodes, CreateUsers]
201
+ specify "#migration_classes should load the migration classes for the specified range for the up direction" do
202
+ Sequel::Migrator.migration_classes('.', 3, 0, :up).should == [CreateSessions, CreateNodes, CreateUsers]
203
+ Sequel::Migrator.migration_classes('alt_app', 3, 0, :up).should == [CreateAltBasic, CreateAltAdvanced]
175
204
  end
176
205
 
177
- specify "should load the migration classes for the specified range" do
178
- Sequel::Migrator.migration_classes('.', 0, 5, :down).should == \
179
- [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
206
+ specify "#migration_classes should load the migration classes for the specified range for the down direction" do
207
+ Sequel::Migrator.migration_classes('.', 0, 5, :down).should == [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
208
+ Sequel::Migrator.migration_classes('alt_app', 0, 3, :down).should == [CreateAltAdvanced, CreateAltBasic]
180
209
  end
181
210
 
182
- specify "should start from current + 1 for the up direction" do
183
- Sequel::Migrator.migration_classes('.', 3, 1, :up).should == \
184
- [CreateNodes, CreateUsers]
211
+ specify "#migration_classes should start from current + 1 for the up direction" do
212
+ Sequel::Migrator.migration_classes('.', 3, 1, :up).should == [CreateNodes, CreateUsers]
213
+ Sequel::Migrator.migration_classes('alt_app', 3, 2, :up).should == [CreateAltAdvanced]
185
214
  end
186
215
 
187
- specify "should end on current + 1 for the down direction" do
188
- Sequel::Migrator.migration_classes('.', 2, 5, :down).should == \
189
- [CreateAttributes, CreateUsers]
216
+ specify "#migration_classes should end on current + 1 for the down direction" do
217
+ Sequel::Migrator.migration_classes('.', 2, 5, :down).should == [CreateAttributes, CreateUsers]
218
+ Sequel::Migrator.migration_classes('alt_app', 2, 4, :down).should == [CreateAltAdvanced]
190
219
  end
191
220
 
192
- specify "should automatically create the schema_info table" do
221
+ specify "#schema_info_dataset should automatically create the schema_info table" do
193
222
  @db.table_exists?(:schema_info).should be_false
194
223
  Sequel::Migrator.schema_info_dataset(@db)
195
224
  @db.table_exists?(:schema_info).should be_true
196
-
197
- # should not raise if table already exists
198
- proc {Sequel::Migrator.schema_info_dataset(@db)}.should_not raise_error
225
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)"]
226
+ end
227
+
228
+ specify "should automatically create new APP_version column in schema_info" do
229
+ @db.table_exists?(:schema_info).should be_false
230
+ Sequel::Migrator.schema_info_dataset(@db, :column => :alt_version)
231
+ @db.table_exists?(:schema_info).should be_true
232
+ @db.sqls.should == ["CREATE TABLE schema_info (alt_version integer)"]
233
+ end
234
+
235
+ specify "should automatically create new APP_version column in schema_info" do
236
+ @db.table_exists?(:alt_table).should be_false
237
+ Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table)
238
+ @db.table_exists?(:alt_table).should be_true
239
+ Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table, :column=>:alt_version)
240
+ @db.sqls.should == ["CREATE TABLE alt_table (version integer)",
241
+ "ALTER TABLE alt_table ADD COLUMN alt_version integer"]
199
242
  end
200
243
 
201
- specify "should return a dataset for the schema_info table" do
202
- d = Sequel::Migrator.schema_info_dataset(@db)
203
- d.from.should == :schema_info
244
+ specify "should return a dataset for the correct table" do
245
+ Sequel::Migrator.schema_info_dataset(@db).first_source_alias.should == :schema_info
246
+ Sequel::Migrator.schema_info_dataset(@db, :table=>:blah).first_source_alias.should == :blah
204
247
  end
205
248
 
206
- specify "should get the migration version stored in the database" do
207
- # default is 0
249
+ specify "should assume a migration version of 0 if no migration information exists in the database" do
208
250
  Sequel::Migrator.get_current_migration_version(@db).should == 0
209
-
210
- Sequel::Migrator.schema_info_dataset(@db) << {:version => 4321}
211
-
251
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "SELECT * FROM schema_info LIMIT 1"]
252
+ end
253
+ specify "should use the migration version stored in the database" do
254
+ Sequel::Migrator.schema_info_dataset(@db).insert(:version => 4321)
212
255
  Sequel::Migrator.get_current_migration_version(@db).should == 4321
256
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "INSERT INTO schema_info (version) VALUES (4321)", "SELECT * FROM schema_info LIMIT 1"]
213
257
  end
214
258
 
215
259
  specify "should set the migration version stored in the database" do
216
- Sequel::Migrator.get_current_migration_version(@db).should == 0
217
260
  Sequel::Migrator.set_current_migration_version(@db, 6666)
218
261
  Sequel::Migrator.get_current_migration_version(@db).should == 6666
262
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
263
+ "SELECT * FROM schema_info LIMIT 1",
264
+ "INSERT INTO schema_info (version) VALUES (6666)",
265
+ "SELECT * FROM schema_info LIMIT 1"]
219
266
  end
220
267
 
221
268
  specify "should apply migrations correctly in the up direction" do
@@ -228,6 +275,14 @@ context "Sequel::Migrator" do
228
275
  @db.creates.should == [3333, 5555]
229
276
 
230
277
  Sequel::Migrator.get_current_migration_version(@db).should == 5
278
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
279
+ "SELECT * FROM schema_info LIMIT 1",
280
+ "INSERT INTO schema_info (version) VALUES (3)",
281
+ "SELECT * FROM schema_info LIMIT 1",
282
+ "SELECT * FROM schema_info LIMIT 1",
283
+ "SELECT * FROM schema_info LIMIT 1",
284
+ "UPDATE schema_info SET version = 5",
285
+ "SELECT * FROM schema_info LIMIT 1"]
231
286
  end
232
287
 
233
288
  specify "should apply migrations correctly in the down direction" do
@@ -235,6 +290,10 @@ context "Sequel::Migrator" do
235
290
  @db.drops.should == [5555, 3333, 2222]
236
291
 
237
292
  Sequel::Migrator.get_current_migration_version(@db).should == 1
293
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
294
+ "SELECT * FROM schema_info LIMIT 1",
295
+ "INSERT INTO schema_info (version) VALUES (1)",
296
+ "SELECT * FROM schema_info LIMIT 1"]
238
297
  end
239
298
 
240
299
  specify "should apply migrations up to the latest version if no target is given" do
@@ -242,6 +301,11 @@ context "Sequel::Migrator" do
242
301
  @db.creates.should == [1111, 2222, 3333, 5555]
243
302
 
244
303
  Sequel::Migrator.get_current_migration_version(@db).should == 5
304
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
305
+ "SELECT * FROM schema_info LIMIT 1",
306
+ "SELECT * FROM schema_info LIMIT 1",
307
+ "INSERT INTO schema_info (version) VALUES (5)",
308
+ "SELECT * FROM schema_info LIMIT 1"]
245
309
  end
246
310
 
247
311
  specify "should apply migrations down to 0 version correctly" do
@@ -249,13 +313,15 @@ context "Sequel::Migrator" do
249
313
  @db.drops.should == [5555, 3333, 2222, 1111]
250
314
 
251
315
  Sequel::Migrator.get_current_migration_version(@db).should == 0
316
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
317
+ "SELECT * FROM schema_info LIMIT 1",
318
+ "INSERT INTO schema_info (version) VALUES (0)",
319
+ "SELECT * FROM schema_info LIMIT 1"]
252
320
  end
253
321
 
254
322
  specify "should return the target version" do
255
323
  Sequel::Migrator.apply(@db, '.', 3, 2).should == 3
256
-
257
324
  Sequel::Migrator.apply(@db, '.', 0).should == 0
258
-
259
325
  Sequel::Migrator.apply(@db, '.').should == 5
260
326
  end
261
327
  end