sequel 4.8.0 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +4 -0
  5. data/doc/postgresql.rdoc +27 -3
  6. data/doc/release_notes/4.9.0.txt +190 -0
  7. data/doc/security.rdoc +1 -1
  8. data/doc/testing.rdoc +2 -2
  9. data/doc/validations.rdoc +8 -0
  10. data/lib/sequel/adapters/jdbc.rb +5 -3
  11. data/lib/sequel/adapters/jdbc/derby.rb +2 -8
  12. data/lib/sequel/adapters/jdbc/h2.rb +2 -13
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
  14. data/lib/sequel/adapters/mysql2.rb +11 -1
  15. data/lib/sequel/adapters/postgres.rb +33 -10
  16. data/lib/sequel/adapters/shared/db2.rb +2 -10
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -8
  18. data/lib/sequel/adapters/shared/oracle.rb +9 -24
  19. data/lib/sequel/adapters/shared/postgres.rb +32 -9
  20. data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
  21. data/lib/sequel/adapters/shared/sqlite.rb +4 -7
  22. data/lib/sequel/database/schema_methods.rb +15 -0
  23. data/lib/sequel/dataset.rb +1 -1
  24. data/lib/sequel/dataset/actions.rb +159 -27
  25. data/lib/sequel/dataset/graph.rb +29 -7
  26. data/lib/sequel/dataset/misc.rb +6 -0
  27. data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
  28. data/lib/sequel/dataset/query.rb +2 -0
  29. data/lib/sequel/dataset/sql.rb +103 -91
  30. data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
  31. data/lib/sequel/extensions/pg_array.rb +68 -106
  32. data/lib/sequel/extensions/pg_hstore.rb +5 -5
  33. data/lib/sequel/extensions/schema_dumper.rb +49 -49
  34. data/lib/sequel/model.rb +4 -2
  35. data/lib/sequel/model/associations.rb +1 -1
  36. data/lib/sequel/model/base.rb +136 -3
  37. data/lib/sequel/model/errors.rb +6 -0
  38. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  39. data/lib/sequel/plugins/eager_each.rb +9 -0
  40. data/lib/sequel/plugins/nested_attributes.rb +2 -2
  41. data/lib/sequel/plugins/timestamps.rb +2 -2
  42. data/lib/sequel/plugins/touch.rb +2 -2
  43. data/lib/sequel/sql.rb +20 -15
  44. data/lib/sequel/version.rb +1 -1
  45. data/spec/adapters/postgres_spec.rb +70 -8
  46. data/spec/core/dataset_spec.rb +172 -27
  47. data/spec/core/expression_filters_spec.rb +3 -3
  48. data/spec/core/object_graph_spec.rb +17 -1
  49. data/spec/core/placeholder_literalizer_spec.rb +128 -0
  50. data/spec/core/schema_spec.rb +54 -0
  51. data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
  52. data/spec/extensions/defaults_setter_spec.rb +12 -0
  53. data/spec/extensions/eager_each_spec.rb +6 -0
  54. data/spec/extensions/nested_attributes_spec.rb +14 -2
  55. data/spec/extensions/pg_array_spec.rb +15 -7
  56. data/spec/extensions/shared_caching_spec.rb +5 -5
  57. data/spec/extensions/timestamps_spec.rb +9 -0
  58. data/spec/extensions/touch_spec.rb +9 -0
  59. data/spec/integration/database_test.rb +1 -1
  60. data/spec/integration/dataset_test.rb +27 -5
  61. data/spec/model/eager_loading_spec.rb +32 -0
  62. data/spec/model/model_spec.rb +119 -9
  63. metadata +8 -2
@@ -3254,46 +3254,30 @@ describe "Dataset#grep" do
3254
3254
  end
3255
3255
  end
3256
3256
 
3257
- describe "Dataset default #fetch_rows, #insert, #update, #delete, #with_sql_delete, #truncate, #execute" do
3257
+ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3258
3258
  before do
3259
- @db = Sequel::Database.new
3259
+ @db = Sequel.mock(:servers=>{:read_only=>{}}, :autoid=>1)
3260
3260
  @ds = @db[:items]
3261
3261
  end
3262
3262
 
