sequel 3.21.0 → 3.28.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -538,34 +538,55 @@ describe "Dataset#where" do
538
538
 
539
539
  specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
540
540
  @dataset.meta_def(:supports_multiple_column_in?){false}
541
- d1 = @d1.select(:id1, :id2)
541
+ db = MockDatabase.new()
542
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
543
+ d1.instance_variable_set(:@columns, [:id1, :id2])
542
544
  def d1.fetch_rows(sql)
543
- @sql_used = sql
545
+ db << sql
544
546
  @columns = [:id1, :id2]
545
547
  yield(:id1=>1, :id2=>2)
546
548
  yield(:id1=>3, :id2=>4)
547
549
  end
548
- d1.instance_variable_get(:@sql_used).should == nil
549
550
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
550
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
551
- d1.instance_variable_set(:@sql_used, nil)
551
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
552
+ db.sqls.clear
552
553
  @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
553
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
554
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
554
555
  end
555
556
 
556
557
  specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
557
558
  @dataset.meta_def(:supports_multiple_column_in?){false}
558
- d1 = @d1.select(:id1, :id2)
559
+ db = MockDatabase.new()
560
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
561
+ d1.instance_variable_set(:@columns, [:id1, :id2])
559
562
  def d1.fetch_rows(sql)
560
- @sql_used = sql
563
+ db << sql
561
564
  @columns = [:id1, :id2]
562
565
  end
563
- d1.instance_variable_get(:@sql_used).should == nil
564
566
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
565
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
566
- d1.instance_variable_set(:@sql_used, nil)
567
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
568
+ db.sqls.clear
567
569
  @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
568
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
570
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
571
+ end
572
+
573
+ specify "should handle IN/NOT IN queries for datasets with row_procs" do
574
+ @dataset.meta_def(:supports_multiple_column_in?){false}
575
+ db = MockDatabase.new()
576
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
577
+ d1.row_proc = proc{|h| Object.new}
578
+ d1.instance_variable_set(:@columns, [:id1, :id2])
579
+ def d1.fetch_rows(sql)
580
+ db << sql
581
+ @columns = [:id1, :id2]
582
+ yield(:id1=>1, :id2=>2)
583
+ yield(:id1=>3, :id2=>4)
584
+ end
585
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
586
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
587
+ db.sqls.clear
588
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
589
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
569
590
  end
570
591
 
571
592
  specify "should accept a subquery for an EXISTS clause" do
@@ -601,10 +622,18 @@ describe "Dataset#where" do
601
622
  end
602
623
 
603
624
  specify "should accept true and false as arguments" do
604
- @dataset.filter(true).sql.should ==
605
- "SELECT * FROM test WHERE 't'"
606
- @dataset.filter(false).sql.should ==
607
- "SELECT * FROM test WHERE 'f'"
625
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE 't'"
626
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE 't'"
627
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE 'f'"
628
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE 'f'"
629
+ end
630
+
631
+ specify "should use boolean expression if dataset does not support where true/false" do
632
+ def @dataset.supports_where_true?() false end
633
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE (1 = 1)"
634
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE (1 = 1)"
635
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE (1 = 0)"
636
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE (1 = 0)"
608
637
  end
609
638
 
610
639
  specify "should allow the use of multiple arguments" do
@@ -738,6 +767,11 @@ describe "Dataset#exclude" do
738
767
  "SELECT * FROM test WHERE (region != 'Asia')"
739
768
  end
740
769
 
770
+ specify "should affect the having clause if having clause is already used" do
771
+ @dataset.group_and_count(:name).having{count > 2}.exclude{count > 5}.sql.should ==
772
+ "SELECT name, count(*) AS count FROM test GROUP BY name HAVING ((count > 2) AND (count <= 5))"
773
+ end
774
+
741
775
  specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
742
776
  @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
