sequel 3.38.0 → 3.39.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 (79) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +2 -2
  3. data/bin/sequel +12 -2
  4. data/doc/advanced_associations.rdoc +1 -1
  5. data/doc/association_basics.rdoc +13 -0
  6. data/doc/release_notes/3.39.0.txt +237 -0
  7. data/doc/schema_modification.rdoc +4 -4
  8. data/lib/sequel/adapters/jdbc/derby.rb +1 -0
  9. data/lib/sequel/adapters/mock.rb +5 -0
  10. data/lib/sequel/adapters/mysql.rb +8 -1
  11. data/lib/sequel/adapters/mysql2.rb +10 -3
  12. data/lib/sequel/adapters/postgres.rb +72 -8
  13. data/lib/sequel/adapters/shared/db2.rb +1 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +57 -0
  15. data/lib/sequel/adapters/shared/mysql.rb +95 -19
  16. data/lib/sequel/adapters/shared/oracle.rb +14 -0
  17. data/lib/sequel/adapters/shared/postgres.rb +63 -24
  18. data/lib/sequel/adapters/shared/sqlite.rb +6 -9
  19. data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
  20. data/lib/sequel/connection_pool/threaded.rb +9 -4
  21. data/lib/sequel/database/query.rb +60 -48
  22. data/lib/sequel/database/schema_generator.rb +13 -6
  23. data/lib/sequel/database/schema_methods.rb +65 -12
  24. data/lib/sequel/dataset/actions.rb +22 -4
  25. data/lib/sequel/dataset/features.rb +5 -0
  26. data/lib/sequel/dataset/graph.rb +2 -3
  27. data/lib/sequel/dataset/misc.rb +2 -2
  28. data/lib/sequel/dataset/query.rb +0 -2
  29. data/lib/sequel/dataset/sql.rb +33 -12
  30. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  31. data/lib/sequel/extensions/eval_inspect.rb +17 -2
  32. data/lib/sequel/extensions/pg_array_ops.rb +15 -5
  33. data/lib/sequel/extensions/pg_interval.rb +2 -2
  34. data/lib/sequel/extensions/pg_row_ops.rb +18 -0
  35. data/lib/sequel/extensions/schema_dumper.rb +3 -11
  36. data/lib/sequel/model/associations.rb +3 -2
  37. data/lib/sequel/model/base.rb +57 -13
  38. data/lib/sequel/model/exceptions.rb +20 -2
  39. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  40. data/lib/sequel/plugins/defaults_setter.rb +15 -1
  41. data/lib/sequel/plugins/dirty.rb +2 -2
  42. data/lib/sequel/plugins/identity_map.rb +12 -8
  43. data/lib/sequel/plugins/subclasses.rb +19 -1
  44. data/lib/sequel/plugins/tree.rb +3 -3
  45. data/lib/sequel/plugins/validation_helpers.rb +24 -4
  46. data/lib/sequel/sql.rb +64 -24
  47. data/lib/sequel/timezones.rb +10 -2
  48. data/lib/sequel/version.rb +1 -1
  49. data/spec/adapters/mssql_spec.rb +26 -25
  50. data/spec/adapters/mysql_spec.rb +57 -23
  51. data/spec/adapters/oracle_spec.rb +34 -49
  52. data/spec/adapters/postgres_spec.rb +226 -128
  53. data/spec/adapters/sqlite_spec.rb +50 -49
  54. data/spec/core/connection_pool_spec.rb +22 -0
  55. data/spec/core/database_spec.rb +53 -47
  56. data/spec/core/dataset_spec.rb +36 -32
  57. data/spec/core/expression_filters_spec.rb +14 -2
  58. data/spec/core/mock_adapter_spec.rb +4 -0
  59. data/spec/core/object_graph_spec.rb +0 -13
  60. data/spec/core/schema_spec.rb +64 -5
  61. data/spec/core_extensions_spec.rb +1 -0
  62. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  63. data/spec/extensions/constraint_validations_spec.rb +316 -0
  64. data/spec/extensions/defaults_setter_spec.rb +24 -0
  65. data/spec/extensions/eval_inspect_spec.rb +9 -0
  66. data/spec/extensions/identity_map_spec.rb +11 -2
  67. data/spec/extensions/pg_array_ops_spec.rb +9 -0
  68. data/spec/extensions/pg_row_ops_spec.rb +11 -1
  69. data/spec/extensions/pg_row_plugin_spec.rb +4 -0
  70. data/spec/extensions/schema_dumper_spec.rb +8 -5
  71. data/spec/extensions/subclasses_spec.rb +14 -0
  72. data/spec/extensions/validation_helpers_spec.rb +15 -2
  73. data/spec/integration/dataset_test.rb +75 -1
  74. data/spec/integration/plugin_test.rb +146 -0
  75. data/spec/integration/schema_test.rb +34 -0
  76. data/spec/model/dataset_methods_spec.rb +38 -0
  77. data/spec/model/hooks_spec.rb +6 -0
  78. data/spec/model/validations_spec.rb +27 -2
  79. metadata +8 -2
@@ -14,10 +14,6 @@ INTEGRATION_DB = MYSQL_DB unless defined?(INTEGRATION_DB)
14
14
 
