sequel 3.33.0 → 3.34.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 (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -14,6 +14,8 @@ if ENV['SEQUEL_COLUMNS_INTROSPECTION']
14
14
  Sequel::Dataset.introspect_all_columns
15
15
  end
16
16
 
17
+ Sequel::Model.cache_anonymous_models = false
18
+
17
19
  class Sequel::Database
18
20
  def log_duration(duration, message)
19
21
  log_info(message)
@@ -78,7 +78,7 @@ describe "An SQLite database" do
78
78
  end
79
79
 
80
80
  specify "should provide a list of existing tables" do
81
- @db.drop_table(:testing) rescue nil
81
+ @db.drop_table?(:testing)
82
82
  @db.tables.should be_a_kind_of(Array)
83
83
  @db.tables.should_not include(:testing)
84
84
  @db.create_table! :testing do
@@ -145,12 +145,12 @@ describe "SQLite type conversion" do
145
145
  @integer_booleans = @db.integer_booleans
146
146
  @db.integer_booleans = true
147
147
  @ds = @db[:items]
148
- @db.drop_table(:items) rescue nil
148
+ @db.drop_table?(:items)
149
149
  end
150
150
  after do
151
151
  @db.integer_booleans = @integer_booleans
152
152
  Sequel.datetime_class = Time
153
- @db.drop_table(:items)
153
+ @db.drop_table?(:items)
154
154
  end
155
155
 
156
156
  specify "should handle integers in boolean columns" do
@@ -352,7 +352,7 @@ describe "SQLite dataset" do
352
352
  @d << {:name => 'ghi', :value => 7.89}
353
353
  end
354
354
  after do
355
- SQLITE_DB.drop_table(:test, :items)
355
+ SQLITE_DB.drop_table?(:test, :items)
356
356
  end
357
357
 
358
358
  specify "should be able to insert from a subquery" do
@@ -363,13 +363,13 @@ describe "SQLite dataset" do
363
363
  end
364
364
 
365
365
  specify "should support #explain" do
366
- SQLITE_DB[:test].explain.should be_a_kind_of(Array)
366
+ SQLITE_DB[:test].explain.should be_a_kind_of(String)
367
367
  end
368
368
 
369
369
  specify "should have #explain work when identifier_output_method is modified" do
370
370
  ds = SQLITE_DB[:test]
371
371
  ds.identifier_output_method = :upcase
372
- ds.explain.should be_a_kind_of(Array)
372
+ ds.explain.should be_a_kind_of(String)
373
373
  end
374
374
  end
375
375
 
@@ -457,7 +457,7 @@ describe "A SQLite database" do
457
457
  @db[:test].filter(:name => 'foo').update(:id=>100)
458
458
  @db[:test3][:name => 'abc'][:test_id].should == 100
459
459
 
460
- @db.drop_table :test, :test3
460
+ @db.drop_table? :test, :test3
461
461
  end
462
462
  end
463
463
 
@@ -495,7 +495,7 @@ describe "A SQLite database" do
495
495
  specify "should handle quoted tables when dropping or renaming columns" do
496
496
  @db.quote_identifiers = true
497
497
  table_name = "T T"
498
- @db.drop_table(table_name) rescue nil
498
+ @db.drop_table?(table_name)
499
499
  @db.create_table! table_name do
500
500
  Integer :"s s"
501
501
  Integer :"i i"
@@ -228,6 +228,45 @@ describe "A connection pool with a max size of 1" do
228
228
  end
229
229
 
230
230
  shared_examples_for "A threaded connection pool" do
231
+ specify "should not have all_connections yield connections allocated to other threads" do
232
+ pool = Sequel::ConnectionPool.get_pool(@cp_opts.merge(:max_connections=>2, :pool_timeout=>0)) {@invoked_count += 1}
233
+ q, q1 = Queue.new, Queue.new
234
+ t = Thread.new do
235
+ pool.hold do |c1|
236
+ q1.push nil
237
+ q.pop
238
+ end
239
+ end
240
+ pool.hold do |c1|
241
+ q1.pop
242
+ pool.all_connections{|c| c.should == c1}
243
+ q.push nil
244
+ end
245
+ t.join
246
+ end
247
+
248
+ specify "should not have all_connections yield all available connections" do
249
+ pool = Sequel::ConnectionPool.get_pool(@cp_opts.merge(:max_connections=>2, :pool_timeout=>0)){@invoked_count += 1}
250
+ q, q1 = Queue.new, Queue.new
251
+ b = []
252
+ t = Thread.new do
253
+ pool.hold do |c1|
254
+ b << c1
255
+ q1.push nil
256
+ q.pop
257
+ end
258
+ end
259
+ pool.hold do |c1|
260
+ q1.pop
261
+ b << c1
262
+ q.push nil
263
+ end
264
+ t.join
265
+ a = []
266
+ pool.all_connections{|c| a << c}
267
+ a.sort.should == b.sort
268
+ end
269
+
231
270
  specify "should raise a PoolTimeout error if a connection couldn't be acquired before timeout" do
232
271
  x = nil
233
272
  q, q1 = Queue.new, Queue.new
@@ -370,6 +409,16 @@ describe "A connection pool with multiple servers" do
370
409
  @pool = Sequel::ConnectionPool.get_pool(CONNECTION_POOL_DEFAULTS.merge(:servers=>{:read_only=>{}})){|server| "#{server}#{@invoked_counts[server] += 1}"}
371
410
  end
372
411
 
412
+ specify "#all_connections should return connections for all servers" do
413
+ @pool.hold{}
414
+ @pool.all_connections{|c1| c1.should == "default1"}
415
+ a = []
416
+ @pool.hold(:read_only) do |c|
417
+ @pool.all_connections{|c1| a << c1}
418
+ end
419
+ a.sort_by{|c| c.to_s}.should == ["default1", "read_only1"]
420
+ end
421
+
373
422
  specify "#servers should return symbols for all servers" do
374
423
  @pool.servers.sort_by{|s| s.to_s}.should == [:default, :read_only]
375
424
  end
@@ -624,6 +673,16 @@ describe "A single threaded pool with multiple servers" do
624
673
  @pool = Sequel::ConnectionPool.get_pool(ST_CONNECTION_POOL_DEFAULTS.merge(:disconnection_proc=>proc{|c| @max_size=3}, :servers=>{:read_only=>{}})){|server| server}
625
674
  end
626
675
 
676
+ specify "#all_connections should return connections for all servers" do
677
+ @pool.hold{}
678
+ @pool.all_connections{|c1| c1.should == :default}
679
+ a = []
680
+ @pool.hold(:read_only) do
681
+ @pool.all_connections{|c1| a << c1}
682
+ end
683
+ a.sort_by{|c| c.to_s}.should == [:default, :read_only]
684
+ end
685
+
627
686
  specify "#servers should return symbols for all servers" do
628
687
  @pool.servers.sort_by{|s| s.to_s}.should == [:default, :read_only]
629
688
  end
@@ -730,6 +789,32 @@ describe "A single threaded pool with multiple servers" do
730
789
  end
731
790
 
732
791
  shared_examples_for "All connection pools classes" do
792
+ specify "should have all_connections yield current and available connections" do
793
+ p = @class.new({}){123}
794
+ p.hold{|c| p.all_connections{|c1| c.should == c1}}
795
+ end
796
+
797
+ specify "should be able to modify after_connect proc after the pool is created" do
798
+ a = []
799
+ p = @class.new({}){123}
800
+ p.after_connect = pr = proc{|c| a << c}
801
+ p.after_connect.should == pr
802
+ a.should == []
803
+ p.hold{}
804
+ a.should == [123]
805
+ end
806
+
807
+ specify "should be able to modify disconnection_proc after the pool is created" do
808
+ a = []
809
+ p = @class.new({}){123}
810
+ p.disconnection_proc = pr = proc{|c| a << c}
811
+ p.disconnection_proc.should == pr
812
+ p.hold{}
813
+ a.should == []
814
+ p.disconnect
815
+ a.should == [123]
816
+ end
817
+
733
818
  specify "should not raise an error when disconnecting twice" do
734
819
  c = @class.new({}){123}
735
820
  proc{c.disconnect}.should_not raise_error
@@ -325,6 +325,10 @@ describe "Database#uri" do
325
325
  @db.uri.should == 'mau://user:pass@localhost:9876/maumau'
326
326
  end
327
327
 
328
+ specify "should return nil if a connection uri was not used" do
329
+ Sequel.mock.uri.should be_nil
330
+ end
331
+
328
332
  specify "should be aliased as #url" do
329
333
  @db.url.should == 'mau://user:pass@localhost:9876/maumau'
330
334
  end
@@ -413,6 +417,13 @@ describe "Database#extend_datasets" do
413
417
  @db.extend_datasets(@m)
414
418
  end
415
419
 
420
+ specify "should clear a cached dataset" do
421
+ @db = Sequel::Database.new
422
+ @db.literal(1).should == '1'
423
+ @db.extend_datasets{def literal(v) '2' end}
424
+ @db.literal(1).should == '2'
425
+ end
426
+
416
427
  specify "should change the dataset class to a subclass the first time it is called" do
417
428
  @db.dataset_class.superclass.should == Sequel::Dataset
418
429
  end
@@ -484,6 +495,12 @@ describe "Database#indexes" do
484
495
  end
485
496
  end
486
497
 
498
+ describe "Database#foreign_key_list" do
499
+ specify "should raise Sequel::NotImplemented" do
500
+ proc {Sequel::Database.new.foreign_key_list(:table)}.should raise_error(Sequel::NotImplemented)
501
+ end
502
+ end
503
+
487
504
  describe "Database#run" do
488
505
  before do
489
506
  @db = Sequel.mock(:servers=>{:s1=>{}})
@@ -1061,21 +1078,20 @@ describe "A Database adapter with a scheme" do
1061
1078
  proc {Sequel.ccc('abc', 'def')}.should raise_error(Sequel::Error)
1062
1079
 
1063
1080
  c = Sequel.ccc('mydb')
1064
- p = proc{c.opts.delete_if{|k,v| k == :disconnection_proc || k == :single_threaded}}
1065
1081
  c.should be_a_kind_of(@ccc)
1066
- p.call.should == {:adapter=>:ccc, :database => 'mydb', :adapter_class=>@ccc}
1082
+ c.opts.values_at(:adapter, :database, :adapter_class).should == [:ccc, 'mydb', @ccc]
1067
1083
 
1068
1084
  c = Sequel.ccc('mydb', :host => 'localhost')
1069
1085
  c.should be_a_kind_of(@ccc)
1070
- p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost', :adapter_class=>@ccc}
1086
+ c.opts.values_at(:adapter, :database, :host, :adapter_class).should == [:ccc, 'mydb', 'localhost', @ccc]
1071
1087
 
1072
1088
  c = Sequel.ccc
1073
1089
  c.should be_a_kind_of(@ccc)
1074
- p.call.should == {:adapter=>:ccc, :adapter_class=>@ccc}
1090
+ c.opts.values_at(:adapter, :adapter_class).should == [:ccc, @ccc]
1075
1091
 
1076
1092
  c = Sequel.ccc(:database => 'mydb', :host => 'localhost')
1077
1093
  c.should be_a_kind_of(@ccc)
1078
- p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost', :adapter_class=>@ccc}
1094
+ c.opts.values_at(:adapter, :database, :host, :adapter_class).should == [:ccc, 'mydb', 'localhost', @ccc]
1079
1095
  end
1080
1096
 
1081
1097
  specify "should be accessible through Sequel.connect with options" do
@@ -1336,6 +1352,14 @@ describe "Database#inspect" do
1336
1352
  specify "should include the class name and the connection url" do
1337
1353
  Sequel.connect('mock://foo/bar').inspect.should == '#<Sequel::Mock::Database: "mock://foo/bar">'
1338
1354
  end
1355
+
1356
+ specify "should include the class name and the connection options if an options hash was given" do
1357
+ Sequel.connect(:adapter=>:mock).inspect.should =~ /#<Sequel::Mock::Database: \{:adapter=>:mock\}>/
1358
+ end
1359
+
1360
+ specify "should include the class name, uri, and connection options if uri and options hash was given" do
1361
+ Sequel.connect('mock://foo', :database=>'bar').inspect.should =~ /#<Sequel::Mock::Database: "mock:\/\/foo" \{:database=>"bar"\}>/
1362
+ end
1339
1363
  end
1340
1364
 
1341
1365
  describe "Database#get" do
@@ -914,12 +914,18 @@ describe "Dataset#group_by" do
914
914
  @dataset.meta_def(:supports_group_rollup?){true}
915
915
  @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id)"
