sequel 3.38.0 → 3.39.0

Sign up to get free protection for your applications and to get access to all the features.
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)