15
15
  MYSQL_URI = URI.parse(MYSQL_DB.uri)
16
16
 
17
- MYSQL_DB.create_table! :test2 do
18
- text :name
19
- integer :value
20
- end
21
17
  def MYSQL_DB.sqls
22
18
  (@sqls ||= [])
23
19
  end
@@ -86,24 +82,17 @@ describe "MySQL", '#create_table' do
86
82
  @db[:dolls].insert
87
83
  @db[:dolls].select_map(:name).should == ["foo"]
88
84
  end
89
- end
90
-
91
- describe "A MySQL database" do
92
- specify "should provide the server version" do
93
- MYSQL_DB.server_version.should >= 40000
94
- end
95
85
 
96
- specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
97
- proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
98
- end
99
-
100
- specify "should support for_share" do
101
- MYSQL_DB.transaction{MYSQL_DB[:test2].for_share.all.should == []}
86
+ specify "should be able to parse the default value for set and enum types" do
87
+ @db.create_table!(:dolls){column :t, "set('a', 'b', 'c', 'd')", :default=>'a,b'}
88
+ @db.schema(:dolls).first.last[:ruby_default].should == 'a,b'
89
+ @db.create_table!(:dolls){column :t, "enum('a', 'b', 'c', 'd')", :default=>'b'}
90
+ @db.schema(:dolls).first.last[:ruby_default].should == 'b'
102
91
  end
103
92
  end
104
93
 
105
- if MYSQL_DB.adapter_scheme == :mysql
106
- describe "Sequel::MySQL.convert_tinyint_to_bool" do
94
+ if [:mysql, :mysql2].include?(MYSQL_DB.adapter_scheme)
95
+ describe "Sequel::MySQL::Database#convert_tinyint_to_bool" do
107
96
  before do
108
97
  @db = MYSQL_DB
109
98
  @db.create_table(:booltest){column :b, 'tinyint(1)'; column :i, 'tinyint(4)'}
@@ -149,6 +138,17 @@ if MYSQL_DB.adapter_scheme == :mysql
149
138
  @ds << {:b=>0, :i=>0}
150
139
  @ds.all.should == [{:b=>0, :i=>0}]
151
140
  end
141
+
142
+ specify "should allow disabling the conversion on a per-dataset basis" do
143
+ @db.convert_tinyint_to_bool = true
144
+ ds = @ds.clone
145
+ ds.meta_def(:cast_tinyint_integer?){|f| true} # mysql
146
+ ds.meta_def(:convert_tinyint_to_bool?){false} # mysql2
147
+ ds.delete
148
+ ds << {:b=>true, :i=>10}
149
+ ds.all.should == [{:b=>1, :i=>10}]
150
+ @ds.all.should == [{:b=>true, :i=>10}]
151
+ end
152
152
  end
153
153
  end
154
154
 
@@ -269,7 +269,6 @@ end
269
269
  describe "MySQL join expressions" do
270
270
  before do
271
271
  @ds = MYSQL_DB[:nodes]
272
- @ds.db.meta_def(:server_version) {50014}
273
272
  end
274
273
 
275
274
  specify "should raise error for :full_outer join requests." do
@@ -342,8 +341,41 @@ describe "Joined MySQL dataset" do
342
341
  end
343
342
 
344
343
  describe "A MySQL database" do
345
- before do
344
+ after do
345
+ MYSQL_DB.drop_table?(:test_innodb)
346
+ end
347
+
348
+ specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
349
+ proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
350
+ end
351
+ end
352
+
353
+ describe "A MySQL database" do
354
+ before(:all) do
346
355
  @db = MYSQL_DB
356
+ @db.create_table! :test2 do
357
+ text :name
358
+ integer :value
359
+ end
360
+ end
361
+ after(:all) do
362
+ @db.drop_table?(:test2)
363
+ end
364
+
365
+ specify "should provide the server version" do
366
+ @db.server_version.should >= 40000
367
+ end
368
+
369
+ specify "should cache the server version" do
370
+ # warm cache:
371
+ @db.server_version
372
+ @db.sqls.clear
373
+ 3.times{@db.server_version}
374
+ @db.sqls.should be_empty
375
+ end
376
+
377
+ specify "should support for_share" do
378
+ @db.transaction{@db[:test2].for_share.all.should == []}
347
379
  end
348
380
 
349
381
  specify "should support add_column operations" do
@@ -472,8 +504,7 @@ describe "A MySQL database" do
472
504
  @db.alter_table(:items){add_foreign_key :p_id, :users, :key => :id, :null => false, :on_delete => :cascade}
473
505
  @db.sqls.should == ["CREATE TABLE `items` (`id` integer)",
474
506
  "CREATE TABLE `users` (`id` integer PRIMARY KEY AUTO_INCREMENT)",
475
- "ALTER TABLE `items` ADD COLUMN `p_id` integer NOT NULL",
476
- "ALTER TABLE `items` ADD FOREIGN KEY (`p_id`) REFERENCES `users`(`id`) ON DELETE CASCADE"]
507
+ "ALTER TABLE `items` ADD COLUMN `p_id` integer NOT NULL, ADD FOREIGN KEY (`p_id`) REFERENCES `users`(`id`) ON DELETE CASCADE"]
477
508
  end
