sequel 3.23.0 → 3.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- metadata +21 -4
@@ -84,7 +84,9 @@ describe "A connection pool handling connections" do
|
|
84
84
|
end
|
85
85
|
|
86
86
|
specify "#make_new should not make more than max_size connections" do
|
87
|
-
|
87
|
+
q = Queue.new
|
88
|
+
50.times{Thread.new{@cpool.hold{q.pop}}}
|
89
|
+
50.times{q.push nil}
|
88
90
|
@cpool.created_count.should <= @max_size
|
89
91
|
end
|
90
92
|
|
@@ -96,7 +98,8 @@ describe "A connection pool handling connections" do
|
|
96
98
|
|
97
99
|
specify "#hold should remove the connection if a DatabaseDisconnectError is raised" do
|
98
100
|
@cpool.created_count.should == 0
|
99
|
-
|
101
|
+
q, q1 = Queue.new, Queue.new
|
102
|
+
@cpool.hold{Thread.new{@cpool.hold{q1.pop; q.push nil}; q1.pop; q.push nil}; q1.push nil; q.pop; q1.push nil; q.pop}
|
100
103
|
@cpool.created_count.should == 2
|
101
104
|
proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
|
102
105
|
@cpool.created_count.should == 1
|
@@ -166,15 +169,14 @@ describe "A connection pool with a max size of 1" do
|
|
166
169
|
|
167
170
|
specify "should let only one thread access the connection at any time" do
|
168
171
|
cc,c1, c2 = nil
|
169
|
-
|
172
|
+
q, q1 = Queue.new, Queue.new
|
170
173
|
|
171
|
-
t1 = Thread.new {@pool.hold {|c| cc = c; c1 = c.dup;
|
172
|
-
|
174
|
+
t1 = Thread.new {@pool.hold {|c| cc = c; c1 = c.dup; q1.push nil; q.pop}}
|
175
|
+
q1.pop
|
173
176
|
cc.should == 'herro'
|
174
177
|
c1.should == 'herro'
|
175
178
|
|
176
|
-
t2 = Thread.new {@pool.hold {|c| c2 = c.dup;
|
177
|
-
sleep 0.02 * m
|
179
|
+
t2 = Thread.new {@pool.hold {|c| c2 = c.dup; q1.push nil; q.pop;}}
|
178
180
|
|
179
181
|
# connection held by t1
|
180
182
|
t1.should be_alive
|
@@ -186,26 +188,22 @@ describe "A connection pool with a max size of 1" do
|
|
186
188
|
|
187
189
|
@pool.available_connections.should be_empty
|
188
190
|
@pool.allocated.should == {t1=>cc}
|
189
|
-
|
191
|
+
|
190
192
|
cc.gsub!('rr', 'll')
|
191
|
-
|
192
|
-
|
193
|
-
|
193
|
+
q.push nil
|
194
|
+
q1.pop
|
195
|
+
|
194
196
|
t1.should_not be_alive
|
195
197
|
t2.should be_alive
|
196
|
-
|
198
|
+
|
197
199
|
c2.should == 'hello'
|
198
200
|
|
199
201
|
@pool.available_connections.should be_empty
|
200
202
|
@pool.allocated.should == {t2=>cc}
|
201
203
|
|
202
|
-
cc.gsub!('ll', 'rr')
|
203
|
-
sleep 0.05 * m
|
204
|
-
|
205
204
|
#connection released
|
206
|
-
|
207
|
-
|
208
|
-
cc.should == 'herro'
|
205
|
+
q.push nil
|
206
|
+
t2.join
|
209
207
|
|
210
208
|
@invoked_count.should == 1
|
211
209
|
@pool.size.should == 1
|
@@ -236,34 +234,36 @@ describe "A connection pool with a max size of 1" do
|
|
236
234
|
end
|
237
235
|
|
238
236
|
shared_examples_for "A threaded connection pool" do
|
237
|
+
specify "should raise a PoolTimeout error if a connection couldn't be acquired before timeout" do
|
238
|
+
x = nil
|
239
|
+
q, q1 = Queue.new, Queue.new
|
240
|
+
pool = Sequel::ConnectionPool.get_pool(@cp_opts.merge(:max_connections=>1, :pool_timeout=>0)) {@invoked_count += 1}
|
241
|
+
t = Thread.new{pool.hold{|c| q1.push nil; q.pop}}
|
242
|
+
q1.pop
|
243
|
+
proc{pool.hold{|c|}}.should raise_error(Sequel::PoolTimeout)
|
244
|
+
q.push nil
|
245
|
+
t.join
|
246
|
+
end
|
247
|
+
|
239
248
|
specify "should let five threads simultaneously access separate connections" do
|
240
249
|
cc = {}
|
241
250
|
threads = []
|
242
|
-
|
251
|
+
q, q1, q2 = Queue.new, Queue.new, Queue.new
|
243
252
|
|
244
|
-
5.times
|
245
|
-
sleep 0.04
|
253
|
+
5.times{|i| threads << Thread.new{@pool.hold{|c| q.pop; cc[i] = c; q1.push nil; q2.pop}}; q.push nil; q1.pop}
|
246
254
|
threads.each {|t| t.should be_alive}
|
247
255
|
cc.size.should == 5
|
248
256
|
@invoked_count.should == 5
|
249
257
|
@pool.size.should == 5
|
250
258
|
@pool.available_connections.should be_empty
|
251
|
-
|
259
|
+
|
252
260
|
h = {}
|
261
|
+
i = 0
|
253
262
|
threads.each{|t| h[t] = (i+=1)}
|
254
263
|
@pool.allocated.should == h
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
threads[3].raise "your'e dead too"
|
259
|
-
|
260
|
-
sleep 0.02
|
261
|
-
|
262
|
-
@pool.available_connections.should == [1, 4]
|
263
|
-
@pool.allocated.should == {threads[1]=>2, threads[2]=>3, threads[4]=>5}
|
264
|
-
|
265
|
-
stop = true
|
266
|
-
sleep 0.04
|
264
|
+
@pool.available_connections.should == []
|
265
|
+
5.times{q2.push nil}
|
266
|
+
threads.each{|t| t.join}
|
267
267
|
|
268
268
|
@pool.available_connections.size.should == 5
|
269
269
|
@pool.allocated.should be_empty
|
@@ -272,16 +272,15 @@ shared_examples_for "A threaded connection pool" do
|
|
272
272
|
specify "should block threads until a connection becomes available" do
|
273
273
|
cc = {}
|
274
274
|
threads = []
|
275
|
-
|
275
|
+
q, q1 = Queue.new, Queue.new
|
276
276
|
|
277
|
-
5.times
|
278
|
-
|
277
|
+
5.times{|i| threads << Thread.new{@pool.hold{|c| cc[i] = c; q1.push nil; q.pop}}}
|
278
|
+
5.times{q1.pop}
|
279
279
|
threads.each {|t| t.should be_alive}
|
280
280
|
@pool.available_connections.should be_empty
|
281
281
|
|
282
|
-
3.times {|i| threads << Thread.new {@pool.hold {|c| cc[i + 5] = c}}}
|
282
|
+
3.times {|i| threads << Thread.new {@pool.hold {|c| cc[i + 5] = c; q1.push nil}}}
|
283
283
|
|
284
|
-
sleep 0.02
|
285
284
|
threads[5].should be_alive
|
286
285
|
threads[6].should be_alive
|
287
286
|
threads[7].should be_alive
|
@@ -290,8 +289,10 @@ shared_examples_for "A threaded connection pool" do
|
|
290
289
|
cc[6].should be_nil
|
291
290
|
cc[7].should be_nil
|
292
291
|
|
293
|
-
|
294
|
-
|
292
|
+
5.times{q.push nil}
|
293
|
+
5.times{|i| threads[i].join}
|
294
|
+
3.times{q1.pop}
|
295
|
+
3.times{|i| threads[i+5].join}
|
295
296
|
|
296
297
|
threads.each {|t| t.should_not be_alive}
|
297
298
|
|
@@ -305,7 +306,8 @@ end
|
|
305
306
|
describe "Threaded Unsharded Connection Pool" do
|
306
307
|
before do
|
307
308
|
@invoked_count = 0
|
308
|
-
@
|
309
|
+
@cp_opts = CONNECTION_POOL_DEFAULTS.merge(:max_connections=>5)
|
310
|
+
@pool = Sequel::ConnectionPool.get_pool(@cp_opts) {@invoked_count += 1}
|
309
311
|
end
|
310
312
|
|
311
313
|
it_should_behave_like "A threaded connection pool"
|
@@ -314,7 +316,8 @@ end
|
|
314
316
|
describe "Threaded Sharded Connection Pool" do
|
315
317
|
before do
|
316
318
|
@invoked_count = 0
|
317
|
-
@
|
319
|
+
@cp_opts = CONNECTION_POOL_DEFAULTS.merge(:max_connections=>5, :servers=>{})
|
320
|
+
@pool = Sequel::ConnectionPool.get_pool(@cp_opts) {@invoked_count += 1}
|
318
321
|
end
|
319
322
|
|
320
323
|
it_should_behave_like "A threaded connection pool"
|
@@ -324,19 +327,15 @@ describe "ConnectionPool#disconnect" do
|
|
324
327
|
before do
|
325
328
|
@count = 0
|
326
329
|
@pool = Sequel::ConnectionPool.get_pool(CONNECTION_POOL_DEFAULTS.merge(:max_connections=>5, :servers=>{})) {{:id => @count += 1}}
|
330
|
+
threads = []
|
331
|
+
q, q1 = Queue.new, Queue.new
|
332
|
+
5.times {|i| threads << Thread.new {@pool.hold {|c| q1.push nil; q.pop}}}
|
333
|
+
5.times{q1.pop}
|
334
|
+
5.times{q.push nil}
|
335
|
+
threads.each {|t| t.join}
|
327
336
|
end
|
328
337
|
|
329
338
|
specify "should invoke the given block for each available connection" do
|
330
|
-
threads = []
|
331
|
-
stop = nil
|
332
|
-
5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.01;end}}; sleep 0.01}
|
333
|
-
while @pool.size < 5
|
334
|
-
sleep 0.02
|
335
|
-
end
|
336
|
-
stop = true
|
337
|
-
sleep 0.1
|
338
|
-
threads.each {|t| t.join}
|
339
|
-
|
340
339
|
@pool.size.should == 5
|
341
340
|
@pool.available_connections.size.should == 5
|
342
341
|
@pool.available_connections.each {|c| c[:id].should_not be_nil}
|
@@ -346,34 +345,13 @@ describe "ConnectionPool#disconnect" do
|
|
346
345
|
end
|
347
346
|
|
348
347
|
specify "should remove all available connections" do
|
349
|
-
threads = []
|
350
|
-
stop = nil
|
351
|
-
5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.01;end}}; sleep 0.01}
|
352
|
-
while @pool.size < 5
|
353
|
-
sleep 0.02
|
354
|
-
end
|
355
|
-
stop = true
|
356
|
-
sleep 0.1
|
357
|
-
threads.each {|t| t.join}
|
358
|
-
|
359
348
|
@pool.size.should == 5
|
360
349
|
@pool.disconnect
|
361
350
|
@pool.size.should == 0
|
362
351
|
end
|
363
352
|
|
364
353
|
specify "should disconnect connections in use as soon as they are no longer in use" do
|
365
|
-
threads = []
|
366
|
-
stop = nil
|
367
|
-
5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.01;end}}; sleep 0.01}
|
368
|
-
while @pool.size < 5
|
369
|
-
sleep 0.02
|
370
|
-
end
|
371
|
-
stop = true
|
372
|
-
sleep 0.1
|
373
|
-
threads.each {|t| t.join}
|
374
|
-
|
375
354
|
@pool.size.should == 5
|
376
|
-
|
377
355
|
@pool.hold do |conn|
|
378
356
|
@pool.available_connections.size.should == 4
|
379
357
|
@pool.available_connections.each {|c| c.should_not be(conn)}
|
@@ -556,9 +534,10 @@ describe "A connection pool with multiple servers" do
|
|
556
534
|
specify "#remove_servers should disconnect available connections immediately" do
|
557
535
|
pool = Sequel::ConnectionPool.get_pool(:max_connections=>5, :servers=>{:server1=>{}}){|s| s}
|
558
536
|
threads = []
|
559
|
-
|
560
|
-
5.times {|i| threads << Thread.new{pool.hold(:server1){|c|
|
561
|
-
|
537
|
+
q, q1 = Queue.new, Queue.new
|
538
|
+
5.times {|i| threads << Thread.new {pool.hold(:server1){|c| q1.push nil; q.pop}}}
|
539
|
+
5.times{q1.pop}
|
540
|
+
5.times{q.push nil}
|
562
541
|
threads.each {|t| t.join}
|
563
542
|
|
564
543
|
pool.size(:server1).should == 5
|
data/spec/core/database_spec.rb
CHANGED
@@ -397,6 +397,12 @@ describe "Database#tables" do
|
|
397
397
|
end
|
398
398
|
end
|
399
399
|
|
400
|
+
describe "Database#views" do
|
401
|
+
specify "should raise Sequel::NotImplemented" do
|
402
|
+
proc {Sequel::Database.new.views}.should raise_error(Sequel::NotImplemented)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
400
406
|
describe "Database#indexes" do
|
401
407
|
specify "should raise Sequel::NotImplemented" do
|
402
408
|
proc {Sequel::Database.new.indexes(:table)}.should raise_error(Sequel::NotImplemented)
|
@@ -803,6 +809,25 @@ describe "Database#transaction" do
|
|
803
809
|
t.join
|
804
810
|
@db.transactions.should be_empty
|
805
811
|
end
|
812
|
+
|
813
|
+
if (!defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
|
814
|
+
specify "should handle Thread#kill for transactions inside threads" do
|
815
|
+
q = Queue.new
|
816
|
+
q1 = Queue.new
|
817
|
+
t = Thread.new do
|
818
|
+
@db.transaction do
|
819
|
+
@db.execute 'DROP TABLE test'
|
820
|
+
q1.push nil
|
821
|
+
q.pop
|
822
|
+
@db.execute 'DROP TABLE test2'
|
823
|
+
end
|
824
|
+
end
|
825
|
+
q1.pop
|
826
|
+
t.kill
|
827
|
+
t.join
|
828
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test', 'ROLLBACK']
|
829
|
+
end
|
830
|
+
end
|
806
831
|
end
|
807
832
|
|
808
833
|
describe "Database#transaction with savepoints" do
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -538,34 +538,55 @@ describe "Dataset#where" do
|
|
538
538
|
|
539
539
|
specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
|
540
540
|
@dataset.meta_def(:supports_multiple_column_in?){false}
|
541
|
-
|
541
|
+
db = MockDatabase.new()
|
542
|
+
d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
|
543
|
+
d1.instance_variable_set(:@columns, [:id1, :id2])
|
542
544
|
def d1.fetch_rows(sql)
|
543
|
-
|
545
|
+
db << sql
|
544
546
|
@columns = [:id1, :id2]
|
545
547
|
yield(:id1=>1, :id2=>2)
|
546
548
|
yield(:id1=>3, :id2=>4)
|
547
549
|
end
|
548
|
-
d1.instance_variable_get(:@sql_used).should == nil
|
549
550
|
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
550
|
-
|
551
|
-
|
551
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
552
|
+
db.sqls.clear
|
552
553
|
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
553
|
-
|
554
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
554
555
|
end
|
555
556
|
|
556
557
|
specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
|
557
558
|
@dataset.meta_def(:supports_multiple_column_in?){false}
|
558
|
-
|
559
|
+
db = MockDatabase.new()
|
560
|
+
d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
|
561
|
+
d1.instance_variable_set(:@columns, [:id1, :id2])
|
559
562
|
def d1.fetch_rows(sql)
|
560
|
-
|
563
|
+
db << sql
|
561
564
|
@columns = [:id1, :id2]
|
562
565
|
end
|
563
|
-
d1.instance_variable_get(:@sql_used).should == nil
|
564
566
|
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
565
|
-
|
566
|
-
|
567
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
568
|
+
db.sqls.clear
|
567
569
|
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
568
|
-
|
570
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
571
|
+
end
|
572
|
+
|
573
|
+
specify "should handle IN/NOT IN queries for datasets with row_procs" do
|
574
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
575
|
+
db = MockDatabase.new()
|
576
|
+
d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia')
|
577
|
+
d1.row_proc = proc{|h| Object.new}
|
578
|
+
d1.instance_variable_set(:@columns, [:id1, :id2])
|
579
|
+
def d1.fetch_rows(sql)
|
580
|
+
db << sql
|
581
|
+
@columns = [:id1, :id2]
|
582
|
+
yield(:id1=>1, :id2=>2)
|
583
|
+
yield(:id1=>3, :id2=>4)
|
584
|
+
end
|
585
|
+
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
586
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
587
|
+
db.sqls.clear
|
588
|
+
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
589
|
+
db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
|
569
590
|
end
|
570
591
|
|
571
592
|
specify "should accept a subquery for an EXISTS clause" do
|
@@ -3283,6 +3304,7 @@ describe "Dataset prepared statements and bound variables " do
|
|
3283
3304
|
ds
|
3284
3305
|
end
|
3285
3306
|
@ds = @db[:items]
|
3307
|
+
@ds.meta_def(:insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
|
3286
3308
|
end
|
3287
3309
|
|
3288
3310
|
specify "#call should take a type and bind hash and interpolate it" do
|
@@ -3291,11 +3313,13 @@ describe "Dataset prepared statements and bound variables " do
|
|
3291
3313
|
@ds.filter(:num=>:$n).call(:delete, :n=>1)
|
3292
3314
|
@ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
|
3293
3315
|
@ds.call(:insert, {:n=>1}, :num=>:$n)
|
3316
|
+
@ds.call(:insert_select, {:n=>1}, :num=>:$n)
|
3294
3317
|
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3295
3318
|
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3296
3319
|
'DELETE FROM items WHERE (num = 1)',
|
3297
3320
|
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3298
|
-
'INSERT INTO items (num) VALUES (1)'
|
3321
|
+
'INSERT INTO items (num) VALUES (1)',
|
3322
|
+
'INSERT INTO items (num) VALUES (1) RETURNING *']
|
3299
3323
|
end
|
3300
3324
|
|
3301
3325
|
specify "#prepare should take a type and name and store it in the database for later use with call" do
|
@@ -3305,18 +3329,21 @@ describe "Dataset prepared statements and bound variables " do
|
|
3305
3329
|
pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
|
3306
3330
|
pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
|
3307
3331
|
pss << @ds.prepare(:insert, :in, :num=>:$n)
|
3308
|
-
@
|
3309
|
-
[:
|
3332
|
+
pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
|
3333
|
+
@db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sn, :un]
|
3334
|
+
[:sn, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
|
3310
3335
|
@db.call(:sn, :n=>1)
|
3311
3336
|
@db.call(:fn, :n=>1)
|
3312
3337
|
@db.call(:dn, :n=>1)
|
3313
3338
|
@db.call(:un, :n=>1, :n2=>2)
|
3314
3339
|
@db.call(:in, :n=>1)
|
3340
|
+
@db.call(:ins, :n=>1)
|
3315
3341
|
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3316
3342
|
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3317
3343
|
'DELETE FROM items WHERE (num = 1)',
|
3318
3344
|
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3319
|
-
'INSERT INTO items (num) VALUES (1)'
|
3345
|
+
'INSERT INTO items (num) VALUES (1)',
|
3346
|
+
'INSERT INTO items (num) VALUES (1) RETURNING *']
|
3320
3347
|
end
|
3321
3348
|
|
3322
3349
|
specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
|
@@ -3599,6 +3626,76 @@ describe "Sequel::Dataset#qualify_to_first_source" do
|
|
3599
3626
|
end
|
3600
3627
|
end
|
3601
3628
|
|
3629
|
+
describe "Sequel::Dataset#unbind" do
|
3630
|
+
before do
|
3631
|
+
@ds = MockDatabase.new[:t]
|
3632
|
+
@u = proc{|ds| ds, bv = ds.unbind; [ds.sql, bv]}
|
3633
|
+
end
|
3634
|
+
|
3635
|
+
specify "should unbind values assigned to equality and inequality statements" do
|
3636
|
+
@ds.filter(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo = $foo)"
|
3637
|
+
@ds.exclude(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo != $foo)"
|
3638
|
+
@ds.filter{foo > 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo > $foo)"
|
3639
|
+
@ds.filter{foo >= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo >= $foo)"
|
3640
|
+
@ds.filter{foo < 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo < $foo)"
|
3641
|
+
@ds.filter{foo <= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo <= $foo)"
|
3642
|
+
end
|
3643
|
+
|
3644
|
+
specify "should return variables that could be used bound to recreate the previous query" do
|
3645
|
+
@ds.filter(:foo=>1).unbind.last.should == {:foo=>1}
|
3646
|
+
@ds.exclude(:foo=>1).unbind.last.should == {:foo=>1}
|
3647
|
+
end
|
3648
|
+
|
3649
|
+
specify "should handle numerics, strings, dates, times, and datetimes" do
|
3650
|
+
@u[@ds.filter(:foo=>1)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1}]
|
3651
|
+
@u[@ds.filter(:foo=>1.0)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1.0}]
|
3652
|
+
@u[@ds.filter(:foo=>BigDecimal.new('1.0'))].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>BigDecimal.new('1.0')}]
|
3653
|
+
@u[@ds.filter(:foo=>'a')].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>'a'}]
|
3654
|
+
@u[@ds.filter(:foo=>Date.today)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>Date.today}]
|
3655
|
+
t = Time.now
|
3656
|
+
@u[@ds.filter(:foo=>t)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>t}]
|
3657
|
+
dt = DateTime.now
|
3658
|
+
@u[@ds.filter(:foo=>dt)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>dt}]
|
3659
|
+
end
|
3660
|
+
|
3661
|
+
specify "should not unbind literal strings" do
|
3662
|
+
@u[@ds.filter(:foo=>'a'.lit)].should == ["SELECT * FROM t WHERE (foo = a)", {}]
|
3663
|
+
end
|
3664
|
+
|
3665
|
+
specify "should not unbind Identifiers, QualifiedIdentifiers, or Symbols used as booleans" do
|
3666
|
+
@u[@ds.filter(:foo).filter{bar}.filter{foo__bar}].should == ["SELECT * FROM t WHERE (foo AND bar AND foo.bar)", {}]
|
3667
|
+
end
|
3668
|
+
|
3669
|
+
specify "should not unbind for values it doesn't understand" do
|
3670
|
+
@u[@ds.filter(:foo=>Class.new{def sql_literal(ds) 'bar' end}.new)].should == ["SELECT * FROM t WHERE (foo = bar)", {}]
|
3671
|
+
end
|
3672
|
+
|
3673
|
+
specify "should handle QualifiedIdentifiers" do
|
3674
|
+
@u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
|
3675
|
+
end
|
3676
|
+
|
3677
|
+
specify "should handle deep nesting" do
|
3678
|
+
@u[@ds.filter{foo > 1}.and{bar < 2}.or(:baz=>3).and({~{:x=>4}=>true}.case(false))].should == ["SELECT * FROM t WHERE ((((foo > $foo) AND (bar < $bar)) OR (baz = $baz)) AND (CASE WHEN (x != $x) THEN 't' ELSE 'f' END))", {:foo=>1, :bar=>2, :baz=>3, :x=>4}]
|
3679
|
+
end
|
3680
|
+
|
3681
|
+
specify "should handle JOIN ON" do
|
3682
|
+
@u[@ds.cross_join(:x).join(:a, [:u]).join(:b, [[:c, :d], [:e,1]])].should == ["SELECT * FROM t CROSS JOIN x INNER JOIN a USING (u) INNER JOIN b ON ((b.c = a.d) AND (b.e = $b.e))", {:"b.e"=>1}]
|
3683
|
+
end
|
3684
|
+
|
3685
|
+
specify "should raise an UnbindDuplicate exception if same variable is used with multiple different values" do
|
3686
|
+
proc{@ds.filter(:foo=>1).or(:foo=>2).unbind}.should raise_error(Sequel::UnbindDuplicate)
|
3687
|
+
end
|
3688
|
+
|
3689
|
+
specify "should handle case where the same variable has the same value in multiple places " do
|
3690
|
+
@u[@ds.filter(:foo=>1).or(:foo=>1)].should == ["SELECT * FROM t WHERE ((foo = $foo) OR (foo = $foo))", {:foo=>1}]
|
3691
|
+
end
|
3692
|
+
|
3693
|
+
specify "should raise Error for unhandled objects inside Identifiers and QualifiedIndentifiers" do
|
3694
|
+
proc{@ds.filter(Sequel::SQL::Identifier.new([]) > 1).unbind}.should raise_error(Sequel::Error)
|
3695
|
+
proc{@ds.filter{foo.qualify({}) > 1}.unbind}.should raise_error(Sequel::Error)
|
3696
|
+
end
|
3697
|
+
end
|
3698
|
+
|
3602
3699
|
describe "Sequel::Dataset #with and #with_recursive" do
|
3603
3700
|
before do
|
3604
3701
|
@db = MockDatabase.new
|
@@ -3977,3 +4074,17 @@ describe "Dataset#lock_style and for_update" do
|
|
3977
4074
|
@ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
|
3978
4075
|
end
|
3979
4076
|
end
|
4077
|
+
|
4078
|
+
describe "Custom ASTTransformer" do
|
4079
|
+
specify "should transform given objects" do
|
4080
|
+
c = Class.new(Sequel::ASTTransformer) do
|
4081
|
+
def v(s)
|
4082
|
+
(s.is_a?(Symbol) || s.is_a?(String)) ? :"#{s}#{s}" : super
|
4083
|
+
end
|
4084
|
+
end.new
|
4085
|
+
ds = MockDatabase.new[:t].cross_join(:a___g).join(:b___h, [:c]).join(:d___i, :e=>:f)
|
4086
|
+
ds.sql.should == 'SELECT * FROM t CROSS JOIN a AS g INNER JOIN b AS h USING (c) INNER JOIN d AS i ON (i.e = h.f)'
|
4087
|
+
ds.clone(:from=>c.transform(ds.opts[:from]), :join=>c.transform(ds.opts[:join])).sql.should ==
|
4088
|
+
'SELECT * FROM tt CROSS JOIN aa AS gg INNER JOIN bb AS hh USING (cc) INNER JOIN dd AS ii ON (ii.ee = hh.ff)'
|
4089
|
+
end
|
4090
|
+
end
|