916
916
  @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id, b)"
917
+ @dataset.meta_def(:uses_with_rollup?){true}
918
+ @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY type_id WITH ROLLUP"
919
+ @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY type_id, b WITH ROLLUP"
917
920
  end
918
921
 
919
922
  specify "should support a #group_cube method if the database supports it" do
920
923
  @dataset.meta_def(:supports_group_cube?){true}
921
924
  @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id)"
922
925
  @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id, b)"
926
+ @dataset.meta_def(:uses_with_rollup?){true}
927
+ @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY type_id WITH CUBE"
928
+ @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY type_id, b WITH CUBE"
923
929
  end
924
930
 
925
931
  specify "should have #group_cube and #group_rollup methods raise an Error if not supported it" do
@@ -935,6 +941,42 @@ describe "Dataset#as" do
935
941
  end
936
942
  end
937
943
 
944
+ describe "Dataset#literal" do
945
+ before do
946
+ @ds = Sequel::Database.new.dataset
947
+ end
948
+
949
+ specify "should convert qualified symbol notation into dot notation" do
950
+ @ds.literal(:abc__def).should == 'abc.def'
951
+ end
952
+
953
+ specify "should convert AS symbol notation into SQL AS notation" do
954
+ @ds.literal(:xyz___x).should == 'xyz AS x'
955
+ @ds.literal(:abc__def___x).should == 'abc.def AS x'
956
+ end
957
+
958
+ specify "should support names with digits" do
959
+ @ds.literal(:abc2).should == 'abc2'
960
+ @ds.literal(:xx__yy3).should == 'xx.yy3'
961
+ @ds.literal(:ab34__temp3_4ax).should == 'ab34.temp3_4ax'
962
+ @ds.literal(:x1___y2).should == 'x1 AS y2'
963
+ @ds.literal(:abc2__def3___ggg4).should == 'abc2.def3 AS ggg4'
964
+ end
965
+
966
+ specify "should support upper case and lower case" do
967
+ @ds.literal(:ABC).should == 'ABC'
968
+ @ds.literal(:Zvashtoy__aBcD).should == 'Zvashtoy.aBcD'
969
+ end
970
+
971
+ specify "should support spaces inside column names" do
972
+ @ds.quote_identifiers = true
973
+ @ds.literal(:"AB C").should == '"AB C"'
974
+ @ds.literal(:"Zvas htoy__aB cD").should == '"Zvas htoy"."aB cD"'
975
+ @ds.literal(:"aB cD___XX XX").should == '"aB cD" AS "XX XX"'
976
+ @ds.literal(:"Zva shtoy__aB cD___XX XX").should == '"Zva shtoy"."aB cD" AS "XX XX"'
977
+ end
978
+ end
979
+
938
980
  describe "Dataset#literal" do