478
509
 
479
510
  specify "should have rename_column support keep existing options" do
@@ -500,7 +531,7 @@ describe "A MySQL database" do
500
531
  specify "should have set_column_type pass through options" do
501
532
  @db.create_table(:items){integer :id; enum :list, :elements=>%w[one]}
502
533
  @db.alter_table(:items){set_column_type :id, :int, :unsigned=>true, :size=>8; set_column_type :list, :enum, :elements=>%w[two]}
503
- @db.sqls.should == ["CREATE TABLE `items` (`id` integer, `list` enum('one'))", "DESCRIBE `items`", "ALTER TABLE `items` CHANGE COLUMN `id` `id` int(8) UNSIGNED NULL", "ALTER TABLE `items` CHANGE COLUMN `list` `list` enum('two') NULL"]
534
+ @db.sqls.should == ["CREATE TABLE `items` (`id` integer, `list` enum('one'))", "DESCRIBE `items`", "ALTER TABLE `items` CHANGE COLUMN `id` `id` int(8) UNSIGNED NULL, CHANGE COLUMN `list` `list` enum('two') NULL"]
504
535
  end
505
536
 
506
537
  specify "should have set_column_default support keep existing options" do
@@ -625,6 +656,9 @@ describe "A grouped MySQL dataset" do
625
656
  MYSQL_DB[:test2] << {:name => '12', :value => 20}
626
657
  MYSQL_DB[:test2] << {:name => '13', :value => 10}
627
658
  end
659
+ after do
660
+ MYSQL_DB.drop_table?(:test2)
661
+ end
628
662
 
629
663
  specify "should return the correct count for raw sql query" do
630
664
  ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
@@ -5,25 +5,34 @@ unless defined?(ORACLE_DB)
5
5
  end
6
6
  INTEGRATION_DB = ORACLE_DB unless defined?(INTEGRATION_DB)
7
7
 
8
- ORACLE_DB.create_table!(:items) do
9
- String :name, :size => 50
10
- Integer :value
11
- Date :date_created
12
- index :value
13
- end
8
+ describe "An Oracle database" do
9
+ before(:all) do
10
+ ORACLE_DB.create_table!(:items) do
11
+ String :name, :size => 50
12
+ Integer :value
13
+ Date :date_created
14
+ index :value
15
+ end
14
16
 
15
- ORACLE_DB.create_table!(:books) do
16
- Integer :id
17
- String :title, :size => 50
18
- Integer :category_id
19
- end
17
+ ORACLE_DB.create_table!(:books) do
18
+ Integer :id
19
+ String :title, :size => 50
20
+ Integer :category_id
21
+ end
20
22
 
21
- ORACLE_DB.create_table!(:categories) do
22
- Integer :id
23
- String :cat_name, :size => 50
24
- end
23
+ ORACLE_DB.create_table!(:categories) do
24
+ Integer :id
25
+ String :cat_name, :size => 50
26
+ end
27
+ @d = ORACLE_DB[:items]
28
+ end
29
+ after do
30
+ @d.delete
31
+ end
32
+ after(:all) do
33
+ ORACLE_DB.drop_table?(:items, :books, :categories)
34
+ end
25
35
 
26
- describe "An Oracle database" do
27
36
  specify "should provide disconnect functionality" do
28
37
  ORACLE_DB.execute("select user from dual")
29
38
  ORACLE_DB.pool.size.should == 1
@@ -81,15 +90,9 @@ describe "An Oracle database" do
81
90
  primary_key :id, :integer, :null => false
82
91
  index :name, :unique => true
83
92
  end
93
+ ORACLE_DB.drop_table?(:test_tmp)
84
94
  end
85
- end
86
95
 
87
- describe "An Oracle dataset" do
88
- before do
89
- @d = ORACLE_DB[:items]
90
- @d.delete # remove all records
91
- end
92
-
93
96
  specify "should return the correct record count" do
94
97
  @d.count.should == 0
95
98
  @d << {:name => 'abc', :value => 123}
@@ -194,8 +197,6 @@ describe "An Oracle dataset" do
194
197
  @d << {:name => 'def', :value => 789}
195
198
  @d.filter(:name => 'abc').update(:value => 530)
196
199
 
197
- # the third record should stay the same
198
- # floating-point precision bullshit
199
200
  @d[:name => 'def'][:value].should == 789
200
201
  @d.filter(:value => 530).count.should == 2
201
202
  end
@@ -230,24 +231,20 @@ describe "An Oracle dataset" do
230
231
 
231
232
  @d.count.should == 1
232
233
  end
233
- end
234
234
 
235
- describe "Joined Oracle dataset" do
236
- before do
235
+ specify "should return correct result" do
237
236
  @d1 = ORACLE_DB[:books]
238
- @d1.delete # remove all records
237
+ @d1.delete
239
238
  @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
240
239
  @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
241
240
  @d1 << {:id => 3, :title => 'ccc', :category_id => 101}