743
777
  should match(Regexp.union(/WHERE \(\(region != 'Asia'\) OR \(name != 'Japan'\)\)/,
@@ -772,6 +806,28 @@ describe "Dataset#exclude" do
772
806
  end
773
807
  end
774
808
 
809
+ describe "Dataset#exclude_where" do
810
+ before do
811
+ @dataset = Sequel::Dataset.new(nil).from(:test)
812
+ end
813
+
814
+ specify "should correctly negate the expression and add it to the where clause" do
815
+ @dataset.exclude_where(:region=>'Asia').sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
816
+ @dataset.exclude_where(:region=>'Asia').exclude_where(:region=>'NA').sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (region != 'NA'))"
817
+ end
818
+
819
+ specify "should affect the where clause even if having clause is already used" do
820
+ @dataset.group_and_count(:name).having{count > 2}.exclude_where(:region=>'Asia').sql.should ==
821
+ "SELECT name, count(*) AS count FROM test WHERE (region != 'Asia') GROUP BY name HAVING (count > 2)"
822
+ end
823
+ end
824
+
825
+ describe "Dataset#exclude_having" do
826
+ specify "should correctly negate the expression and add it to the having clause" do
827
+ Sequel::Dataset.new(nil).from(:test).exclude_having{count > 2}.exclude_having{count < 0}.sql.should == "SELECT * FROM test HAVING ((count <= 2) AND (count >= 0))"
828
+ end
829
+ end
830
+
775
831
  describe "Dataset#invert" do
776
832
  before do
777
833
  @d = Sequel::Dataset.new(nil).from(:test)
@@ -876,7 +932,7 @@ describe "Dataset#group_by" do
876
932
  "SELECT * FROM test GROUP BY (type_id IS NULL)"
877
933
  end
878
934
 
879
- specify "should ungroup when passed nil, empty, or no arguments" do
935
+ specify "should ungroup when passed nil or no arguments" do
880
936
  @dataset.group_by.select_sql.should ==
881
937
  "SELECT * FROM test"
882
938
  @dataset.group_by(nil).select_sql.should ==
@@ -894,6 +950,13 @@ describe "Dataset#group_by" do
894
950
  @dataset.group(:type_id=>nil).select_sql.should ==
895
951
  "SELECT * FROM test GROUP BY (type_id IS NULL)"
896
952
  end
953
+
954
+ specify "should take a virtual row block" do
955
+ @dataset.group{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
956
+ @dataset.group_by{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
957
+ @dataset.group{[type_id > 1, type_id < 2]}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1), (type_id < 2)"
958
+ @dataset.group(:foo){type_id > 1}.sql.should == "SELECT * FROM test GROUP BY foo, (type_id > 1)"
959
+ end
897
960
  end
898
961
 
899
962
  describe "Dataset#as" do
@@ -965,6 +1028,12 @@ describe "Dataset#literal" do
965
1028
  d.literal(d).should == "(#{d.sql})"
966
1029
  end
967
1030
 
1031
+ specify "should literalize Sequel::SQLTime properly" do
1032
+ t = Sequel::SQLTime.now
1033
+ s = t.strftime("'%H:%M:%S")
1034
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
1035
+ end
1036
+
968
1037
  specify "should literalize Time properly" do
969
1038
  t = Time.now
970
1039
  s = t.strftime("'%Y-%m-%d %H:%M:%S")
@@ -1198,6 +1267,27 @@ describe "Dataset#select" do
1198
1267
  end
1199
1268
  end
1200
1269
 
1270
+ describe "Dataset#select_group" do
1271
+ before do
1272
+ @d = Sequel::Dataset.new(nil).from(:test)
1273
+ end
1274
+
1275
+ specify "should set both SELECT and GROUP" do
1276
+ @d.select_group(:name).sql.should == 'SELECT name FROM test GROUP BY name'
1277
+ @d.select_group(:a, :b__c, :d___e).sql.should == 'SELECT a, b.c, d AS e FROM test GROUP BY a, b.c, d'
1278
+ end
1279
+
1280
+ specify "should remove from both SELECT and GROUP if no arguments" do
1281
+ @d.select_group(:name).select_group.sql.should == 'SELECT * FROM test'
1282
+ end
1283
+
1284
+ specify "should accept virtual row blocks" do
1285
+ @d.select_group{name}.sql.should == 'SELECT name FROM test GROUP BY name'
1286
+ @d.select_group{[name, f(v).as(a)]}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1287
+ @d.select_group(:name){f(v).as(a)}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1288
+ end
1289
+ end
1290
+
1201
1291
  describe "Dataset#select_all" do
1202
1292
  before do
1203
1293
  @d = Sequel::Dataset.new(nil).from(:test)
@@ -1210,6 +1300,40 @@ describe "Dataset#select_all" do
1210
1300
  specify "should overrun the previous select option" do
1211
1301
  @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
1212
1302
  end
1303
+
1304
+ specify "should select all columns in a table if given an argument" do
1305
+ @d.select_all(:test).sql.should == 'SELECT test.* FROM test'
1306
+ end
1307
+
1308
+ specify "should select all columns all tables if given a multiple arguments" do
1309
+ @d.select_all(:test, :foo).sql.should == 'SELECT test.*, foo.* FROM test'
1310
+ end
1311
+
1312
+ specify "should work correctly with qualified symbols" do
1313
+ @d.select_all(:sch__test).sql.should == 'SELECT sch.test.* FROM test'
1314
+ end
1315
+
1316
+ specify "should work correctly with aliased symbols" do
1317
+ @d.select_all(:test___al).sql.should == 'SELECT al.* FROM test'
1318
+ @d.select_all(:sch__test___al).sql.should == 'SELECT al.* FROM test'
1319
+ end
1320
+
1321
+ specify "should work correctly with SQL::Identifiers" do
1322
+ @d.select_all(:test.identifier).sql.should == 'SELECT test.* FROM test'
1323
+ end
1324
+
1325
+ specify "should work correctly with SQL::QualifiedIdentifier" do
1326
+ @d.select_all(:test.qualify(:sch)).sql.should == 'SELECT sch.test.* FROM test'
1327
+ end
1328
+
1329
+ specify "should work correctly with SQL::AliasedExpressions" do
1330
+ @d.select_all(:test.as(:al)).sql.should == 'SELECT al.* FROM test'
1331
+ end
1332
+
1333
+ specify "should work correctly with SQL::JoinClauses" do
1334
+ d = @d.cross_join(:foo).cross_join(:test___al)
1335
+ @d.select_all(*d.opts[:join]).sql.should == 'SELECT foo.*, al.* FROM test'
1336
+ end
1213
1337
  end
1214
1338
 
1215
1339
  describe "Dataset#select_more" do
@@ -1254,6 +1378,14 @@ describe "Dataset#select_append" do
1254
1378
  @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1255
1379
  @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1256
1380
  end
1381
+
1382
+ specify "should select from all from and join tables if SELECT *, column not supported" do
1383
+ @d.meta_def(:supports_select_all_and_column?){false}
1384
+ @d.select_append(:b).sql.should == 'SELECT test.*, b FROM test'
1385
+ @d.from(:test, :c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test, c'
1386
+ @d.cross_join(:c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test CROSS JOIN c'
1387
+ @d.cross_join(:c).cross_join(:d).select_append(:b).sql.should == 'SELECT test.*, c.*, d.*, b FROM test CROSS JOIN c CROSS JOIN d'
1388
+ end
1257
1389
  end
1258
1390
 
1259
1391
  describe "Dataset#order" do
@@ -1355,6 +1487,13 @@ describe "Dataset#with_sql" do
1355
1487
  specify "should keep row_proc" do
1356
1488
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1357
1489
  end
1490
+
1491
+ specify "should work with method symbols and arguments" do
1492
+ @dataset.with_sql(:delete_sql).sql.should == 'DELETE FROM test'
1493
+ @dataset.with_sql(:insert_sql, :b=>1).sql.should == 'INSERT INTO test (b) VALUES (1)'
1494
+ @dataset.with_sql(:update_sql, :b=>1).sql.should == 'UPDATE test SET b = 1'
1495
+ end
1496
+
1358
1497
  end
1359
1498
 
1360
1499
  describe "Dataset#order_by" do
@@ -1571,17 +1710,6 @@ describe "Dataset#qualified_column_name" do
1571
1710
  end
1572
1711
  end
1573
1712
 
1574
- class DummyDataset < Sequel::Dataset
1575
- VALUES = [
1576
- {:a => 1, :b => 2},
1577
- {:a => 3, :b => 4},
1578
- {:a => 5, :b => 6}
1579
- ]
1580
- def fetch_rows(sql, &block)
1581
- VALUES.each(&block)
1582
- end
1583
- end
1584
-
1585
1713
  describe "Dataset#map" do
1586
1714
  before do
1587
1715
  @d = DummyDataset.new(nil).from(:items)
@@ -1595,6 +1723,10 @@ describe "Dataset#map" do
1595
1723
  @d.map(:a).should == [1, 3, 5]
1596
1724
  end
1597
1725
 
1726
+ specify "should support multiple column names if an array of column names is given" do
1727
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1728
+ end
1729
+
1598
1730
  specify "should return the complete dataset values if nothing is given" do
1599
1731
  @d.map.to_a.should == DummyDataset::VALUES
1600
1732
  end
@@ -1614,6 +1746,13 @@ describe "Dataset#to_hash" do
1614
1746
  @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1615
1747
  @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1616
1748
  end
1749
+
1750
+ specify "should support using an array of columns as either the key or the value" do
1751
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1752
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1753
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1754
+ @d.to_hash([:a, :b]).should == {[1, 2] => {:a => 1, :b => 2}, [3, 4] => {:a => 3, :b => 4}, [5, 6] => {:a => 5, :b => 6}}
1755
+ end
1617
1756
  end
1618
1757
 
1619
1758
  describe "Dataset#distinct" do
@@ -1753,6 +1892,12 @@ describe "Dataset#group_and_count" do
1753
1892
  @ds.group_and_count(:name.identifier.as(:n)).sql.should ==
1754
1893
  "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1755
1894
  end
1895
+
1896
+ specify "should take a virtual row block" do
1897
+ @ds.group_and_count{(type_id > 1).as(t)}.sql.should == "SELECT (type_id > 1) AS t, count(*) AS count FROM test GROUP BY (type_id > 1)"
1898
+ @ds.group_and_count{[(type_id > 1).as(t), type_id < 2]}.sql.should == "SELECT (type_id > 1) AS t, (type_id < 2), count(*) AS count FROM test GROUP BY (type_id > 1), (type_id < 2)"
1899
+ @ds.group_and_count(:foo){type_id > 1}.sql.should == "SELECT foo, (type_id > 1), count(*) AS count FROM test GROUP BY foo, (type_id > 1)"
1900
+ end
1756
1901
  end
1757
1902
 
1758
1903
  describe "Dataset#empty?" do
@@ -2081,6 +2226,12 @@ describe "Dataset#join_table" do
2081
2226
  @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
2082
2227
  end
2083
2228
 
2229
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
2230
+ @d.meta_def(:supports_cte?){true}
2231
+ @d.meta_def(:supports_cte_in_subselect?){false}
2232
+ @d.join(Sequel::Dataset.new(nil).from(:categories).with(:a, Sequel::Dataset.new(nil).from(:b)), [:id]).sql.should == 'WITH "a" AS (SELECT * FROM b) SELECT * FROM "items" INNER JOIN (SELECT * FROM categories) AS "t1" USING ("id")'
2233
+ end
2234
+
2084
2235
  specify "should raise an error if using an array of symbols with a block" do
2085
2236
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
2086
2237
  end
@@ -2155,7 +2306,6 @@ describe "Dataset#join_table" do
2155
2306
  'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2156
2307
  end
2157
2308
 
2158
-
2159
2309
  specify "should not allow insert, update, delete, or truncate" do
2160
2310
  proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2161
2311
  proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
@@ -3283,6 +3433,7 @@ describe "Dataset prepared statements and bound variables " do
3283
3433
  ds
3284
3434
  end
3285
3435
  @ds = @db[:items]
3436
+ @ds.meta_def(:insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
3286
3437
  end
3287
3438
 
3288
3439
  specify "#call should take a type and bind hash and interpolate it" do
@@ -3291,11 +3442,13 @@ describe "Dataset prepared statements and bound variables " do
3291
3442
  @ds.filter(:num=>:$n).call(:delete, :n=>1)
3292
3443
  @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3293
3444
  @ds.call(:insert, {:n=>1}, :num=>:$n)
3445
+ @ds.call(:insert_select, {:n=>1}, :num=>:$n)
3294
3446
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3295
3447
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3296
3448
  'DELETE FROM items WHERE (num = 1)',
3297
3449
  'UPDATE items SET num = 2 WHERE (num = 1)',
3298
- 'INSERT INTO items (num) VALUES (1)']
3450
+ 'INSERT INTO items (num) VALUES (1)',
3451
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3299
3452
  end
3300
3453
 
3301
3454
  specify "#prepare should take a type and name and store it in the database for later use with call" do
@@ -3305,18 +3458,21 @@ describe "Dataset prepared statements and bound variables " do
3305
3458
  pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3306
3459
  pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3307
3460
  pss << @ds.prepare(:insert, :in, :num=>:$n)
3308
- @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3309
- [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3461
+ pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
3462
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sn, :un]
3463
+ [:sn, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3310
3464
  @db.call(:sn, :n=>1)
3311
3465
  @db.call(:fn, :n=>1)
3312
3466
  @db.call(:dn, :n=>1)
3313
3467
  @db.call(:un, :n=>1, :n2=>2)
3314
3468
  @db.call(:in, :n=>1)
3469
+ @db.call(:ins, :n=>1)
3315
3470
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3316
3471
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3317
3472
  'DELETE FROM items WHERE (num = 1)',
3318
3473
  'UPDATE items SET num = 2 WHERE (num = 1)',
3319
- 'INSERT INTO items (num) VALUES (1)']
3474
+ 'INSERT INTO items (num) VALUES (1)',
3475
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3320
3476
  end
3321
3477
 
3322
3478
  specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
@@ -3599,6 +3755,76 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3599
3755
  end
3600
3756
  end
3601
3757
 
3758
+ describe "Sequel::Dataset#unbind" do
3759
+ before do
3760
+ @ds = MockDatabase.new[:t]
3761
+ @u = proc{|ds| ds, bv = ds.unbind; [ds.sql, bv]}
3762
+ end
3763
+
3764
+ specify "should unbind values assigned to equality and inequality statements" do
3765
+ @ds.filter(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo = $foo)"
3766
+ @ds.exclude(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo != $foo)"
3767
+ @ds.filter{foo > 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo > $foo)"
3768
+ @ds.filter{foo >= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo >= $foo)"
3769
+ @ds.filter{foo < 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo < $foo)"
3770
+ @ds.filter{foo <= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo <= $foo)"
3771
+ end
3772
+
3773
+ specify "should return variables that could be used bound to recreate the previous query" do
3774
+ @ds.filter(:foo=>1).unbind.last.should == {:foo=>1}
3775
+ @ds.exclude(:foo=>1).unbind.last.should == {:foo=>1}
3776
+ end
3777
+
3778
+ specify "should handle numerics, strings, dates, times, and datetimes" do
3779
+ @u[@ds.filter(:foo=>1)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1}]
3780
+ @u[@ds.filter(:foo=>1.0)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1.0}]
3781
+ @u[@ds.filter(:foo=>BigDecimal.new('1.0'))].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>BigDecimal.new('1.0')}]
3782
+ @u[@ds.filter(:foo=>'a')].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>'a'}]
3783
+ @u[@ds.filter(:foo=>Date.today)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>Date.today}]
3784
+ t = Time.now
3785
+ @u[@ds.filter(:foo=>t)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>t}]
3786
+ dt = DateTime.now
3787
+ @u[@ds.filter(:foo=>dt)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>dt}]
3788
+ end
3789
+
3790
+ specify "should not unbind literal strings" do
3791
+ @u[@ds.filter(:foo=>'a'.lit)].should == ["SELECT * FROM t WHERE (foo = a)", {}]
3792
+ end
3793
+
3794
+ specify "should not unbind Identifiers, QualifiedIdentifiers, or Symbols used as booleans" do
3795
+ @u[@ds.filter(:foo).filter{bar}.filter{foo__bar}].should == ["SELECT * FROM t WHERE (foo AND bar AND foo.bar)", {}]
3796
+ end
3797
+
3798
+ specify "should not unbind for values it doesn't understand" do
3799
+ @u[@ds.filter(:foo=>Class.new{def sql_literal(ds) 'bar' end}.new)].should == ["SELECT * FROM t WHERE (foo = bar)", {}]
3800
+ end
3801
+
3802
+ specify "should handle QualifiedIdentifiers" do
3803
+ @u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3804
+ end
3805
+
3806
+ specify "should handle deep nesting" do
3807
+ @u[@ds.filter{foo > 1}.and{bar < 2}.or(:baz=>3).and({~{:x=>4}=>true}.case(false))].should == ["SELECT * FROM t WHERE ((((foo > $foo) AND (bar < $bar)) OR (baz = $baz)) AND (CASE WHEN (x != $x) THEN 't' ELSE 'f' END))", {:foo=>1, :bar=>2, :baz=>3, :x=>4}]
3808
+ end
3809
+
3810
+ specify "should handle JOIN ON" do
3811
+ @u[@ds.cross_join(:x).join(:a, [:u]).join(:b, [[:c, :d], [:e,1]])].should == ["SELECT * FROM t CROSS JOIN x INNER JOIN a USING (u) INNER JOIN b ON ((b.c = a.d) AND (b.e = $b.e))", {:"b.e"=>1}]
3812
+ end
3813
+
3814
+ specify "should raise an UnbindDuplicate exception if same variable is used with multiple different values" do
3815
+ proc{@ds.filter(:foo=>1).or(:foo=>2).unbind}.should raise_error(Sequel::UnbindDuplicate)
3816
+ end
3817
+
3818
+ specify "should handle case where the same variable has the same value in multiple places " do
3819
+ @u[@ds.filter(:foo=>1).or(:foo=>1)].should == ["SELECT * FROM t WHERE ((foo = $foo) OR (foo = $foo))", {:foo=>1}]
3820
+ end
3821
+
3822
+ specify "should raise Error for unhandled objects inside Identifiers and QualifiedIndentifiers" do
3823
+ proc{@ds.filter(Sequel::SQL::Identifier.new([]) > 1).unbind}.should raise_error(Sequel::Error)
3824
+ proc{@ds.filter{foo.qualify({}) > 1}.unbind}.should raise_error(Sequel::Error)
3825
+ end
3826
+ end
3827
+
3602
3828
  describe "Sequel::Dataset #with and #with_recursive" do