939
981
  before do
940
982
  @dataset = Sequel::Database.new.from(:test)
@@ -1105,6 +1147,17 @@ describe "Dataset#literal" do
1105
1147
  @dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
1106
1148
  end
1107
1149
 
1150
+ specify "should literalize PlaceholderLiteralStrings correctly" do
1151
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new('? = ?', [1, 2])).should == '1 = 2'
1152
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new('? = ?', [1, 2], true)).should == '(1 = 2)'
1153
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(':a = :b', :a=>1, :b=>2)).should == '1 = 2'
1154
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(':a = :b', {:a=>1, :b=>2}, true)).should == '(1 = 2)'
1155
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = ', ''], [1, 2])).should == '1 = 2'
1156
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = ', ''], [1, 2], true)).should == '(1 = 2)'
1157
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = '], [1, 2])).should == '1 = 2'
1158
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = '], [1, 2], true)).should == '(1 = 2)'
1159
+ end
1160
+
1108
1161
  specify "should raise an Error if the object can't be literalized" do
1109
1162
  proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
1110
1163
  end
@@ -1720,7 +1773,45 @@ describe "Dataset#to_hash" do
1720
1773
  @d.to_hash(:b).should == {4 => {:a => 2, :b => 4}, 8 => {:a => 6, :b => 8}, 12 => {:a => 10, :b => 12}}
