sequel 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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