3603
3829
  before do
3604
3830
  @db = MockDatabase.new
@@ -3633,6 +3859,15 @@ describe "Sequel::Dataset #with and #with_recursive" do
3633
3859
  proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3634
3860
  proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3635
3861
  end
3862
+
3863
+ specify "#with should work on insert, update, and delete statements if they support it" do
3864
+ [:insert, :update, :delete].each do |m|
3865
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_with_sql"]}
3866
+ end
3867
+ @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3868
+ @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
3869
+ @ds.with(:t, @db[:x]).delete_sql.should == 'WITH t AS (SELECT * FROM x) DELETE FROM t'
3870
+ end
3636
3871
  end
3637
3872
 
3638
3873
  describe Sequel::SQL::Constants do
@@ -3860,6 +4095,14 @@ describe "Sequel::Dataset#select_map" do
3860
4095
  @ds.select_map{a(t__c)}.should == [1, 2]
3861
4096
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
3862
4097
  end
4098
+
4099
+ specify "should handle an array of columns" do
4100
+ @ds.select_map([:c, :c]).should == [[1, 1], [2, 2]]
4101
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
4102
+ @ds.db.reset
4103
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :a__c, :a__d___c]).should == [[1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2]]
4104
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, a.c, a.d']
4105
+ end
3863
4106
  end