1721
1774
  @d.to_hash([:a, :b]).should == {[2, 4] => {:a => 2, :b => 4}, [6, 8] => {:a => 6, :b => 8}, [10, 12] => {:a => 10, :b => 12}}
1722
1775
  end
1776
+ end
1777
+
1778
+ describe "Dataset#to_hash_groups" do
1779
+ before do
1780
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 1, :b => 6}, {:a => 7, :b => 4}])[:items]
1781
+ end
1723
1782
 
1783
+ specify "should provide a hash with the first column as key and the second as value" do
1784
+ @d.to_hash_groups(:a, :b).should == {1 => [2, 6], 3 => [4], 7 => [4]}
1785
+ @d.to_hash_groups(:b, :a).should == {2 => [1], 4=>[3, 7], 6=>[1]}
1786
+ end
1787
+
1788
+ specify "should provide a hash with the first column as key and the entire hash as value if the value column is blank or nil" do
1789
+ @d.to_hash_groups(:a).should == {1 => [{:a => 1, :b => 2}, {:a => 1, :b => 6}], 3 => [{:a => 3, :b => 4}], 7 => [{:a => 7, :b => 4}]}
1790
+ @d.to_hash_groups(:b).should == {2 => [{:a => 1, :b => 2}], 4 => [{:a => 3, :b => 4}, {:a => 7, :b => 4}], 6 => [{:a => 1, :b => 6}]}
1791
+ end
1792
+
1793
+ specify "should support using an array of columns as either the key or the value" do
1794
+ @d.to_hash_groups([:a, :b], :b).should == {[1, 2] => [2], [3, 4] => [4], [1, 6] => [6], [7, 4]=>[4]}
1795
+ @d.to_hash_groups(:b, [:a, :b]).should == {2 => [[1, 2]], 4 => [[3, 4], [7, 4]], 6 => [[1, 6]]}
1796
+ @d.to_hash_groups([:b, :a], [:a, :b]).should == {[2, 1] => [[1, 2]], [4, 3] => [[3, 4]], [6, 1] => [[1, 6]], [4, 7]=>[[7, 4]]}
1797
+ @d.to_hash_groups([:a, :b]).should == {[1, 2] => [{:a => 1, :b => 2}], [3, 4] => [{:a => 3, :b => 4}], [1, 6] => [{:a => 1, :b => 6}], [7, 4] => [{:a => 7, :b => 4}]}
1798
+ end
1799
+
1800
+ specify "should not call the row_proc if two arguments are given" do
1801
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1802
+ @d.to_hash_groups(:a, :b).should == {1 => [2, 6], 3 => [4], 7 => [4]}
1803
+ @d.to_hash_groups(:b, :a).should == {2 => [1], 4=>[3, 7], 6=>[1]}
1804
+ @d.to_hash_groups([:a, :b], :b).should == {[1, 2] => [2], [3, 4] => [4], [1, 6] => [6], [7, 4]=>[4]}
1805
+ @d.to_hash_groups(:b, [:a, :b]).should == {2 => [[1, 2]], 4 => [[3, 4], [7, 4]], 6 => [[1, 6]]}
1806
+ @d.to_hash_groups([:b, :a], [:a, :b]).should == {[2, 1] => [[1, 2]], [4, 3] => [[3, 4]], [6, 1] => [[1, 6]], [4, 7]=>[[7, 4]]}
1807
+ end
1808
+
1809
+ specify "should call the row_proc if only a single argument is given" do
1810
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1811
+ @d.to_hash_groups(:a).should == {2 => [{:a => 2, :b => 4}, {:a => 2, :b => 12}], 6 => [{:a => 6, :b => 8}], 14 => [{:a => 14, :b => 8}]}
1812
+ @d.to_hash_groups(:b).should == {4 => [{:a => 2, :b => 4}], 8 => [{:a => 6, :b => 8}, {:a => 14, :b => 8}], 12 => [{:a => 2, :b => 12}]}
1813
+ @d.to_hash_groups([:a, :b]).should == {[2, 4] => [{:a => 2, :b => 4}], [6, 8] => [{:a => 6, :b => 8}], [2, 12] => [{:a => 2, :b => 12}], [14, 8] => [{:a => 14, :b => 8}]}
1814
+ end
1724
1815
  end