3263
3263
  specify "#delete should execute delete SQL" do
3264
- @db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
3265
- @ds.delete
3266
- @db.should_receive(:execute_dui).once.with('DELETE FROM items', :server=>:default)
3267
- @ds.delete
3268
- end
3269
-
3270
- specify "#with_sql_delete should execute delete SQL" do
3271
- sql = 'DELETE FROM foo'
3272
- @db.should_receive(:execute).once.with(sql, :server=>:default)
3273
- @ds.with_sql_delete(sql)
3274
- @db.should_receive(:execute_dui).once.with(sql, :server=>:default)
3275
- @ds.with_sql_delete(sql)
3264
+ @ds.delete.should == 0
3265
+ @db.sqls.should == ["DELETE FROM items"]
3276
3266
  end
3277
3267
 
3278
3268
  specify "#insert should execute insert SQL" do
3279
- @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3280
- @ds.insert([])
3281
- @db.should_receive(:execute_insert).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3282
- @ds.insert([])
3269
+ @ds.insert([]).should == 1
3270
+ @db.sqls.should == ["INSERT INTO items DEFAULT VALUES"]
3283
3271
  end
3284
3272
 
3285
3273
  specify "#update should execute update SQL" do
3286
- @db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
3287
- @ds.update(:number=>1)
3288
- @db.should_receive(:execute_dui).once.with('UPDATE items SET number = 1', :server=>:default)
3289
- @ds.update(:number=>1)
3274
+ @ds.update(:number=>1).should == 0
3275
+ @db.sqls.should == ["UPDATE items SET number = 1"]
3290
3276
  end
3291
3277
 
3292
3278
  specify "#truncate should execute truncate SQL" do
3293
- @db.should_receive(:execute).once.with('TRUNCATE TABLE items', :server=>:default)
3294
- @ds.truncate.should == nil
3295
- @db.should_receive(:execute_ddl).once.with('TRUNCATE TABLE items', :server=>:default)
3296
3279
  @ds.truncate.should == nil
3280
+ @db.sqls.should == ["TRUNCATE TABLE items"]
3297
3281
  end
3298
3282
 
3299
3283
  specify "#truncate should raise an InvalidOperation exception if the dataset is filtered" do
@@ -3302,8 +3286,71 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #with_sql_dele
3302
3286
  end
3303
3287
 
3304
3288
  specify "#execute should execute the SQL on the database" do
3305
- @db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
3306
3289
  @ds.send(:execute, 'SELECT 1')
3290
+ @db.sqls.should == ["SELECT 1 -- read_only"]
3291
+ end
3292
+ end
3293
+
3294
+ describe "Dataset#with_sql_*" do
3295
+ before do
3296
+ @db = Sequel.mock(:servers=>{:read_only=>{}}, :autoid=>1, :fetch=>{:id=>1})
3297
+ @ds = @db[:items]
3298
+ end
3299
+
3300
+ specify "#with_sql_insert should execute given insert SQL" do
3301
+ @ds.with_sql_insert('INSERT INTO foo (1)').should == 1
3302
+ @db.sqls.should == ["INSERT INTO foo (1)"]
3303
+ end
3304
+
3305
+ specify "#with_sql_delete should execute given delete SQL" do
3306
+ @ds.with_sql_delete('DELETE FROM foo').should == 0
3307
+ @db.sqls.should == ["DELETE FROM foo"]
3308
+ end
3309
+
3310
+ specify "#with_sql_update should execute given update SQL" do
3311
+ @ds.with_sql_update('UPDATE foo SET a = 1').should == 0
3312
+ @db.sqls.should == ["UPDATE foo SET a = 1"]
3313
+ end
3314
+
3315
+ specify "#with_sql_all should return all rows from running the SQL" do
3316
+ @ds.with_sql_all('SELECT * FROM foo').should == [{:id=>1}]
3317
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3318
+ end
3319
+
3320
+ specify "#with_sql_all should yield each row to the block" do
3321
+ a = []
3322
+ @ds.with_sql_all('SELECT * FROM foo'){|r| a << r}
3323
+ a.should == [{:id=>1}]
3324
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3325
+ end
3326
+
3327
+ specify "#with_sql_each should yield each row to the block" do
3328
+ a = []
3329
+ @ds.with_sql_each('SELECT * FROM foo'){|r| a << r}
3330
+ a.should == [{:id=>1}]
3331
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3332
+ end
3333
+
3334
+ specify "#with_sql_first should return first row" do
3335
+ @ds.with_sql_first('SELECT * FROM foo').should == {:id=>1}
3336
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3337
+ end
3338
+
3339
+ specify "#with_sql_first should return nil if no rows returned" do
3340
+ @db.fetch = []
3341
+ @ds.with_sql_first('SELECT * FROM foo').should == nil
3342
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3343
+ end
3344
+
3345
+ specify "#with_sql_single_value should return first value from first row" do
3346
+ @ds.with_sql_single_value('SELECT * FROM foo').should == 1
3347
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3348
+ end
3349
+
3350
+ specify "#with_sql_single_value should return nil if no rows returned" do
3351
+ @db.fetch = []
3352
+ @ds.with_sql_single_value('SELECT * FROM foo').should == nil
3353
+ @db.sqls.should == ["SELECT * FROM foo -- read_only"]
3307
3354
  end
