sequel 4.8.0 → 4.9.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 (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