sequel 2.2.0 → 2.3.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 (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