3308
3355
  end
3309
3356
 
@@ -4314,7 +4361,7 @@ describe "Dataset emulating bitwise operator support" do
4314
4361
  @ds = Sequel::Database.new.dataset
4315
4362
  @ds.quote_identifiers = true
4316
4363
  def @ds.complex_expression_sql_append(sql, op, args)
4317
- sql << complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
4364
+ complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(:bitand, a, b)}
4318
4365
  end
4319
4366
  end
4320
4367
 
@@ -4562,6 +4609,49 @@ describe "Dataset#paged_each" do
4562
4609
  @ds.limit(nil, 2).paged_each(:rows_per_fetch=>3, &@proc)
4563
4610
  @ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 2", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 5", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 8", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 11"]
4564
4611
  end
4612
+
4613
+ it "should support :strategy=>:filter" do
4614
+ @ds._fetch = @db.each_slice(5).to_a
4615
+ @ds.paged_each(:rows_per_fetch=>5, :strategy=>:filter, &@proc)
4616
+ @ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY x LIMIT 5", "SELECT * FROM test WHERE (x > 4) ORDER BY x LIMIT 5", "SELECT * FROM test WHERE (x > 9) ORDER BY x LIMIT 5"]
4617
+ @rows.should == @db
4618
+
4619
+ @rows = []
4620
+ db = @db.map{|h| h[:y] = h[:x] % 5; h[:z] = h[:x] % 9; h}.sort_by{|h| [h[:z], -h[:y], h[:x]]}
4621
+ @ds._fetch = db.each_slice(5).to_a
4622
+ @ds.order(Sequel.identifier(:z), Sequel.desc(Sequel.qualify(:test, :y)), Sequel.asc(:x)).paged_each(:rows_per_fetch=>5, :strategy=>:filter, &@proc)
4623
+ @ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY z, test.y DESC, x ASC LIMIT 5",
4624
+ "SELECT * FROM test WHERE ((z > 3) OR ((z = 3) AND (test.y < 3)) OR ((z = 3) AND (test.y = 3) AND (x > 3))) ORDER BY z, test.y DESC, x ASC LIMIT 5",
4625
+ "SELECT * FROM test WHERE ((z > 8) OR ((z = 8) AND (test.y < 3)) OR ((z = 8) AND (test.y = 3) AND (x > 8))) ORDER BY z, test.y DESC, x ASC LIMIT 5"]
4626
+ @rows.should == db
4627
+ end
4628
+
4629
+ it "should support :strategy=>:filter with :filter_values option" do
4630
+ db = @db.map{|h| h[:y] = h[:x] % 5; h[:z] = h[:x] % 9; h}.sort_by{|h| [h[:z], -h[:y], h[:x]]}
4631
+ @ds._fetch = db.each_slice(5).to_a
4632
+ @ds.order(Sequel.identifier(:z), Sequel.desc(Sequel.qualify(:test, :y) * 2), Sequel.asc(:x)).paged_each(:rows_per_fetch=>5, :strategy=>:filter, :filter_values=>proc{|row, expr| [row[expr[0].value], row[expr[1].args.first.column] * expr[1].args.last, row[expr[2]]]}, &@proc)
4633
+ @ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY z, (test.y * 2) DESC, x ASC LIMIT 5",
4634
+ "SELECT * FROM test WHERE ((z > 3) OR ((z = 3) AND ((test.y * 2) < 6)) OR ((z = 3) AND ((test.y * 2) = 6) AND (x > 3))) ORDER BY z, (test.y * 2) DESC, x ASC LIMIT 5",
4635
+ "SELECT * FROM test WHERE ((z > 8) OR ((z = 8) AND ((test.y * 2) < 6)) OR ((z = 8) AND ((test.y * 2) = 6) AND (x > 8))) ORDER BY z, (test.y * 2) DESC, x ASC LIMIT 5"]
4636
+ @rows.should == db
4637
+ end
4638
+ end
4639
+
4640
+ describe "Dataset#current_datetime" do
4641
+ after do
4642
+ Sequel.datetime_class = Time
4643
+ end
4644
+
4645
+ it "should return an instance of Sequel.datetime_class for the current datetime" do
4646
+ t = Sequel::Dataset.new(nil).current_datetime
4647
+ t.should be_a_kind_of(Time)
4648
+ (Time.now - t < 0.1).should == true
4649
+
4650
+ Sequel.datetime_class = DateTime
4651
+ t = Sequel::Dataset.new(nil).current_datetime
4652
+ t.should be_a_kind_of(DateTime)
4653
+ (DateTime.now - t < (0.1/86400)).should == true
4654
+ end
4565
4655
  end