3864
4107
 
3865
4108
  describe "Sequel::Dataset#select_order_map" do
@@ -3898,10 +4141,23 @@ describe "Sequel::Dataset#select_order_map" do
3898
4141
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3899
4142
  end
3900
4143
 
4144
+ specify "should handle OrderedExpressions" do
4145
+ @ds.select_order_map(:a.desc).should == [1, 2]
4146
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a DESC']
4147
+ end
4148
+
3901
4149
  specify "should accept a block" do
3902
4150
  @ds.select_order_map{a(t__c)}.should == [1, 2]
3903
4151
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
3904
4152
  end
4153
+
4154
+ specify "should handle an array of columns" do
4155
+ @ds.select_order_map([:c, :c]).should == [[1, 1], [2, 2]]
4156
+ @ds.db.sqls.should == ['SELECT c, c FROM t ORDER BY c, c']
4157
+ @ds.db.reset
4158
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :c.identifier.qualify(:b).desc, :a__c, :a__d___c.desc]).should == [[1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2]]
4159
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, b.c DESC, a.c, a.d DESC']
4160
+ end
3905
4161
  end
3906
4162
 
3907
4163
  describe "Sequel::Dataset#select_hash" do
@@ -3917,7 +4173,7 @@ describe "Sequel::Dataset#select_hash" do
3917
4173
  @ds.db.reset