1725
1816
 
1726
1817
  describe "Dataset#distinct" do
@@ -3077,7 +3168,7 @@ describe "Dataset#grep" do
3077
3168
  end
3078
3169
  end
3079
3170
 
3080
- describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3171
+ describe "Dataset default #fetch_rows, #insert, #update, #delete, #with_sql_delete, #truncate, #execute" do
3081
3172
  before do
3082
3173
  @db = Sequel::Database.new
3083
3174
  @ds = @db[:items]
@@ -3094,6 +3185,14 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
3094
3185
  @ds.delete
3095
3186
  end
3096
3187
 
3188
+ specify "#with_sql_delete should execute delete SQL" do
3189
+ sql = 'DELETE FROM foo'
3190
+ @db.should_receive(:execute).once.with(sql, :server=>:default)
3191
+ @ds.with_sql_delete(sql)
3192
+ @db.should_receive(:execute_dui).once.with(sql, :server=>:default)
3193
+ @ds.with_sql_delete(sql)
3194
+ end
3195
+
3097
3196
  specify "#insert should execute insert SQL" do
3098
3197
  @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3099
3198
  @ds.insert([])
@@ -3136,12 +3235,14 @@ describe "Dataset prepared statements and bound variables " do
3136
3235
  @ds.filter(:num=>:$n).call(:select, :n=>1)