4566
4656
 
4567
4657
  describe "Dataset#escape_like" do
@@ -4693,3 +4783,58 @@ describe "Dataset mutation methods" do
4693
4783
  dsc.graph!(dsc, {:b=>:c}, :table_alias=>:foo).ungraphed!.opts[:graph].should be_nil
4694
4784
  end
4695
4785
  end
4786
+
4787
+ describe "Dataset emulated complex expression operators" do
4788
+ before do
4789
+ @ds = Sequel.mock[:test]
4790
+ def @ds.complex_expression_sql_append(sql, op, args)
4791
+ case op
4792
+ when :&, :|, :^, :%, :<<, :>>, :'B~'
4793
+ complex_expression_emulate_append(sql, op, args)
4794
+ else
4795
+ super
4796
+ end
4797
+ end
4798
+ @n = Sequel.expr(:x).sql_number
4799
+ end
4800
+
4801
+ it "should emulate &" do
4802
+ @ds.literal(Sequel::SQL::NumericExpression.new(:&, @n)).should == "x"
4803
+ @ds.literal(@n & 1).should == "BITAND(x, 1)"
4804
+ @ds.literal(@n & 1 & 2).should == "BITAND(BITAND(x, 1), 2)"
4805
+ end
4806
+
4807
+ it "should emulate |" do
4808
+ @ds.literal(Sequel::SQL::NumericExpression.new(:|, @n)).should == "x"
4809
+ @ds.literal(@n | 1).should == "BITOR(x, 1)"
4810
+ @ds.literal(@n | 1 | 2).should == "BITOR(BITOR(x, 1), 2)"
4811
+ end
4812
+
4813
+ it "should emulate ^" do
4814
+ @ds.literal(Sequel::SQL::NumericExpression.new(:^, @n)).should == "x"
4815
+ @ds.literal(@n ^ 1).should == "BITXOR(x, 1)"
4816
+ @ds.literal(@n ^ 1 ^ 2).should == "BITXOR(BITXOR(x, 1), 2)"
4817
+ end
4818
+
4819
+ it "should emulate %" do
4820
+ @ds.literal(Sequel::SQL::NumericExpression.new(:%, @n)).should == "x"
4821
+ @ds.literal(@n % 1).should == "MOD(x, 1)"
4822
+ @ds.literal(@n % 1 % 2).should == "MOD(MOD(x, 1), 2)"
4823
+ end
4824
+
4825
+ it "should emulate >>" do
4826
+ @ds.literal(Sequel::SQL::NumericExpression.new(:>>, @n)).should == "x"
4827
+ @ds.literal(@n >> 1).should == "(x / power(2, 1))"
4828
+ @ds.literal(@n >> 1 >> 2).should == "(x / power(2, 1) / power(2, 2))"
4829
+ end
4830
+
4831
+ it "should emulate <<" do
4832
+ @ds.literal(Sequel::SQL::NumericExpression.new(:<<, @n)).should == "x"
4833
+ @ds.literal(@n << 1).should == "(x * power(2, 1))"
4834
+ @ds.literal(@n << 1 << 2).should == "(x * power(2, 1) * power(2, 2))"
4835
+ end
4836
+
4837
+ it "should emulate B~" do
4838
+ @ds.literal(~@n).should == "((0 - x) - 1)"
4839
+ end
4840
+ end
@@ -714,7 +714,7 @@ describe "Sequel core extension replacements" do
714
714
  end