3918
4174
  end
3919
4175
 
3920
- specify "should do select and map in one step" do
4176
+ specify "should do select and to_hash in one step" do
3921
4177
  @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3922
4178
  @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
3923
4179
  @ds.db.sqls.should == ['SELECT a, b FROM t']
@@ -3940,6 +4196,36 @@ describe "Sequel::Dataset#select_hash" do
3940
4196
  @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
3941
4197
  @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
3942
4198
  end
4199
+
4200
+ specify "should handle SQL::Identifiers in arguments" do
4201
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4202
+ @ds.select_hash(:a.identifier, :b.identifier).should == {1=>2, 3=>4}
4203
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4204
+ end
4205
+
4206
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
4207
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4208
+ @ds.select_hash(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>2, 3=>4}
4209
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4210
+ end
4211
+
4212
+ specify "should handle SQL::AliasedExpressions in arguments" do
4213
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4214
+ @ds.select_hash(:c.as(:a), :t.as(:b)).should == {1=>2, 3=>4}
4215
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
4216
+ end
4217
+
4218
+ specify "should work with arrays of columns" do
4219
+ @ds.set_fr_yield([{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}])
4220
+ @ds.select_hash([:a, :c], :b).should == {[1, 3]=>2, [4, 6]=>5}
4221
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
4222
+ @ds.select_hash(:a, [:b, :c]).should == {1=>[2, 3], 4=>[5, 6]}
4223
+ @ds.select_hash([:a, :b], [:b, :c]).should == {[1, 2]=>[2, 3], [4, 5]=>[5, 6]}
4224
+ end
4225
+
4226
+ specify "should raise an error if the resulting symbol cannot be determined" do
4227
+ proc{@ds.select_hash(:c.as(:a), 'foo')}.should raise_error(Sequel::Error)
4228
+ end
3943
4229
  end