3137
3236
  @ds.filter(:num=>:$n).call([:map, :a], :n=>1)
3138
3237
  @ds.filter(:num=>:$n).call([:to_hash, :a, :b], :n=>1)
3238
+ @ds.filter(:num=>:$n).call([:to_hash_groups, :a, :b], :n=>1)
3139
3239
  @ds.filter(:num=>:$n).call(:first, :n=>1)
3140
3240
  @ds.filter(:num=>:$n).call(:delete, :n=>1)
3141
3241
  @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3142
3242
  @ds.call(:insert, {:n=>1}, :num=>:$n)
3143
3243
  @ds.call(:insert_select, {:n=>1}, :num=>:$n)
3144
3244
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3245
+ 'SELECT * FROM items WHERE (num = 1)',
3145
3246
  'SELECT * FROM items WHERE (num = 1)',
3146
3247
  'SELECT * FROM items WHERE (num = 1)',
3147
3248
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
@@ -3156,22 +3257,25 @@ describe "Dataset prepared statements and bound variables " do
3156
3257
  pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3157
3258
  pss << @ds.filter(:num=>:$n).prepare([:map, :a], :sm)
3158
3259
  pss << @ds.filter(:num=>:$n).prepare([:to_hash, :a, :b], :sh)
3260
+ pss << @ds.filter(:num=>:$n).prepare([:to_hash_groups, :a, :b], :shg)
3159
3261
  pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3160
3262
  pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3161
3263
  pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3162
3264
  pss << @ds.prepare(:insert, :in, :num=>:$n)
3163
3265
  pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