715
715
 
716
716
  it "Sequel.& should join all arguments given with AND" do
717
- l(Sequel.&(:a), "(a)")
717
+ l(Sequel.&(:a), "a")
718
718
  l(Sequel.&(:a, :b=>:c), "(a AND (b = c))")
719
719
  l(Sequel.&(:a, {:b=>:c}, Sequel.lit('d')), "(a AND (b = c) AND d)")
720
720
  end
@@ -724,7 +724,7 @@ describe "Sequel core extension replacements" do
724
724
  end
725
725
 
726
726
  it "Sequel.| should join all arguments given with OR" do
727
- l(Sequel.|(:a), "(a)")
727
+ l(Sequel.|(:a), "a")
728
728
  l(Sequel.|(:a, :b=>:c), "(a OR (b = c))")
729
729
  l(Sequel.|(:a, {:b=>:c}, Sequel.lit('d')), "(a OR (b = c) OR d)")
730
730
  end
@@ -808,7 +808,7 @@ describe "Sequel core extension replacements" do
808
808
 
809
809
  it "Sequel.{+,-,*,/} should accept arguments and use the appropriate operator" do
810
810
  %w'+ - * /'.each do |op|
811
- l(Sequel.send(op, 1), '(1)')
811
+ l(Sequel.send(op, 1), '1')
812
812
  l(Sequel.send(op, 1, 2), "(1 #{op} 2)")
813
813
  l(Sequel.send(op, 1, 2, 3), "(1 #{op} 2 #{op} 3)")
814
814
  end
@@ -104,11 +104,20 @@ describe Sequel::Dataset, "graphing" do
104
104
 
105
105
  it "should accept a SQL::Identifier as the dataset" do
106
106
  ds = @ds1.graph(Sequel.identifier(:lines), :x=>:id)
107
- 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 lines AS lines ON (lines.x = points.id)'
107
+ 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 lines ON (lines.x = points.id)'
108
108
  ds = @ds1.graph(Sequel.identifier('lines'), :x=>:id)
109
109
  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 lines AS lines ON (lines.x = points.id)'
110
110
  end
111
111
 
112
+ it "should handle a SQL::Identifier with double underscores correctly" do
113
+ ds = @ds1.graph(Sequel.identifier(:lin__es), :x=>:id)
114
+ ds.sql.should == 'SELECT points.id, points.x, points.y, lin__es.id AS lin__es_id, lin__es.name, lin__es.x AS lin__es_x, lin__es.y AS lin__es_y, lin__es.lines_x FROM points LEFT OUTER JOIN lin__es ON (lin__es.x = points.id)'
115
+ ds = @ds1.from(Sequel.identifier(:poi__nts)).graph(Sequel.identifier(:lin__es), :x=>:id)
116
+ ds.sql.should == 'SELECT poi__nts.id, poi__nts.name, poi__nts.x, poi__nts.y, poi__nts.lines_x, lin__es.id AS lin__es_id, lin__es.name AS lin__es_name, lin__es.x AS lin__es_x, lin__es.y AS lin__es_y, lin__es.lines_x AS lin__es_lines_x FROM poi__nts LEFT OUTER JOIN lin__es ON (lin__es.x = poi__nts.id)'
117
+ ds = @ds1.from(Sequel.identifier(:poi__nts).qualify(:foo)).graph(Sequel.identifier(:lin__es).qualify(:bar), :x=>:id)
118
+ ds.sql.should == 'SELECT foo.poi__nts.id, foo.poi__nts.x, foo.poi__nts.y, foo.poi__nts.graph_id, lin__es.id AS lin__es_id, lin__es.name, lin__es.x AS lin__es_x, lin__es.y AS lin__es_y, lin__es.lines_x FROM foo.poi__nts LEFT OUTER JOIN bar.lin__es AS lin__es ON (lin__es.x = foo.poi__nts.id)'
119
+ end
120
+
112
121
  it "should accept a SQL::QualifiedIdentifier as the dataset" do
