sequel 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,964 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ context "A new Database" do
4
+ setup do
5
+ @db = Sequel::Database.new(1 => 2, :logger => 3)
6
+ end
7
+ teardown do
8
+ Sequel.quote_identifiers = false
9
+ end
10
+
11
+ specify "should receive options" do
12
+ @db.opts.should == {1 => 2, :logger => 3}
13
+ end
14
+
15
+ specify "should set the logger from opts[:logger] and opts[:loggers]" do
16
+ @db.logger.should == 3
17
+ @db.loggers.should == [3]
18
+ Sequel::Database.new(1 => 2, :loggers => 3).logger.should == 3
19
+ Sequel::Database.new(1 => 2, :loggers => 3).loggers.should == [3]
20
+ Sequel::Database.new(1 => 2, :loggers => [3]).logger.should == 3
21
+ Sequel::Database.new(1 => 2, :loggers => [3]).loggers.should == [3]
22
+ Sequel::Database.new(1 => 2, :logger => 4, :loggers => 3).logger.should == 4
23
+ Sequel::Database.new(1 => 2, :logger => 4, :loggers => 3).loggers.should == [4,3]
24
+ Sequel::Database.new(1 => 2, :logger => [4], :loggers => [3]).logger.should == 4
25
+ Sequel::Database.new(1 => 2, :logger => [4], :loggers => [3]).loggers.should == [4,3]
26
+ end
27
+
28
+ specify "should create a connection pool" do
29
+ @db.pool.should be_a_kind_of(Sequel::ConnectionPool)
30
+ @db.pool.max_size.should == 4
31
+
32
+ Sequel::Database.new(:max_connections => 10).pool.max_size.should == 10
33
+ end
34
+
35
+ specify "should pass the supplied block to the connection pool" do
36
+ cc = nil
37
+ d = Sequel::Database.new {1234}
38
+ d.synchronize {|c| cc = c}
39
+ cc.should == 1234
40
+ end
41
+
42
+ specify "should respect the :quote_identifiers and :single_threaded options" do
43
+ db = Sequel::Database.new(:quote_identifiers=>false, :single_threaded=>true)
44
+ db.quote_identifiers?.should == false
45
+ db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
46
+ db = Sequel::Database.new(:quote_identifiers=>true, :single_threaded=>false)
47
+ db.quote_identifiers?.should == true
48
+ db.pool.should be_a_kind_of(Sequel::ConnectionPool)
49
+ end
50
+
51
+ specify "should use the default Sequel.quote_identifiers value" do
52
+ Sequel.quote_identifiers = true
53
+ Sequel::Database.new({}).quote_identifiers?.should == true
54
+ Sequel.quote_identifiers = false
55
+ Sequel::Database.new({}).quote_identifiers?.should == false
56
+ Sequel::Database.quote_identifiers = true
57
+ Sequel::Database.new({}).quote_identifiers?.should == true
58
+ Sequel::Database.quote_identifiers = false
59
+ Sequel::Database.new({}).quote_identifiers?.should == false
60
+ end
61
+
62
+ specify "should just use a :uri option for jdbc with the full connection string" do
63
+ Sequel::Database.should_receive(:adapter_class).once.with(:jdbc).and_return(Sequel::Database)
64
+ db = Sequel.connect('jdbc:test://host/db_name')
65
+ db.should be_a_kind_of(Sequel::Database)
66
+ db.opts[:uri].should == 'jdbc:test://host/db_name'
67
+ end
68
+ end
69
+
70
+ context "Database#connect" do
71
+ specify "should raise Sequel::Error::NotImplemented" do
72
+ proc {Sequel::Database.new.connect}.should raise_error(NotImplementedError)
73
+ end
74
+ end
75
+
76
+ context "Database#disconnect" do
77
+ specify "should raise Sequel::Error::NotImplemented" do
78
+ proc {Sequel::Database.new.disconnect}.should raise_error(NotImplementedError)
79
+ end
80
+ end
81
+
82
+ context "Database#uri" do
83
+ setup do
84
+ @c = Class.new(Sequel::Database) do
85
+ set_adapter_scheme :mau
86
+ end
87
+
88
+ @db = Sequel.connect('mau://user:pass@localhost:9876/maumau')
89
+ end
90
+
91
+ specify "should return the connection URI for the database" do
92
+ @db.uri.should == 'mau://user:pass@localhost:9876/maumau'
93
+ end
94
+ end
95
+
96
+ context "Database.adapter_scheme" do
97
+ specify "should return the database schema" do
98
+ Sequel::Database.adapter_scheme.should be_nil
99
+
100
+ @c = Class.new(Sequel::Database) do
101
+ set_adapter_scheme :mau
102
+ end
103
+
104
+ @c.adapter_scheme.should == :mau
105
+ end
106
+ end
107
+
108
+ context "Database#dataset" do
109
+ setup do
110
+ @db = Sequel::Database.new
111
+ @ds = @db.dataset
112
+ end
113
+
114
+ specify "should provide a blank dataset through #dataset" do
115
+ @ds.should be_a_kind_of(Sequel::Dataset)
116
+ @ds.opts.should == {}
117
+ @ds.db.should be(@db)
118
+ end
119
+
120
+ specify "should provide a #from dataset" do
121
+ d = @db.from(:mau)
122
+ d.should be_a_kind_of(Sequel::Dataset)
123
+ d.sql.should == 'SELECT * FROM mau'
124
+
125
+ e = @db[:miu]
126
+ e.should be_a_kind_of(Sequel::Dataset)
127
+ e.sql.should == 'SELECT * FROM miu'
128
+ end
129
+
130
+ specify "should provide a filtered #from dataset if a block is given" do
131
+ d = @db.from(:mau) {:x > 100}
132
+ d.should be_a_kind_of(Sequel::Dataset)
133
+ d.sql.should == 'SELECT * FROM mau WHERE (x > 100)'
134
+ end
135
+
136
+ specify "should provide a #select dataset" do
137
+ d = @db.select(:a, :b, :c).from(:mau)
138
+ d.should be_a_kind_of(Sequel::Dataset)
139
+ d.sql.should == 'SELECT a, b, c FROM mau'
140
+ end
141
+ end
142
+
143
+ context "Database#execute" do
144
+ specify "should raise Sequel::Error::NotImplemented" do
145
+ proc {Sequel::Database.new.execute('blah blah')}.should raise_error(NotImplementedError)
146
+ proc {Sequel::Database.new << 'blah blah'}.should raise_error(NotImplementedError)
147
+ end
148
+ end
149
+
150
+ context "Database#<<" do
151
+ setup do
152
+ @c = Class.new(Sequel::Database) do
153
+ define_method(:execute) {|sql| sql}
154
+ end
155
+ @db = @c.new({})
156
+ end
157
+
158
+ specify "should pass the supplied sql to #execute" do
159
+ (@db << "DELETE FROM items").should == "DELETE FROM items"
160
+ end
161
+
162
+ specify "should accept an array and convert it to SQL" do
163
+ a = %[
164
+ --
165
+ CREATE TABLE items (a integer, /*b integer*/
166
+ b text, c integer);
167
+ DROP TABLE old_items;
168
+ ].split($/)
169
+ (@db << a).should ==
170
+ "CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
171
+ end
172
+
173
+ specify "should remove comments and whitespace from arrays" do
174
+ s = %[
175
+ --
176
+ CREATE TABLE items (a integer, /*b integer*/
177
+ b text, c integer); \r\n
178
+ DROP TABLE old_items;
179
+ ].split($/)
180
+ (@db << s).should ==
181
+ "CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
182
+ end
183
+
184
+ specify "should not remove comments and whitespace from strings" do
185
+ s = "INSERT INTO items VALUES ('---abc')"
186
+ (@db << s).should == s
187
+ end
188
+ end
189
+
190
+ context "Database#synchronize" do
191
+ setup do
192
+ @db = Sequel::Database.new(:max_connections => 1)
193
+ @db.pool.connection_proc = proc {12345}
194
+ end
195
+
196
+ specify "should wrap the supplied block in pool.hold" do
197
+ stop = false
198
+ c1, c2 = nil
199
+ t1 = Thread.new {@db.synchronize {|c| c1 = c; while !stop;sleep 0.1;end}}
200
+ while !c1;end
201
+ c1.should == 12345
202
+ t2 = Thread.new {@db.synchronize {|c| c2 = c}}
203
+ sleep 0.2
204
+ @db.pool.available_connections.should be_empty
205
+ c2.should be_nil
206
+ stop = true
207
+ t1.join
208
+ sleep 0.1
209
+ c2.should == 12345
210
+ t2.join
211
+ end
212
+ end
213
+
214
+ context "Database#test_connection" do
215
+ setup do
216
+ @db = Sequel::Database.new
217
+ @test = nil
218
+ @db.pool.connection_proc = proc {@test = rand(100)}
219
+ end
220
+
221
+ specify "should call pool#hold" do
222
+ @db.test_connection
223
+ @test.should_not be_nil
224
+ end
225
+
226
+ specify "should return true if successful" do
227
+ @db.test_connection.should be_true
228
+ end
229
+ end
230
+
231
+ class DummyDataset < Sequel::Dataset
232
+ def first
233
+ raise if @opts[:from] == [:a]
234
+ true
235
+ end
236
+ end
237
+
238
+ class DummyDatabase < Sequel::Database
239
+ attr_reader :sqls
240
+
241
+ def execute(sql)
242
+ @sqls ||= []
243
+ @sqls << sql
244
+ end
245
+
246
+ def transaction; yield; end
247
+
248
+ def dataset
249
+ DummyDataset.new(self)
250
+ end
251
+ end
252
+
253
+ context "Database#create_table" do
254
+ setup do
255
+ @db = DummyDatabase.new
256
+ end
257
+
258
+ specify "should construct proper SQL" do
259
+ @db.create_table :test do
260
+ primary_key :id, :integer, :null => false
261
+ column :name, :text
262
+ index :name, :unique => true
263
+ end
264
+ @db.sqls.should == [
265
+ 'CREATE TABLE test (id integer NOT NULL PRIMARY KEY AUTOINCREMENT, name text)',
266
+ 'CREATE UNIQUE INDEX test_name_index ON test (name)'
267
+ ]
268
+ end
269
+ end
270
+
271
+ context "Database#alter_table" do
272
+ setup do
273
+ @db = DummyDatabase.new
274
+ end
275
+
276
+ specify "should construct proper SQL" do
277
+ @db.alter_table :xyz do
278
+ add_column :aaa, :text, :null => false, :unique => true
279
+ drop_column :bbb
280
+ rename_column :ccc, :ddd
281
+ set_column_type :eee, :integer
282
+ set_column_default :hhh, 'abcd'
283
+
284
+ add_index :fff, :unique => true
285
+ drop_index :ggg
286
+ end
287
+
288
+ @db.sqls.should == [
289
+ 'ALTER TABLE xyz ADD COLUMN aaa text UNIQUE NOT NULL',
290
+ 'ALTER TABLE xyz DROP COLUMN bbb',
291
+ 'ALTER TABLE xyz RENAME COLUMN ccc TO ddd',
292
+ 'ALTER TABLE xyz ALTER COLUMN eee TYPE integer',
293
+ "ALTER TABLE xyz ALTER COLUMN hhh SET DEFAULT 'abcd'",
294
+
295
+ 'CREATE UNIQUE INDEX xyz_fff_index ON xyz (fff)',
296
+ 'DROP INDEX xyz_ggg_index'
297
+ ]
298
+ end
299
+ end
300
+
301
+ context "Database#add_column" do
302
+ setup do
303
+ @db = DummyDatabase.new
304
+ end
305
+
306
+ specify "should construct proper SQL" do
307
+ @db.add_column :test, :name, :text, :unique => true
308
+ @db.sqls.should == [
309
+ 'ALTER TABLE test ADD COLUMN name text UNIQUE'
310
+ ]
311
+ end
312
+ end
313
+
314
+ context "Database#drop_column" do
315
+ setup do
316
+ @db = DummyDatabase.new
317
+ end
318
+
319
+ specify "should construct proper SQL" do
320
+ @db.drop_column :test, :name
321
+ @db.sqls.should == [
322
+ 'ALTER TABLE test DROP COLUMN name'
323
+ ]
324
+ end
325
+ end
326
+
327
+ context "Database#rename_column" do
328
+ setup do
329
+ @db = DummyDatabase.new
330
+ end
331
+
332
+ specify "should construct proper SQL" do
333
+ @db.rename_column :test, :abc, :def
334
+ @db.sqls.should == [
335
+ 'ALTER TABLE test RENAME COLUMN abc TO def'
336
+ ]
337
+ end
338
+ end
339
+
340
+ context "Database#set_column_type" do
341
+ setup do
342
+ @db = DummyDatabase.new
343
+ end
344
+
345
+ specify "should construct proper SQL" do
346
+ @db.set_column_type :test, :name, :integer
347
+ @db.sqls.should == [
348
+ 'ALTER TABLE test ALTER COLUMN name TYPE integer'
349
+ ]
350
+ end
351
+ end
352
+
353
+ context "Database#set_column_default" do
354
+ setup do
355
+ @db = DummyDatabase.new
356
+ end
357
+
358
+ specify "should construct proper SQL" do
359
+ @db.set_column_default :test, :name, 'zyx'
360
+ @db.sqls.should == [
361
+ "ALTER TABLE test ALTER COLUMN name SET DEFAULT 'zyx'"
362
+ ]
363
+ end
364
+ end
365
+
366
+ context "Database#add_index" do
367
+ setup do
368
+ @db = DummyDatabase.new
369
+ end
370
+
371
+ specify "should construct proper SQL" do
372
+ @db.add_index :test, :name, :unique => true
373
+ @db.sqls.should == [
374
+ 'CREATE UNIQUE INDEX test_name_index ON test (name)'
375
+ ]
376
+ end
377
+
378
+ specify "should accept multiple columns" do
379
+ @db.add_index :test, [:one, :two]
380
+ @db.sqls.should == [
381
+ 'CREATE INDEX test_one_two_index ON test (one, two)'
382
+ ]
383
+ end
384
+ end
385
+
386
+ context "Database#drop_index" do
387
+ setup do
388
+ @db = DummyDatabase.new
389
+ end
390
+
391
+ specify "should construct proper SQL" do
392
+ @db.drop_index :test, :name
393
+ @db.sqls.should == [
394
+ 'DROP INDEX test_name_index'
395
+ ]
396
+ end
397
+
398
+ end
399
+
400
+ class Dummy2Database < Sequel::Database
401
+ attr_reader :sql
402
+ def execute(sql); @sql = sql; end
403
+ def transaction; yield; end
404
+ end
405
+
406
+ context "Database#drop_table" do
407
+ setup do
408
+ @db = DummyDatabase.new
409
+ end
410
+
411
+ specify "should construct proper SQL" do
412
+ @db.drop_table :test
413
+ @db.sqls.should == ['DROP TABLE test']
414
+ end
415
+
416
+ specify "should accept multiple table names" do
417
+ @db.drop_table :a, :bb, :ccc
418
+ @db.sqls.should == [
419
+ 'DROP TABLE a',
420
+ 'DROP TABLE bb',
421
+ 'DROP TABLE ccc'
422
+ ]
423
+ end
424
+ end
425
+
426
+ context "Database#rename_table" do
427
+ setup do
428
+ @db = DummyDatabase.new
429
+ end
430
+
431
+ specify "should construct proper SQL" do
432
+ @db.rename_table :abc, :xyz
433
+ @db.sqls.should == ['ALTER TABLE abc RENAME TO xyz']
434
+ end
435
+ end
436
+
437
+ context "Database#table_exists?" do
438
+ setup do
439
+ @db = DummyDatabase.new
440
+ @db.stub!(:tables).and_return([:a, :b])
441
+ @db2 = DummyDatabase.new
442
+ end
443
+
444
+ specify "should use Database#tables if available" do
445
+ @db.table_exists?(:a).should be_true
446
+ @db.table_exists?(:b).should be_true
447
+ @db.table_exists?(:c).should be_false
448
+ end
449
+
450
+ specify "should otherwise try to select the first record from the table's dataset" do
451
+ @db2.table_exists?(:a).should be_false
452
+ @db2.table_exists?(:b).should be_true
453
+ end
454
+ end
455
+
456
+ class Dummy3Database < Sequel::Database
457
+ attr_reader :sql, :transactions
458
+ def execute(sql); @sql ||= []; @sql << sql; end
459
+
460
+ class DummyConnection
461
+ def initialize(db); @db = db; end
462
+ def execute(sql); @db.execute(sql); end
463
+ end
464
+ end
465
+
466
+ context "Database#transaction" do
467
+ setup do
468
+ @db = Dummy3Database.new
469
+ @db.pool.connection_proc = proc {Dummy3Database::DummyConnection.new(@db)}
470
+ end
471
+
472
+ specify "should wrap the supplied block with BEGIN + COMMIT statements" do
473
+ @db.transaction {@db.execute 'DROP TABLE test;'}
474
+ @db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
475
+ end
476
+
477
+ specify "should handle returning inside of the block by committing" do
478
+ def @db.ret_commit
479
+ transaction do
480
+ execute 'DROP TABLE test;'
481
+ return
482
+ execute 'DROP TABLE test2;';
483
+ end
484
+ end
485
+ @db.ret_commit
486
+ @db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
487
+ end
488
+
489
+ specify "should issue ROLLBACK if an exception is raised, and re-raise" do
490
+ @db.transaction {@db.execute 'DROP TABLE test'; raise RuntimeError} rescue nil
491
+ @db.sql.should == ['BEGIN', 'DROP TABLE test', 'ROLLBACK']
492
+
493
+ proc {@db.transaction {raise RuntimeError}}.should raise_error(RuntimeError)
494
+ end
495
+
496
+ specify "should issue ROLLBACK if Sequel::Error::Rollback is called in the transaction" do
497
+ @db.transaction do
498
+ @db.drop_table(:a)
499
+ raise Sequel::Error::Rollback
500
+ @db.drop_table(:b)
501
+ end
502
+
503
+ @db.sql.should == ['BEGIN', 'DROP TABLE a', 'ROLLBACK']
504
+ end
505
+
506
+ specify "should be re-entrant" do
507
+ stop = false
508
+ cc = nil
509
+ t = Thread.new do
510
+ @db.transaction {@db.transaction {@db.transaction {|c|
511
+ cc = c
512
+ while !stop; sleep 0.1; end
513
+ }}}
514
+ end
515
+ while cc.nil?; sleep 0.1; end
516
+ cc.should be_a_kind_of(Dummy3Database::DummyConnection)
517
+ @db.transactions.should == [t]
518
+ stop = true
519
+ t.join
520
+ @db.transactions.should be_empty
521
+ end
522
+ end
523
+
524
+ class Sequel::Database
525
+ def self.get_adapters; @@adapters; end
526
+ end
527
+
528
+ context "A Database adapter with a scheme" do
529
+ setup do
530
+ class CCC < Sequel::Database
531
+ if defined?(DISCONNECTS)
532
+ DISCONNECTS.clear
533
+ else
534
+ DISCONNECTS = []
535
+ end
536
+ set_adapter_scheme :ccc
537
+ def disconnect
538
+ DISCONNECTS << self
539
+ end
540
+ end
541
+ end
542
+
543
+ specify "should be registered in adapters" do
544
+ Sequel::Database.get_adapters[:ccc].should == CCC
545
+ end
546
+
547
+ specify "should be instantiated when its scheme is specified" do
548
+ c = Sequel::Database.connect('ccc://localhost/db')
549
+ c.should be_a_kind_of(CCC)
550
+ c.opts[:host].should == 'localhost'
551
+ c.opts[:database].should == 'db'
552
+ end
553
+
554
+ specify "should be accessible through Sequel.connect" do
555
+ c = Sequel.connect 'ccc://localhost/db'
556
+ c.should be_a_kind_of(CCC)
557
+ c.opts[:host].should == 'localhost'
558
+ c.opts[:database].should == 'db'
559
+ end
560
+
561
+ specify "should be accessible through Sequel.connect via a block" do
562
+ x = nil
563
+ y = nil
564
+ z = nil
565
+
566
+ p = proc do |c|
567
+ c.should be_a_kind_of(CCC)
568
+ c.opts[:host].should == 'localhost'
569
+ c.opts[:database].should == 'db'
570
+ z = y
571
+ y = x
572
+ x = c
573
+ end
574
+ Sequel::Database.connect('ccc://localhost/db', &p).should == nil
575
+ CCC::DISCONNECTS.should == [x]
576
+
577
+ Sequel.connect('ccc://localhost/db', &p).should == nil
578
+ CCC::DISCONNECTS.should == [y, x]
579
+
580
+ Sequel.send(:def_adapter_method, :ccc)
581
+ Sequel.ccc('db', :host=>'localhost', &p).should == nil
582
+ CCC::DISCONNECTS.should == [z, y, x]
583
+ end
584
+
585
+ specify "should be accessible through Sequel.open" do
586
+ c = Sequel.open 'ccc://localhost/db'
587
+ c.should be_a_kind_of(CCC)
588
+ c.opts[:host].should == 'localhost'
589
+ c.opts[:database].should == 'db'
590
+ end
591
+
592
+ specify "should be accessible through Sequel.<adapter>" do
593
+ Sequel.send(:def_adapter_method, :ccc)
594
+
595
+ # invalid parameters
596
+ proc {Sequel.ccc('abc', 'def')}.should raise_error(Sequel::Error)
597
+
598
+ c = Sequel.ccc('mydb')
599
+ c.should be_a_kind_of(CCC)
600
+ c.opts.should == {:adapter=>:ccc, :database => 'mydb'}
601
+
602
+ c = Sequel.ccc('mydb', :host => 'localhost')
603
+ c.should be_a_kind_of(CCC)
604
+ c.opts.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost'}
605
+
606
+ c = Sequel.ccc
607
+ c.should be_a_kind_of(CCC)
608
+ c.opts.should == {:adapter=>:ccc}
609
+
610
+ c = Sequel.ccc(:database => 'mydb', :host => 'localhost')
611
+ c.should be_a_kind_of(CCC)
612
+ c.opts.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost'}
613
+ end
614
+
615
+ specify "should be accessible through Sequel.connect with options" do
616
+ c = Sequel.connect(:adapter => :ccc, :database => 'mydb')
617
+ c.should be_a_kind_of(CCC)
618
+ c.opts.should == {:adapter => :ccc, :database => 'mydb'}
619
+ end
620
+
621
+ specify "should be accessible through Sequel.connect with URL parameters" do
622
+ c = Sequel.connect 'ccc://localhost/db?host=/tmp&user=test'
623
+ c.should be_a_kind_of(CCC)
624
+ c.opts[:host].should == '/tmp'
625
+ c.opts[:database].should == 'db'
626
+ c.opts[:user].should == 'test'
627
+ end
628
+
629
+ end
630
+
631
+ context "An unknown database scheme" do
632
+ specify "should raise an error in Sequel::Database.connect" do
633
+ proc {Sequel::Database.connect('ddd://localhost/db')}.should raise_error(Sequel::Error::AdapterNotFound)
634
+ end
635
+
636
+ specify "should raise an error in Sequel.connect" do
637
+ proc {Sequel.connect('ddd://localhost/db')}.should raise_error(Sequel::Error::AdapterNotFound)
638
+ end
639
+
640
+ specify "should raise an error in Sequel.open" do
641
+ proc {Sequel.open('ddd://localhost/db')}.should raise_error(Sequel::Error::AdapterNotFound)
642
+ end
643
+ end
644
+
645
+ context "A broken adapter (lib is there but the class is not)" do
646
+ setup do
647
+ @fn = File.join(File.dirname(__FILE__), '../../lib/sequel_core/adapters/blah.rb')
648
+ File.open(@fn,'a'){}
649
+ end
650
+
651
+ teardown do
652
+ File.delete(@fn)
653
+ end
654
+
655
+ specify "should raise an error" do
656
+ proc {Sequel.connect('blah://blow')}.should raise_error(Sequel::Error::AdapterNotFound)
657
+ end
658
+ end
659
+
660
+ context "A single threaded database" do
661
+ teardown do
662
+ Sequel::Database.single_threaded = false
663
+ end
664
+
665
+ specify "should use a SingleThreadedPool instead of a ConnectionPool" do
666
+ db = Sequel::Database.new(:single_threaded => true)
667
+ db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
668
+ end
669
+
670
+ specify "should be constructable using :single_threaded => true option" do
671
+ db = Sequel::Database.new(:single_threaded => true)
672
+ db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
673
+ end
674
+
675
+ specify "should be constructable using Database.single_threaded = true" do
676
+ Sequel::Database.single_threaded = true
677
+ db = Sequel::Database.new
678
+ db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
679
+ end
680
+
681
+ specify "should be constructable using Sequel.single_threaded = true" do
682
+ Sequel.single_threaded = true
683
+ db = Sequel::Database.new
684
+ db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
685
+ end
686
+ end
687
+
688
+ context "A single threaded database" do
689
+ setup do
690
+ conn = 1234567
691
+ @db = Sequel::Database.new(:single_threaded => true) do
692
+ conn += 1
693
+ end
694
+ end
695
+
696
+ specify "should invoke connection_proc only once" do
697
+ @db.pool.hold {|c| c.should == 1234568}
698
+ @db.pool.hold {|c| c.should == 1234568}
699
+ end
700
+
701
+ specify "should convert an Exception into a RuntimeError" do
702
+ db = Sequel::Database.new(:single_threaded => true) do
703
+ raise Exception
704
+ end
705
+
706
+ proc {db.pool.hold {|c|}}.should raise_error(RuntimeError)
707
+ end
708
+ end
709
+
710
+ context "A database" do
711
+ setup do
712
+ Sequel::Database.single_threaded = false
713
+ end
714
+
715
+ teardown do
716
+ Sequel::Database.single_threaded = false
717
+ end
718
+
719
+ specify "should be either single_threaded? or multi_threaded?" do
720
+ db = Sequel::Database.new(:single_threaded => true)
721
+ db.should be_single_threaded
722
+ db.should_not be_multi_threaded
723
+
724
+ db = Sequel::Database.new(:max_options => 1)
725
+ db.should_not be_single_threaded
726
+ db.should be_multi_threaded
727
+
728
+ db = Sequel::Database.new
729
+ db.should_not be_single_threaded
730
+ db.should be_multi_threaded
731
+
732
+ Sequel::Database.single_threaded = true
733
+
734
+ db = Sequel::Database.new
735
+ db.should be_single_threaded
736
+ db.should_not be_multi_threaded
737
+
738
+ db = Sequel::Database.new(:max_options => 4)
739
+ db.should be_single_threaded
740
+ db.should_not be_multi_threaded
741
+ end
742
+
743
+ specify "should accept a logger object" do
744
+ db = Sequel::Database.new
745
+ s = "I'm a logger"
746
+ db.logger = s
747
+ db.logger.should == s
748
+ db.loggers.should == [s]
749
+ db.logger = nil
750
+ db.logger.should == nil
751
+ db.loggers.should == []
752
+
753
+ db.loggers = [s]
754
+ db.logger.should == s
755
+ db.loggers.should == [s]
756
+ db.loggers = []
757
+ db.logger.should == nil
758
+ db.loggers.should == []
759
+
760
+ t = "I'm also a logger"
761
+ db.loggers = [s, t]
762
+ db.logger.should == s
763
+ db.loggers.should == [s,t]
764
+ db.loggers = []
765
+ db.logger.should == nil
766
+ db.loggers.should == []
767
+ end
768
+ end
769
+
770
+ context "Database#dataset" do
771
+ setup do
772
+ @db = Sequel::Database.new
773
+ end
774
+
775
+ specify "should delegate to Dataset#query if block is provided" do
776
+ @d = @db.query {select :x; from :y}
777
+ @d.should be_a_kind_of(Sequel::Dataset)
778
+ @d.sql.should == "SELECT x FROM y"
779
+ end
780
+ end
781
+
782
+ context "Database#fetch" do
783
+ setup do
784
+ @db = Sequel::Database.new
785
+ c = Class.new(Sequel::Dataset) do
786
+ def fetch_rows(sql); yield({:sql => sql}); end
787
+ end
788
+ @db.meta_def(:dataset) {c.new(self)}
789
+ end
790
+
791
+ specify "should create a dataset and invoke its fetch_rows method with the given sql" do
792
+ sql = nil
793
+ @db.fetch('select * from xyz') {|r| sql = r[:sql]}
794
+ sql.should == 'select * from xyz'
795
+ end
796
+
797
+ specify "should format the given sql with any additional arguments" do
798
+ sql = nil
799
+ @db.fetch('select * from xyz where x = ? and y = ?', 15, 'abc') {|r| sql = r[:sql]}
800
+ sql.should == "select * from xyz where x = 15 and y = 'abc'"
801
+
802
+ # and Aman Gupta's example
803
+ @db.fetch('select name from table where name = ? or id in ?',
804
+ 'aman', [3,4,7]) {|r| sql = r[:sql]}
805
+ sql.should == "select name from table where name = 'aman' or id in (3, 4, 7)"
806
+ end
807
+
808
+ specify "should return the dataset if no block is given" do
809
+ @db.fetch('select * from xyz').should be_a_kind_of(Sequel::Dataset)
810
+
811
+ @db.fetch('select a from b').map {|r| r[:sql]}.should == ['select a from b']
812
+
813
+ @db.fetch('select c from d').inject([]) {|m, r| m << r; m}.should == \
814
+ [{:sql => 'select c from d'}]
815
+ end
816
+
817
+ specify "should return a dataset that always uses the given sql for SELECTs" do
818
+ ds = @db.fetch('select * from xyz')
819
+ ds.select_sql.should == 'select * from xyz'
820
+ ds.sql.should == 'select * from xyz'
821
+
822
+ ds.filter!(:price < 100)
823
+ ds.select_sql.should == 'select * from xyz'
824
+ ds.sql.should == 'select * from xyz'
825
+ end
826
+ end
827
+
828
+
829
+ context "Database#[]" do
830
+ setup do
831
+ @db = Sequel::Database.new
832
+ end
833
+
834
+ specify "should return a dataset when symbols are given" do
835
+ ds = @db[:items]
836
+ ds.class.should == Sequel::Dataset
837
+ ds.opts[:from].should == [:items]
838
+ end
839
+
840
+ specify "should return an enumerator when a string is given" do
841
+ c = Class.new(Sequel::Dataset) do
842
+ def fetch_rows(sql); yield({:sql => sql}); end
843
+ end
844
+ @db.meta_def(:dataset) {c.new(self)}
845
+
846
+ sql = nil
847
+ @db['select * from xyz where x = ? and y = ?', 15, 'abc'].each {|r| sql = r[:sql]}
848
+ sql.should == "select * from xyz where x = 15 and y = 'abc'"
849
+ end
850
+ end
851
+
852
+ context "Database#create_view" do
853
+ setup do
854
+ @db = DummyDatabase.new
855
+ end
856
+
857
+ specify "should construct proper SQL with raw SQL" do
858
+ @db.create_view :test, "SELECT * FROM xyz"
859
+ @db.sqls.should == ['CREATE VIEW test AS SELECT * FROM xyz']
860
+ end
861
+
862
+ specify "should construct proper SQL with dataset" do
863
+ @db.create_view :test, @db[:items].select(:a, :b).order(:c)
864
+ @db.sqls.should == ['CREATE VIEW test AS SELECT a, b FROM items ORDER BY c']
865
+ end
866
+ end
867
+
868
+ context "Database#create_or_replace_view" do
869
+ setup do
870
+ @db = DummyDatabase.new
871
+ end
872
+
873
+ specify "should construct proper SQL with raw SQL" do
874
+ @db.create_or_replace_view :test, "SELECT * FROM xyz"
875
+ @db.sqls.should == ['CREATE OR REPLACE VIEW test AS SELECT * FROM xyz']
876
+ end
877
+
878
+ specify "should construct proper SQL with dataset" do
879
+ @db.create_or_replace_view :test, @db[:items].select(:a, :b).order(:c)
880
+ @db.sqls.should == ['CREATE OR REPLACE VIEW test AS SELECT a, b FROM items ORDER BY c']
881
+ end
882
+ end
883
+
884
+ context "Database#drop_view" do
885
+ setup do
886
+ @db = DummyDatabase.new
887
+ end
888
+
889
+ specify "should construct proper SQL" do
890
+ @db.drop_view :test
891
+ @db.sqls.should == ['DROP VIEW test']
892
+ end
893
+ end
894
+
895
+ # TODO: beaf this up with specs for all supported ops
896
+ context "Database#alter_table_sql" do
897
+ setup do
898
+ @db = DummyDatabase.new
899
+ end
900
+
901
+ specify "should raise error for an invalid op" do
902
+ proc {@db.alter_table_sql(:mau, :op => :blah)}.should raise_error(Sequel::Error)
903
+ end
904
+ end
905
+
906
+ context "Database.connect" do
907
+ EEE_YAML = "development:\r\n adapter: eee\r\n username: mau\r\n password: tau\r\n host: alfonso\r\n database: mydb\r\n"
908
+
909
+ setup do
910
+ class EEE < Sequel::Database
911
+ set_adapter_scheme :eee
912
+ end
913
+
914
+ @fn = File.join(File.dirname(__FILE__), 'eee.yaml')
915
+ File.open(@fn, 'wb') {|f| f << EEE_YAML}
916
+ end
917
+
918
+ teardown do
919
+ File.delete(@fn)
920
+ end
921
+
922
+ specify "should accept hashes loaded from YAML files" do
923
+ db = Sequel.connect(YAML.load_file(@fn)['development'])
924
+ db.class.should == EEE
925
+ db.opts[:database].should == 'mydb'
926
+ db.opts[:user].should == 'mau'
927
+ db.opts[:password].should == 'tau'
928
+ db.opts[:host].should == 'alfonso'
929
+ end
930
+ end
931
+
932
+ context "Database#inspect" do
933
+ setup do
934
+ @db = DummyDatabase.new
935
+
936
+ @db.meta_def(:uri) {'blah://blahblah/blah'}
937
+ end
938
+
939
+ specify "should include the class name and the connection url" do
940
+ @db.inspect.should == '#<DummyDatabase: "blah://blahblah/blah">'
941
+ end
942
+ end
943
+
944
+ context "Database#get" do
945
+ setup do
946
+ @c = Class.new(DummyDatabase) do
947
+ def dataset
948
+ ds = super
949
+ ds.meta_def(:get) {|c| @db.execute select(c).sql; c}
950
+ ds
951
+ end
952
+ end
953
+
954
+ @db = @c.new
955
+ end
956
+
957
+ specify "should use Dataset#get to get a single value" do
958
+ @db.get(1).should == 1
959
+ @db.sqls.last.should == 'SELECT 1'
960
+
961
+ @db.get(:version[])
962
+ @db.sqls.last.should == 'SELECT version()'
963
+ end
964
+ end