3164
- @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sh, :sm, :sn, :un]
3165
- [:sn, :sm, :sh, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3266
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sh, :shg, :sm, :sn, :un]
3267
+ [:sn, :sm, :sh, :shg, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3166
3268
  @db.call(:sn, :n=>1)
3167
3269
  @db.call(:sm, :n=>1)
3168
3270
  @db.call(:sh, :n=>1)
3271
+ @db.call(:shg, :n=>1)
3169
3272
  @db.call(:fn, :n=>1)
3170
3273
  @db.call(:dn, :n=>1)
3171
3274
  @db.call(:un, :n=>1, :n2=>2)
3172
3275
  @db.call(:in, :n=>1)
3173
3276
  @db.call(:ins, :n=>1)
3174
3277
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3278
+ 'SELECT * FROM items WHERE (num = 1)',
3175
3279
  'SELECT * FROM items WHERE (num = 1)',
3176
3280
  'SELECT * FROM items WHERE (num = 1)',
3177
3281
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
@@ -3427,6 +3531,10 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3427
3531
  @ds.filter(':a > :b', :a=>:c, :b=>1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.c > 1)'
3428
3532
  end
3429
3533
 
3534
+ specify "should handle SQL::Wrappers" do
3535
+ @ds.filter(Sequel::SQL::Wrapper.new(:a)).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a'
3536
+ end
3537
+
3430
3538
  specify "should handle SQL::WindowFunctions" do
3431
3539
  @ds.meta_def(:supports_window_functions?){true}
3432
3540
  @ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify_to_first_source.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
@@ -3485,6 +3593,10 @@ describe "Sequel::Dataset#unbind" do
3485
3593
  @u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3486
3594
  end
3487
3595
 
3596
+ specify "should handle wrapped objects" do
3597
+ @u[@ds.filter{Sequel::SQL::Wrapper.new(foo__bar) > Sequel::SQL::Wrapper.new(1)}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3598
+ end
3599
+
3488
3600
  specify "should handle deep nesting" do
3489
3601
  @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}]
3490
3602
  end
@@ -3972,6 +4084,62 @@ describe "Sequel::Dataset#select_hash" do
3972
4084
  end
3973
4085
  end
3974
4086
 
4087
+ describe "Sequel::Dataset#select_hash_groups" do
4088
+ before do
4089
+ @db = Sequel.mock(:fetch=>[{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4090
+ @ds = @db[:t]
4091
+ end
4092
+
4093
+ specify "should do select and to_hash in one step" do
4094
+ @ds.select_hash_groups(:a, :b).should == {1=>[2], 3=>[4]}
4095
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4096
+ end
4097
+
4098
+ specify "should handle implicit qualifiers in arguments" do
4099
+ @ds.select_hash_groups(:t__a, :t__b).should == {1=>[2], 3=>[4]}
4100
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4101
+ end
4102
+
4103
+ specify "should handle implicit aliases in arguments" do
4104
+ @ds.select_hash_groups(:c___a, :d___b).should == {1=>[2], 3=>[4]}
4105
+ @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
4106
+ end
4107
+
4108
+ specify "should handle implicit qualifiers and aliases in arguments" do
4109
+ @ds.select_hash_groups(:t__c___a, :t__d___b).should == {1=>[2], 3=>[4]}
4110
+ @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
4111
+ end
4112
+
4113
+ specify "should handle SQL::Identifiers in arguments" do
4114
+ @ds.select_hash_groups(:a.identifier, :b.identifier).should == {1=>[2], 3=>[4]}
4115
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4116
+ end
4117
+
4118
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
4119
+ @ds.select_hash_groups(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>[2], 3=>[4]}
4120
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4121
+ end
4122
+
4123
+ specify "should handle SQL::AliasedExpressions in arguments" do
4124
+ @ds.select_hash_groups(:c.as(:a), :t.as(:b)).should == {1=>[2], 3=>[4]}
4125
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
4126
+ end
4127
+
4128
+ specify "should work with arrays of columns" do
4129
+ @db.fetch = [{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}]
4130
+ @ds.select_hash_groups([:a, :c], :b).should == {[1, 3]=>[2], [4, 6]=>[5]}
4131
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
4132
+ @ds.select_hash_groups(:a, [:b, :c]).should == {1=>[[2, 3]], 4=>[[5, 6]]}
4133
+ @ds.db.sqls.should == ['SELECT a, b, c FROM t']
4134
+ @ds.select_hash_groups([:a, :b], [:b, :c]).should == {[1, 2]=>[[2, 3]], [4, 5]=>[[5, 6]]}
4135
+ @ds.db.sqls.should == ['SELECT a, b, b, c FROM t']
4136
+ end
4137
+
4138
+ specify "should raise an error if the resulting symbol cannot be determined" do
4139
+ proc{@ds.select_hash_groups(:c.as(:a), :b.sql_function)}.should raise_error(Sequel::Error)
4140
+ end
4141
+ end
4142
+
3975
4143
  describe "Modifying joined datasets" do
3976
4144
  before do
3977
4145
  @ds = Sequel.mock.from(:b, :c).join(:d, [:id]).where(:id => 2)