113
122
  ds = @ds1.graph(Sequel.qualify(:schema, :lines), :x=>:id)
114
123
  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 schema.lines AS lines ON (lines.x = points.id)'
@@ -120,6 +129,13 @@ describe Sequel::Dataset, "graphing" do
120
129
  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 schema.lines AS lines ON (lines.x = points.id)'
121
130
  end
122
131
 
132
+ it "should handle a qualified identifier as the source" do
133
+ ds = @ds1.from(:schema__points).graph(:lines, :x=>:id)
134
+ ds.sql.should == 'SELECT schema.points.id, schema.points.x, schema.points.y, lines.id AS lines_id, lines.x AS lines_x, lines.y AS lines_y, lines.graph_id FROM schema.points LEFT OUTER JOIN lines ON (lines.x = schema.points.id)'
135
+ ds = @ds1.from(Sequel.qualify(:schema, :points)).graph(:lines, :x=>:id)
136
+ ds.sql.should == 'SELECT schema.points.id, schema.points.x, schema.points.y, lines.id AS lines_id, lines.x AS lines_x, lines.y AS lines_y, lines.graph_id FROM schema.points LEFT OUTER JOIN lines ON (lines.x = schema.points.id)'
137
+ end
138
+
123
139
  it "should accept a SQL::AliasedExpression as the dataset" do
124
140
  ds = @ds1.graph(Sequel.as(:lines, :foo), :x=>:id)
125
141
  ds.sql.should == 'SELECT points.id, points.x, points.y, foo.id AS foo_id, foo.x AS foo_x, foo.y AS foo_y, foo.graph_id FROM points LEFT OUTER JOIN lines AS foo ON (foo.x = points.id)'