242
241
  @d1 << {:id => 4, :title => 'ddd', :category_id => 102}
243
242
 
244
243
  @d2 = ORACLE_DB[:categories]
245
- @d2.delete # remove all records
244
+ @d2.delete
246
245
  @d2 << {:id => 100, :cat_name => 'ruby'}
247
246
  @d2 << {:id => 101, :cat_name => 'rails'}
248
- end
249
247
 
250
- specify "should return correct result" do
251
248
  @d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
252
249
  {:id => 1, :title => 'aaa', :cat_name => 'ruby'},
253
250
  {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
@@ -271,18 +268,14 @@ describe "Joined Oracle dataset" do
271
268
  {:id => 3, :title => 'ccc', :cat_name => 'rails'}
272
269
  ]
273
270
  end
274
- end
275
271
 
276
- describe "Oracle aliasing" do
277
- before do
272
+ specify "should allow columns to be renamed" do
278
273
  @d1 = ORACLE_DB[:books]
279
- @d1.delete # remove all records
274
+ @d1.delete
280
275
  @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
281
276
  @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
282
277
  @d1 << {:id => 3, :title => 'bbb', :category_id => 100}
283
- end
284
278
 
285
- specify "should allow columns to be renamed" do
286
279
  @d1.select(Sequel.as(:title, :name)).order_by(:id).to_a.should == [
287
280
  { :name => 'aaa' },
288
281
  { :name => 'bbb' },
@@ -291,22 +284,14 @@ describe "Oracle aliasing" do
291
284
  end
292
285
 
293
286
  specify "nested queries should work" do
294
- @d1.select(:title).group_by(:title).count.should == 2
295
- end
296
- end
297
-
298
- describe "Row locks in Oracle" do
299
- before do
300
- @d1 = ORACLE_DB[:books]
301
- @d1.delete
302
- @d1 << {:id => 1, :title => 'aaa'}
287
+ ORACLE_DB[:books].select(:title).group_by(:title).count.should == 2
303
288
  end
304
289
 
305
290
  specify "#for_update should use FOR UPDATE" do
306
- @d1.for_update.sql.should == 'SELECT * FROM "BOOKS" FOR UPDATE'
291
+ ORACLE_DB[:books].for_update.sql.should == 'SELECT * FROM "BOOKS" FOR UPDATE'
307
292
  end
308
293
 
309
294
  specify "#lock_style should accept symbols" do
310
- @d1.lock_style(:update).sql.should == 'SELECT * FROM "BOOKS" FOR UPDATE'
295
+ ORACLE_DB[:books].lock_style(:update).sql.should == 'SELECT * FROM "BOOKS" FOR UPDATE'
311
296
  end
312
297
  end
@@ -24,26 +24,14 @@ end
24
24
  POSTGRES_DB.loggers << logger
25
25
 
26
26
  #POSTGRES_DB.instance_variable_set(:@server_version, 80200)
27
- POSTGRES_DB.create_table! :test do
28
- text :name
29
- integer :value, :index => true
30
- end
31
- POSTGRES_DB.create_table! :test2 do
32
- text :name
33
- integer :value
34
- end
35
- POSTGRES_DB.create_table! :test3 do
36
- integer :value
37
- timestamp :time
38
- end
39
- POSTGRES_DB.create_table! :test4 do
40
- varchar :name, :size => 20
41
- bytea :value
42
- end
43
27
 
44
28
  describe "A PostgreSQL database" do
45
- before do
29
+ before(:all) do
46
30
  @db = POSTGRES_DB
31
+ @db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
32
+ end
33
+ after(:all) do
34
+ @db.drop_table?(:public__testfk)
47
35
  end
48
36
 
49
37
  specify "should provide the server version" do
@@ -51,23 +39,13 @@ describe "A PostgreSQL database" do
51
39
  end
52
40
 
53
41
  specify "should correctly parse the schema" do
54
- @db.schema(:test3, :reload=>true).should == [
55
- [:value, {:oid=>23, :type=>:integer, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"integer", :primary_key=>false}],
56
- [:time, {:oid=>1114, :type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
57
- ]
58
- @db.schema(:test4, :reload=>true).should == [
59
- [:name, {:oid=>1043, :type=>:string, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
60
- [:value, {:oid=>17, :type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
61
- ]
42
+ @db.schema(:public__testfk, :reload=>true).should == [
43
+ [:id, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>"nextval('testfk_id_seq'::regclass)", :oid=>23, :primary_key=>true, :allow_null=>false}],
44
+ [:i, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>nil, :oid=>23, :primary_key=>false, :allow_null=>true}]]
62
45
  end
63
46
 
64
47
  specify "should parse foreign keys for tables in a schema" do
65
- begin
66
- @db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
67
- @db.foreign_key_list(:public__testfk).should == [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
68
- ensure
69
- @db.drop_table(:public__testfk)
70
- end
48
+ @db.foreign_key_list(:public__testfk).should == [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
71
49
  end
72
50
 
73
51
  specify "should return uuid fields as strings" do
@@ -76,10 +54,23 @@ describe "A PostgreSQL database" do
76
54
  end
77
55
 
78
56
  describe "A PostgreSQL dataset" do
57
+ before(:all) do
58
+ @db = POSTGRES_DB
59
+ @d = @db[:test]
60
+ @db.create_table! :test do
61
+ text :name
62
+ integer :value, :index => true
63
+ end
64
+ end
79
65
  before do
80
- @d = POSTGRES_DB[:test]
81
66
  @d.delete
82
- POSTGRES_DB.sqls.clear
67
+ @db.sqls.clear
68
+ end
69
+ after do
70
+ @db.drop_table?(:atest)
71
+ end
72
+ after(:all) do
73
+ @db.drop_table?(:test)
83
74
  end
84
75
 
85
76
  specify "should quote columns and tables using double quotes if quoting identifiers" do
@@ -130,68 +121,52 @@ describe "A PostgreSQL dataset" do
130
121
  end
131
122
 
132
123
  specify "should support exclusion constraints when creating or altering tables" do
133
- begin
134
- @db = POSTGRES_DB
135
- @db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
136
- @db[:atest].insert(1)
137
- @db[:atest].insert(2)
138
- proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
139
-
140
- @db.create_table!(:atest){Integer :t}
141
- @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
142
- @db[:atest].insert(1)
143
- @db[:atest].insert(2)
144
- proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
145
- @db.alter_table(:atest){drop_constraint 'atest_ex'}
146
- ensure
147
- @db.drop_table?(:atest)
148
- end
124
+ @db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
125
+ @db[:atest].insert(1)
126
+ @db[:atest].insert(2)
127
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
128
+
129
+ @db.create_table!(:atest){Integer :t}
130
+ @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
131
+ @db[:atest].insert(1)
132
+ @db[:atest].insert(2)
133
+ proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
134
+ @db.alter_table(:atest){drop_constraint 'atest_ex'}
149
135
  end if POSTGRES_DB.server_version >= 90000
150
136
 
151
137
  specify "should support adding foreign key constarints that are not yet valid, and validating them later" do
152
- begin
153
- @db = POSTGRES_DB
154
- @db.create_table!(:atest){primary_key :id; Integer :fk}
155
- @db[:atest].insert(1, 5)
156
- @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
157
- @db[:atest].insert(2, 1)
158
- proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
159
-
160
- proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
161
- @db[:atest].where(:id=>1).update(:fk=>2)
162
- @db.alter_table(:atest){validate_constraint :atest_fk}
163
- proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
164
- ensure
165
- @db.drop_table?(:atest)
166
- end
138
+ @db.create_table!(:atest){primary_key :id; Integer :fk}
139
+ @db[:atest].insert(1, 5)
140
+ @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
141
+ @db[:atest].insert(2, 1)
142
+ proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
143
+
144
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
145
+ @db[:atest].where(:id=>1).update(:fk=>2)
146
+ @db.alter_table(:atest){validate_constraint :atest_fk}
147
+ proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
167
148
  end if POSTGRES_DB.server_version >= 90200
168
149
 
169
150
  specify "should support :using when altering a column's type" do
170
- begin
171
- @db = POSTGRES_DB
172
- @db.create_table!(:atest){Integer :t}
173
- @db[:atest].insert(1262304000)
174
- @db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
175
- @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
176
- ensure
177
- @db.drop_table?(:atest)
178
- end
151
+ @db.create_table!(:atest){Integer :t}
152
+ @db[:atest].insert(1262304000)
153
+ @db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
154
+ @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
179
155
  end
180
156
 
181
157
  specify "should support :using with a string when altering a column's type" do
182
- begin
183
- @db = POSTGRES_DB
184
- @db.create_table!(:atest){Integer :t}
185
- @db[:atest].insert(1262304000)
186
- @db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
187
- @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
188
- ensure
189
- @db.drop_table?(:atest)
190
- end
158
+ @db.create_table!(:atest){Integer :t}
159
+ @db[:atest].insert(1262304000)
160
+ @db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
161
+ @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
162
+ end
163
+
164
+ specify "should be able to parse the default value for an interval type" do
165
+ @db.create_table!(:atest){interval :t, :default=>'1 week'}
166
+ @db.schema(:atest).first.last[:ruby_default].should == '7 days'
191
167
  end
192
168
 
193
169
  specify "should have #transaction support various types of synchronous options" do
194
- @db = POSTGRES_DB
195
170
  @db.transaction(:synchronous=>:on){}
196
171
  @db.transaction(:synchronous=>true){}
197
172
  @db.transaction(:synchronous=>:off){}
@@ -216,7 +191,6 @@ describe "A PostgreSQL dataset" do
216
191
  end
217
192
 
218
193
  specify "should have #transaction support read only transactions" do
219
- @db = POSTGRES_DB
220
194
  @db.transaction(:read_only=>true){}
221
195
  @db.transaction(:read_only=>false){}
222
196
  @db.transaction(:isolation=>:serializable, :read_only=>true){}
@@ -225,7 +199,6 @@ describe "A PostgreSQL dataset" do
225
199
  end
226
200
 
227
201
  specify "should have #transaction support deferrable transactions" do
228
- @db = POSTGRES_DB
229
202
  @db.transaction(:deferrable=>true){}
230
203
  @db.transaction(:deferrable=>false){}
231
204
  @db.transaction(:deferrable=>true, :read_only=>true){}
@@ -236,43 +209,42 @@ describe "A PostgreSQL dataset" do
236
209
  end if POSTGRES_DB.server_version >= 90100
237
210
 
238
211
  specify "should support creating indexes concurrently" do
239
- POSTGRES_DB.sqls.clear
240
- POSTGRES_DB.add_index :test, [:name, :value], :concurrently=>true
241
- POSTGRES_DB.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")'] if check_sqls
212
+ @db.add_index :test, [:name, :value], :concurrently=>true
213
+ @db.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")'] if check_sqls
242
214
  end
243
215
 
244
216
  specify "should support dropping indexes only if they already exist" do
245
- POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv1'
246
- POSTGRES_DB.sqls.clear
247
- POSTGRES_DB.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
248
- POSTGRES_DB.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
217
+ @db.add_index :test, [:name, :value], :name=>'tnv1'
218
+ @db.sqls.clear
219
+ @db.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
220
+ @db.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
249
221
  end
250
222
 
251
223
  specify "should support CASCADE when dropping indexes" do
252
- POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
253
- POSTGRES_DB.sqls.clear
254
- POSTGRES_DB.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
255
- POSTGRES_DB.sqls.should == ['DROP INDEX "tnv2" CASCADE']
224
+ @db.add_index :test, [:name, :value], :name=>'tnv2'
225
+ @db.sqls.clear
226
+ @db.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
227
+ @db.sqls.should == ['DROP INDEX "tnv2" CASCADE']
256
228
  end
257
229
 
258
230
  specify "should support dropping indexes concurrently" do
259
- POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
260
- POSTGRES_DB.sqls.clear
261
- POSTGRES_DB.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
262
- POSTGRES_DB.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
231
+ @db.add_index :test, [:name, :value], :name=>'tnv2'
232
+ @db.sqls.clear
233
+ @db.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
234
+ @db.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
263
235
  end if POSTGRES_DB.server_version >= 90200
264
236
 
265
237
  specify "#lock should lock table if inside a transaction" do
266
- POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
238
+ @db.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
267
239
  end
268
240
 
269
241
  specify "#lock should return nil" do
270
242
  @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
271
- POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
243
+ @db.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
272
244
  end
273
245
 
274
246
  specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
275
- proc{POSTGRES_DB[:test].join(:test2, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
247
+ proc{@db[:test].join(:test, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
276
248
  end
277
249
 
278
250
  specify "should truncate with options" do
@@ -288,9 +260,9 @@ describe "A PostgreSQL dataset" do
288
260
  end
289
261
 
290
262
  specify "should truncate multiple tables at once" do
291
- tables = [:test, :test2, :test3, :test4]
263
+ tables = [:test, :test]
292
264
  tables.each{|t| @d.from(t).insert}
293
- @d.from(:test, :test2, :test3, :test4).truncate
265
+ @d.from(:test, :test).truncate
294
266
  tables.each{|t| @d.from(t).count.should == 0}
295
267
  end
296
268
  end
@@ -377,14 +349,23 @@ if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
377
349
  end
378
350
 
379
351
  describe "A PostgreSQL dataset with a timestamp field" do
380
- before do
352
+ before(:all) do
381
353
  @db = POSTGRES_DB
354
+ @db.create_table! :test3 do
355
+ integer :value
356
+ timestamp :time
357
+ end
382
358
  @d = @db[:test3]
359
+ end
360
+ before do
383
361
  @d.delete
384
362
  end
385
363
  after do
386
364
  @db.convert_infinite_timestamps = false if @db.adapter_scheme == :postgres
387
365
  end
366
+ after(:all) do
367
+ @db.drop_table?(:test3)
368
+ end
388
369
 
389
370
  cspecify "should store milliseconds in time fields for Time objects", :do, :swift do
390
371
  t = Time.now
@@ -432,19 +413,29 @@ describe "A PostgreSQL dataset with a timestamp field" do
432
413
  c.new(:time=>-1.0/0.0).time.should == -1.0/0.0
433
414
  end
434
415
  end
435
- end
436
416
 
437
- describe "PostgreSQL's EXPLAIN and ANALYZE" do
438
- specify "should not raise errors" do
417
+ specify "explain and analyze should not raise errors" do
439
418
  @d = POSTGRES_DB[:test3]
440
419
  proc{@d.explain}.should_not raise_error
441
420
  proc{@d.analyze}.should_not raise_error
442
421
  end
422
+
423
+ specify "#locks should be a dataset returning database locks " do
424
+ @db.locks.should be_a_kind_of(Sequel::Dataset)
425
+ @db.locks.all.should be_a_kind_of(Array)
426
+ end
443
427
  end
444
428
 
445
429
  describe "A PostgreSQL database" do
446
430
  before do
447
431
  @db = POSTGRES_DB
432
+ @db.create_table! :test2 do
433
+ text :name
434
+ integer :value
435
+ end
436
+ end
437
+ after do
438
+ @db.drop_table?(:test2)
448
439
  end
449
440
 
450
441
  specify "should support column operations" do
@@ -478,11 +469,6 @@ describe "A PostgreSQL database" do
478
469
 
479
470
  @db[:test2].first[:xyz].should == 57
480
471
  end
481
-
482
- specify "#locks should be a dataset returning database locks " do
483
- @db.locks.should be_a_kind_of(Sequel::Dataset)
484
- @db.locks.all.should be_a_kind_of(Array)
485
- end
486
472
  end
487
473
 
488
474
  describe "A PostgreSQL database" do
@@ -679,9 +665,9 @@ describe "Postgres::Dataset#insert" do
679
665
  end
680
666
 
681
667
  specify "should return nil if the table has no primary key" do
682
- ds = POSTGRES_DB[:test4]
683
- ds.delete
684
- ds.insert(:name=>'a').should == nil
668
+ @db.create_table!(:test5){String :name; Integer :value}
669
+ @ds.delete
670
+ @ds.insert(:name=>'a').should == nil
685
671
  end
686
672
  end
687
673
 
@@ -763,7 +749,7 @@ describe "Postgres::Database schema qualified tables" do
763
749
 
764
750
  specify "should be able to get serial sequences for tables in a given schema" do
765
751
  POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
766
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
752
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
767
753
  end
768
754
 
769
755
  specify "should be able to get serial sequences for tables that have spaces in the name in a given schema" do
@@ -789,7 +775,7 @@ describe "Postgres::Database schema qualified tables" do
789
775
  POSTGRES_DB.table_exists?(:schema_test).should == true
790
776
  POSTGRES_DB.tables.should == [:schema_test]
791
777
  POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
792
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
778
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
793
779
  end
794
780
  end
795
781
 
@@ -972,20 +958,21 @@ describe "Postgres::Database schema qualified tables and eager graphing" do
972
958
  end
973
959
 
974
960
  if POSTGRES_DB.server_version >= 80300
975
-
976
- POSTGRES_DB.create_table! :test6 do
977
- text :title
978
- text :body
979
- full_text_index [:title, :body]
980
- end
981
-
982
961
  describe "PostgreSQL tsearch2" do
983
- before do
962
+ before(:all) do
963
+ POSTGRES_DB.create_table! :test6 do
964
+ text :title
965
+ text :body
966
+ full_text_index [:title, :body]
967
+ end
984
968
  @ds = POSTGRES_DB[:test6]
985
969
  end
986
970
  after do
987
971
  POSTGRES_DB[:test6].delete
988
972
  end
973
+ after(:all) do
974
+ POSTGRES_DB.drop_table?(:test6)
975
+ end
989
976
 
990
977
  specify "should search by indexed column" do
991
978
  record = {:title => "oopsla conference", :body => "test"}
@@ -1244,6 +1231,78 @@ if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRE
1244
1231
  end
1245
1232
  end
1246
1233
 
1234
+ describe "Postgres::Database#copy_table_from" do
1235
+ before(:all) do
1236
+ @db = POSTGRES_DB
1237
+ @db.create_table!(:test_copy){Integer :x; Integer :y}
1238
+ @ds = @db[:test_copy].order(:x, :y)
1239
+ end
1240
+ before do
1241
+ @db[:test_copy].delete
1242
+ end
1243
+ after(:all) do
1244
+ @db.drop_table?(:test_copy)
1245
+ end
1246
+
1247
+ specify "should work with a :data option containing data in PostgreSQL text format" do
1248
+ @db.copy_into(:test_copy, :data=>"1\t2\n3\t4\n")
1249
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1250
+ end
1251
+
1252
+ specify "should work with :format=>:csv option and :data option containing data in CSV format" do
1253
+ @db.copy_into(:test_copy, :format=>:csv, :data=>"1,2\n3,4\n")
1254
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1255
+ end
1256
+
1257
+ specify "should respect given :options" do
1258
+ @db.copy_into(:test_copy, :options=>"FORMAT csv, HEADER TRUE", :data=>"x,y\n1,2\n3,4\n")
1259
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1260
+ end
1261
+
1262
+ specify "should respect given :options options when :format is used" do
1263
+ @db.copy_into(:test_copy, :options=>"QUOTE '''', DELIMITER '|'", :format=>:csv, :data=>"'1'|'2'\n'3'|'4'\n")
1264
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1265
+ end
1266
+
1267
+ specify "should accept :columns option to online copy the given columns" do
1268
+ @db.copy_into(:test_copy, :data=>"1\t2\n3\t4\n", :columns=>[:y, :x])
1269
+ @ds.select_map([:x, :y]).should == [[2, 1], [4, 3]]
1270
+ end
1271
+
1272
+ specify "should accept a block and use returned values for the copy in data stream" do
1273
+ buf = ["1\t2\n", "3\t4\n"]
1274
+ @db.copy_into(:test_copy){buf.shift}
1275
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1276
+ end
1277
+
1278
+ specify "should work correctly with a block and :format=>:csv" do
1279
+ buf = ["1,2\n", "3,4\n"]
1280
+ @db.copy_into(:test_copy, :format=>:csv){buf.shift}
1281
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1282
+ end
1283
+
1284
+ specify "should accept an enumerable as the :data option" do
1285
+ @db.copy_into(:test_copy, :data=>["1\t2\n", "3\t4\n"])
1286
+ @ds.select_map([:x, :y]).should == [[1, 2], [3, 4]]
1287
+ end
1288
+
1289
+ specify "should have an exception should cause a rollback of copied data and still have a usable connection" do
1290
+ 2.times do
1291
+ sent = false
1292
+ proc{@db.copy_into(:test_copy){raise ArgumentError if sent; sent = true; "1\t2\n"}}.should raise_error(ArgumentError)
1293
+ @ds.select_map([:x, :y]).should == []
1294
+ end
1295
+ end
1296
+
1297
+ specify "should raise an Error if both :data and a block are provided" do
1298
+ proc{@db.copy_into(:test_copy, :data=>["1\t2\n", "3\t4\n"]){}}.should raise_error(Sequel::Error)
1299
+ end
1300
+
1301
+ specify "should raise an Error if neither :data or a block are provided" do
1302
+ proc{@db.copy_into(:test_copy)}.should raise_error(Sequel::Error)
1303
+ end
1304
+ end
1305
+
1247
1306
  describe "Postgres::Database LISTEN/NOTIFY" do
1248
1307
  before(:all) do
1249
1308
  @db = POSTGRES_DB
@@ -2363,6 +2422,7 @@ end if ((require 'active_support/duration'; require 'active_support/inflector';
2363
2422
  describe 'PostgreSQL row-valued/composite types' do
2364
2423
  before(:all) do
2365
2424
  @db = POSTGRES_DB
2425
+ Sequel.extension :pg_array_ops, :pg_row_ops
2366
2426
  @db.extension :pg_array, :pg_row
2367
2427
  @ds = @db[:person]
2368
2428
 
@@ -2450,7 +2510,6 @@ describe 'PostgreSQL row-valued/composite types' do
2450
2510
  end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
2451
2511
 
2452
2512
  specify 'operations/functions with pg_row_ops' do
2453
- Sequel.extension :pg_row_ops, :pg_array_ops
2454
2513
  @ds.insert(:id=>1, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))
2455
2514
  @ds.get(Sequel.pg_row(:address)[:street]).should == '123 Sesame St'
2456
2515
  @ds.get(Sequel.pg_row(:address)[:city]).should == 'Somewhere'
@@ -2470,6 +2529,45 @@ describe 'PostgreSQL row-valued/composite types' do
2470
2529
  @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:zip]).should == '12345'
2471
2530
  end
2472
2531
 
2532
+ context "#splat and #*" do
2533
+ before(:all) do
2534
+ @db.create_table!(:a){Integer :a}
2535
+ @db.create_table!(:b){a :b; Integer :a}
2536
+ @db.register_row_type(:a)
2537
+ @db.register_row_type(:b)
2538
+ @db[:b].insert(:a=>1, :b=>@db.row_type(:a, [2]))
2539
+ end
2540
+ after(:all) do
2541
+ @db.drop_table?(:b, :a)
2542
+ end
2543
+
2544
+ specify "splat should reference the table type" do
2545
+ @db[:b].select(:a).first.should == {:a=>1}
2546
+ @db[:b].select(:b__a).first.should == {:a=>1}
2547
+ @db[:b].select(Sequel.pg_row(:b)[:a]).first.should == {:a=>2}
2548
+ @db[:b].select(Sequel.pg_row(:b).splat[:a]).first.should == {:a=>1}
2549
+
2550
+ if @native
2551
+ @db[:b].select(:b).first.should == {:b=>{:a=>2}}
2552
+ @db[:b].select(Sequel.pg_row(:b).splat).first.should == {:a=>1, :b=>{:a=>2}}
2553
+ @db[:b].select(Sequel.pg_row(:b).splat(:b)).first.should == {:b=>{:a=>1, :b=>{:a=>2}}}
2554
+ end
2555
+ end
2556
+
2557
+ specify "* should expand the table type into separate columns" do
2558
+ ds = @db[:b].select(Sequel.pg_row(:b).splat(:b)).from_self(:alias=>:t)
2559
+ if @native
2560
+ ds.first.should == {:b=>{:a=>1, :b=>{:a=>2}}}
2561
+ ds.select(Sequel.pg_row(:b).*).first.should == {:a=>1, :b=>{:a=>2}}
2562
+ ds.select(Sequel.pg_row(:b)[:b]).first.should == {:b=>{:a=>2}}
2563
+ ds.select(Sequel.pg_row(:t__b).*).first.should == {:a=>1, :b=>{:a=>2}}
2564
+ ds.select(Sequel.pg_row(:t__b)[:b]).first.should == {:b=>{:a=>2}}
2565
+ end
2566
+ ds.select(Sequel.pg_row(:b)[:a]).first.should == {:a=>1}
2567
+ ds.select(Sequel.pg_row(:t__b)[:a]).first.should == {:a=>1}
2568
+ end
2569
+ end
2570
+
2473
2571
  context "with models" do
2474
2572
  before(:all) do
2475
2573
  class Address < Sequel::Model(:address)