3944
4230
 
3945
4231
  describe "Modifying joined datasets" do
@@ -3977,3 +4263,66 @@ describe "Dataset#lock_style and for_update" do
3977
4263
  @ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
3978
4264
  end
3979
4265
  end
4266
+
4267
+ describe "Custom ASTTransformer" do
4268
+ specify "should transform given objects" do
4269
+ c = Class.new(Sequel::ASTTransformer) do
4270
+ def v(s)
4271
+ (s.is_a?(Symbol) || s.is_a?(String)) ? :"#{s}#{s}" : super
4272
+ end
4273
+ end.new
4274
+ ds = MockDatabase.new[:t].cross_join(:a___g).join(:b___h, [:c]).join(:d___i, :e=>:f)
4275
+ ds.sql.should == 'SELECT * FROM t CROSS JOIN a AS g INNER JOIN b AS h USING (c) INNER JOIN d AS i ON (i.e = h.f)'
4276
+ ds.clone(:from=>c.transform(ds.opts[:from]), :join=>c.transform(ds.opts[:join])).sql.should ==
4277
+ 'SELECT * FROM tt CROSS JOIN aa AS gg INNER JOIN bb AS hh USING (cc) INNER JOIN dd AS ii ON (ii.ee = hh.ff)'
4278
+ end
4279
+ end
4280
+
4281
+ describe "Dataset#returning" do
4282
+ before do
4283
+ @ds = Sequel::Database.new[:t].returning(:foo)
4284
+ @pr = proc do
4285
+ [:insert, :update, :delete].each do |m|
4286
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_returning_sql"]}
4287
+ end
4288
+ end
4289
+ end
4290
+
4291
+ specify "should use RETURNING clause in the SQL if the dataset supports it" do
4292
+ @pr.call
4293
+ @ds.delete_sql.should == "DELETE FROM t RETURNING foo"
4294
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1) RETURNING foo"
4295
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1 RETURNING foo"
4296
+ end
4297
+
4298
+ specify "should not use RETURNING clause in the SQL if the dataset does not support it" do
4299
+ @ds.delete_sql.should == "DELETE FROM t"
4300
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1)"
4301
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1"
4302
+ end
4303
+
4304
+ specify "should have insert, update, and delete yield to blocks if RETURNING is used" do
4305
+ @pr.call
4306
+ def @ds.fetch_rows(sql)
4307
+ yield(:foo=>sql)
4308
+ end
4309
+ h = {}
4310
+ @ds.delete{|r| h = r}
4311
+ h.should == {:foo=>"DELETE FROM t RETURNING foo"}
4312
+ @ds.insert(1){|r| h = r}
4313
+ h.should == {:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}
4314
+ @ds.update(:foo=>1){|r| h = r}
4315
+ h.should == {:foo=>"UPDATE t SET foo = 1 RETURNING foo"}
4316
+ end
4317
+
4318
+ specify "should have insert, update, and delete return arrays of hashes if RETURNING is used and a block is not given" do
4319
+ @pr.call
4320
+ def @ds.fetch_rows(sql)
4321
+ yield(:foo=>sql)
4322
+ end
4323
+ h = {}
4324
+ @ds.delete.should == [{:foo=>"DELETE FROM t RETURNING foo"}]
4325
+ @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
4326
+ @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
4327
+ end
4328
+ end