@@ -0,0 +1,128 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Dataset::PlaceholderLiteralizer" do
4
+ before do
5
+ @c = Sequel::Dataset::PlaceholderLiteralizer
6
+ @db = Sequel.mock
7
+ @ds = @db[:items]
8
+ @h = {:id=>1}
9
+ @ds.db.fetch = @h
10
+ end
11
+
12
+ specify "should handle calls with no placeholders" do
13
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>1)}
14
+ loader.first.should == @h
15
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)"]
16
+ end
17
+
18
+ specify "should handle calls with a single placeholder" do
19
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
20
+ loader.first(1).should == @h
21
+ loader.first(2).should == @h
22
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)", "SELECT * FROM items WHERE (a = 2)"]
23
+ end
24
+
25
+ specify "should handle calls with multiple placeholders" do
26
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg).where(:b=>Sequel.+(pl.arg, 1)).where(pl.arg)}
27
+ loader.first(1, :c, :id=>1).should == @h
28
+ loader.first(2, :d, :id=>2).should == @h
29
+ @db.sqls.should == ["SELECT * FROM items WHERE ((a = 1) AND (b = (c + 1)) AND (id = 1))", "SELECT * FROM items WHERE ((a = 2) AND (b = (d + 1)) AND (id = 2))"]
30
+ end
31
+
32
+ specify "should handle calls with a placeholders used as filter arguments" do
33
+ loader = @c.loader(@ds){|pl, ds| ds.where(pl.arg)}
34
+ loader.first(:id=>1).should == @h
35
+ loader.first(proc{a(b)}).should == @h
36
+ loader.first("a = 1").should == @h
37
+ @db.sqls.should == ["SELECT * FROM items WHERE (id = 1)", "SELECT * FROM items WHERE a(b)", "SELECT * FROM items WHERE (a = 1)"]
38
+ end
39
+
40
+ specify "should handle calls with a placeholders used as right hand side of condition specifiers" do
41
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
42
+ loader.first(1).should == @h
43
+ loader.first([1, 2]).should == @h
44
+ loader.first(nil).should == @h
45
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)", "SELECT * FROM items WHERE (a IN (1, 2))", "SELECT * FROM items WHERE (a IS NULL)"]
46
+ end
47
+
48
+ specify "should handle calls with a placeholder used multiple times" do
49
+ loader = @c.loader(@ds){|pl, ds| a = pl.arg; ds.where(:a=>a).where(:b=>a)}
50
+ loader.first(1).should == @h
51
+ loader.first(2).should == @h
52
+ @db.sqls.should == ["SELECT * FROM items WHERE ((a = 1) AND (b = 1))", "SELECT * FROM items WHERE ((a = 2) AND (b = 2))"]
53
+ end
54
+
55
+ specify "should handle calls with a placeholder used multiple times in different capacities" do
56
+ loader = @c.loader(@ds){|pl, ds| a = pl.arg; ds.where(a).where(:b=>a)}
57
+ loader.first("a = 1").should == @h
58
+ loader.first(["a = ?", 2]).should == @h
59
+ @db.sqls.should == ["SELECT * FROM items WHERE ((a = 1) AND (b = 'a = 1'))", "SELECT * FROM items WHERE ((a = 2) AND (b IN ('a = ?', 2)))"]
60
+ end
61
+
62
+ specify "should handle calls with manually specified argument positions" do
63
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg(1)).where(:b=>pl.arg(0))}
64
+ loader.first(1, 2).should == @h
65
+ loader.first(2, 1).should == @h
66
+ @db.sqls.should == ["SELECT * FROM items WHERE ((a = 2) AND (b = 1))", "SELECT * FROM items WHERE ((a = 1) AND (b = 2))"]
67
+ end
68
+
69
+ specify "should handle dataset with row procs" do
70
+ @ds.row_proc = proc{|r| {:foo=>r[:id]+1}}
71
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
72
+ loader.first(1).should == {:foo=>2}
73
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)"]
74
+ end
75
+
76
+ specify "should return all rows for #all" do
77
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
78
+ loader.all(1).should == [@h]
79
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)"]
80
+ end
81
+
82
+ specify "should iterate over block for #all" do
83
+ a = []
84
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
85
+ loader.all(1){|r| a << r}.should == [@h]
86
+ a.should == [@h]
87
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)"]
88
+ end
89
+
90
+ specify "should iterate over block for #each" do
91
+ a = []
92
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
93
+ loader.each(1){|r| a << r}
94
+ a.should == [@h]
95
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 1)"]
96
+ end
97
+
98
+ specify "should return first value for #get" do
99
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
100
+ loader.get(2).should == 1
101
+ @db.sqls.should == ["SELECT * FROM items WHERE (a = 2)"]
102
+ end
103
+
104
+ specify "should raise an error if called with an incorrect number of arguments" do
105
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg)}
106
+ proc{loader.first}.should raise_error(Sequel::Error)
107
+ proc{loader.first(1, 2)}.should raise_error(Sequel::Error)
108
+ end
109
+
110
+ specify "should raise an error if called with an incorrect number of arguments when manually providing argument positions" do
111
+ loader = @c.loader(@ds){|pl, ds| ds.where(:a=>pl.arg(1))}
112
+ proc{loader.first}.should raise_error(Sequel::Error)
113
+ proc{loader.first(1)}.should raise_error(Sequel::Error)
114
+ proc{loader.first(1, 2, 3)}.should raise_error(Sequel::Error)
115
+ end
116
+
117
+ specify "should raise an error if argument literalized into a different string than returned by query" do
118
+ o = Object.new
119
+ def o.wrap(v)
120
+ @v = v
121
+ self
122
+ end
123
+ def o.sql_literal(ds)
124
+ ds.literal(@v)
125
+ end
126
+ proc{@c.loader(@ds){|pl, ds| ds.where(o.wrap(pl.arg))}}.should raise_error(Sequel::Error)
127
+ end
128
+ end
@@ -701,6 +701,12 @@ describe "DB#create_table!" do
701
701
  @db.create_table!(:cats){|*a|}
702
702
  @db.sqls.should == ['DROP TABLE cats', 'CREATE TABLE cats ()']
703
703
  end
704
+
705
+ specify "should use IF EXISTS if the database supports it" do
706
+ meta_def(@db, :supports_drop_table_if_exists?){true}
707
+ @db.create_table!(:cats){|*a|}
708
+ @db.sqls.should == ['DROP TABLE IF EXISTS cats', 'CREATE TABLE cats ()']
709
+ end
704
710
  end
705
711
 
706
712
  describe "DB#create_table?" do
@@ -775,6 +781,54 @@ describe "DB#create_join_table" do
775
781
  end
776
782
  end
777
783
 
784
+ describe "DB#create_join_table?" do
785
+ before do
786
+ @db = Sequel.mock
787
+ end
788
+
789
+ specify "should create the table if it does not already exist" do
790
+ meta_def(@db, :table_exists?){|a| false}
791
+ @db.create_join_table?(:cat_id=>:cats, :dog_id=>:dogs)
792
+ @db.sqls.should == ['CREATE TABLE cats_dogs (cat_id integer NOT NULL REFERENCES cats, dog_id integer NOT NULL REFERENCES dogs, PRIMARY KEY (cat_id, dog_id))', 'CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs (dog_id, cat_id)']
793
+ end
794
+
795
+ specify "should not create the table if it already exists" do
796
+ meta_def(@db, :table_exists?){|a| true}
797
+ @db.create_join_table?(:cat_id=>:cats, :dog_id=>:dogs)
798
+ @db.sqls.should == []
799
+ end
800
+
801
+ specify "should use IF NOT EXISTS if the database supports it" do
802
+ meta_def(@db, :supports_create_table_if_not_exists?){true}
803
+ @db.create_join_table?(:cat_id=>:cats, :dog_id=>:dogs)
804
+ @db.sqls.should == ['CREATE TABLE IF NOT EXISTS cats_dogs (cat_id integer NOT NULL REFERENCES cats, dog_id integer NOT NULL REFERENCES dogs, PRIMARY KEY (cat_id, dog_id))', 'CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs (dog_id, cat_id)']
805
+ end
806
+ end
807
+
808
+ describe "DB#create_join_table!" do
809
+ before do
810
+ @db = Sequel.mock
811
+ end
812
+
813
+ specify "should drop the table first if it already exists" do
814
+ meta_def(@db, :table_exists?){|a| true}
815
+ @db.create_join_table!(:cat_id=>:cats, :dog_id=>:dogs)
816
+ @db.sqls.should == ['DROP TABLE cats_dogs', 'CREATE TABLE cats_dogs (cat_id integer NOT NULL REFERENCES cats, dog_id integer NOT NULL REFERENCES dogs, PRIMARY KEY (cat_id, dog_id))', 'CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs (dog_id, cat_id)']
817
+ end
818
+
819
+ specify "should not drop the table if it doesn't exists" do
820
+ meta_def(@db, :table_exists?){|a| false}
821
+ @db.create_join_table!(:cat_id=>:cats, :dog_id=>:dogs)
822
+ @db.sqls.should == ['CREATE TABLE cats_dogs (cat_id integer NOT NULL REFERENCES cats, dog_id integer NOT NULL REFERENCES dogs, PRIMARY KEY (cat_id, dog_id))', 'CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs (dog_id, cat_id)']
823
+ end
824
+
825
+ specify "should use IF EXISTS if the database supports it" do
826
+ meta_def(@db, :supports_drop_table_if_exists?){true}
827
+ @db.create_join_table!(:cat_id=>:cats, :dog_id=>:dogs)
828
+ @db.sqls.should == ['DROP TABLE IF EXISTS cats_dogs', 'CREATE TABLE cats_dogs (cat_id integer NOT NULL REFERENCES cats, dog_id integer NOT NULL REFERENCES dogs, PRIMARY KEY (cat_id, dog_id))', 'CREATE INDEX cats_dogs_dog_id_cat_id_index ON cats_dogs (dog_id, cat_id)']
829
+ end
830
+ end
831
+
778
832
  describe "DB#drop_join_table" do
779
833
  before do
780
834
  @db = Sequel.mock