sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -23,7 +23,7 @@ module Sequel
23
23
  # stored so when the dataset changes, methods defined with def_dataset_method
24
24
  # will be applied to the new dataset.
25
25
  attr_reader :dataset_methods
26
- #
26
+
27
27
  # Array of plugin modules loaded by this class
28
28
  #
29
29
  # Sequel::Model.plugins
@@ -423,10 +423,12 @@ module Sequel
423
423
  @allowed_columns = cols
424
424
  end
425
425
 
426
- # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+
427
- # (specifying a table name in the current database), or a +Dataset+.
426
+ # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+,
427
+ # +LiteralString+, <tt>SQL::Identifier</tt>, <tt>SQL::QualifiedIdentifier</tt>,
428
+ # <tt>SQL::AliasedExpression</tt>
429
+ # (all specifying a table name in the current database), or a +Dataset+.
428
430
  # If a dataset is used, the model's database is changed to the database of the given
429
- # dataset. If a symbol is used, a dataset is created from the current
431
+ # dataset. If a dataset is not used, a dataset is created from the current
430
432
  # database with the table name given. Other arguments raise an +Error+.
431
433
  # Returns self.
432
434
  #
@@ -441,15 +443,15 @@ module Sequel
441
443
  def set_dataset(ds, opts={})
442
444
  inherited = opts[:inherited]
443
445
  @dataset = case ds
444
- when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression
446
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
445
447
  @simple_table = db.literal(ds)
446
- db[ds]
448
+ db.from(ds)
447
449
  when Dataset
448
450
  @simple_table = nil
449
451
  @db = ds.db
450
452
  ds
451
453
  else
452
- raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
454
+ raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
453
455
  end
454
456
  @dataset.row_proc = Proc.new{|r| load(r)}
455
457
  @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
@@ -1684,15 +1686,7 @@ module Sequel
1684
1686
  # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1685
1687
  # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1686
1688
  def with_pk(pk)
1687
- case primary_key = model.primary_key
1688
- when Array
1689
- raise(Error, "single primary key given (#{pk.inspect}) when a composite primary key is expected (#{primary_key.inspect})") unless pk.is_a?(Array)
1690
- raise(Error, "composite primary key given (#{pk.inspect}) does not match composite primary key length (#{primary_key.inspect})") if pk.length != primary_key.length
1691
- first(primary_key.zip(pk))
1692
- else
1693
- raise(Error, "composite primary key given (#{pk.inspect}) when a single primary key is expected (#{primary_key.inspect})") if pk.is_a?(Array)
1694
- first(primary_key=>pk)
1695
- end
1689
+ first(model.qualified_primary_key_hash(pk))
1696
1690
  end
1697
1691
  end
1698
1692
 
@@ -58,6 +58,19 @@ module Sequel
58
58
  self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
59
59
  end
60
60
 
61
+ # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
62
+ def eager_loading_predicate_key
63
+ self[:eager_loading_predicate_key] ||= begin
64
+ calculate_edges
65
+ e = self[:edges].first
66
+ if self[:uses_left_composite_keys]
67
+ e[:right].map{|k| SQL::QualifiedIdentifier.new(e[:table], k)}
68
+ else
69
+ SQL::QualifiedIdentifier.new(e[:table], e[:right])
70
+ end
71
+ end
72
+ end
73
+
61
74
  # The list of joins to use when eager graphing
62
75
  def edges
63
76
  self[:edges] || calculate_edges || self[:edges]
@@ -182,13 +195,28 @@ module Sequel
182
195
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
183
196
  opts[:eager_loader] ||= lambda do |eo|
184
197
  h = eo[:key_hash][left_pk]
185
- eo[:rows].each{|object| object.associations[name] = []}
198
+ rows = eo[:rows]
199
+ rows.each{|object| object.associations[name] = []}
186
200
  ds = opts.associated_class
187
201
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
188
202
  ft = opts[:final_reverse_edge]
189
203
  conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
190
204
  ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
191
- model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
205
+ ds = model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo)
206
+ case opts.eager_limit_strategy
207
+ when :window_function
208
+ delete_rn = true
209
+ rn = ds.row_number_column
210
+ ds = apply_window_function_eager_limit_strategy(ds, opts)
211
+ when :correlated_subquery
212
+ ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
213
+ dsa = ds.send(:dataset_alias, 2)
214
+ opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
215
+ xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa)
216
+ end
217
+ end
218
+ ds.all do |assoc_record|
219
+ assoc_record.values.delete(rn) if delete_rn
192
220
  hash_key = if uses_lcks
193
221
  left_key_alias.map{|k| assoc_record.values.delete(k)}
194
222
  else
@@ -197,6 +225,10 @@ module Sequel
197
225
  next unless objects = h[hash_key]
198
226
  objects.each{|object| object.associations[name].push(assoc_record)}
199
227
  end
228
+ if opts.eager_limit_strategy == :ruby
229
+ limit, offset = opts.limit_and_offset
230
+ rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
231
+ end
200
232
  end
201
233
 
202
234
  join_type = opts[:graph_join_type]
@@ -31,7 +31,7 @@ module Sequel
31
31
  # Return a prepared statement that can be used to lookup a row given a dataset for the row matching
32
32
  # the primary key.
33
33
  def prepared_lookup_dataset(ds)
34
- @prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key)), :first)
34
+ @prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key).map{|k, v| [SQL::QualifiedIdentifier.new(ds.model.table_name, k), v]}), :first)
35
35
  end
36
36
  end
37
37
 
@@ -41,6 +41,11 @@ module Sequel
41
41
  # Time subclass that gets literalized with only the time value, so it operates
42
42
  # like a standard SQL time type.
43
43
  class SQLTime < ::Time
44
+ # Create a new SQLTime instance given an hour, minute, and second.
45
+ def self.create(hour, minute, second, usec = 0)
46
+ t = now
47
+ local(t.year, t.month, t.day, hour, minute, second, usec)
48
+ end
44
49
  end
45
50
 
46
51
  # The SQL module holds classes whose instances represent SQL fragments.
@@ -149,14 +154,17 @@ module Sequel
149
154
  # Operator symbols that take exactly two arguments
150
155
  TWO_ARITY_OPERATORS = [:'=', :'!=', :LIKE, :'NOT LIKE', \
151
156
  :~, :'!~', :'~*', :'!~*', :ILIKE, :'NOT ILIKE'] + \
152
- INEQUALITY_OPERATORS + BITWISE_OPERATORS + IS_OPERATORS + IN_OPERATORS
157
+ INEQUALITY_OPERATORS + IS_OPERATORS + IN_OPERATORS
153
158
 
154
159
  # Operator symbols that take one or more arguments
155
- N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS
160
+ N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS + BITWISE_OPERATORS
156
161
 
157
162
  # Operator symbols that take only a single argument
158
163
  ONE_ARITY_OPERATORS = [:NOT, :NOOP, :'B~']
159
164
 
165
+ # Custom expressions that may have different syntax on different databases
166
+ CUSTOM_EXPRESSIONS = [:extract]
167
+
160
168
  # An array of args for this object
161
169
  attr_reader :args
162
170
 
@@ -185,6 +193,8 @@ module Sequel
185
193
  args[1] = orig_args[1] if IN_OPERATORS.include?(op)
186
194
  when *ONE_ARITY_OPERATORS
187
195
  raise(Error, "The #{op} operator requires a single argument") unless args.length == 1
196
+ when *CUSTOM_EXPRESSIONS
197
+ # nothing
188
198
  else
189
199
  raise(Error, "Invalid operator #{op}")
190
200
  end
@@ -224,14 +234,7 @@ module Sequel
224
234
  # ~:a.sql_number # ~"a"
225
235
  module BitwiseMethods
226
236
  ComplexExpression::BITWISE_OPERATORS.each do |o|
227
- define_method(o) do |ce|
228
- case ce
229
- when BooleanExpression, StringExpression
230
- raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
231
- else
232
- NumericExpression.new(o, self, ce)
233
- end
234
- end
237
+ module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
235
238
  end
236
239
 
237
240
  # Do the bitwise compliment of the self
@@ -249,16 +252,24 @@ module Sequel
249
252
  # :a & :b # "a" AND "b"
250
253
  # :a | :b # "a" OR "b"
251
254
  # ~:a # NOT "a"
255
+ #
256
+ # One exception to this is when a NumericExpression or Integer is the argument
257
+ # to & or |, in which case a bitwise method will be used:
258
+ #
259
+ # :a & 1 # "a" & 1
260
+ # :a | (:b + 1) # "a" | ("b" + 1)
252
261
  module BooleanMethods
253
262
  ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
254
- define_method(m) do |ce|
255
- case ce
256
- when NumericExpression, StringExpression
257
- raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression")
258
- else
259
- BooleanExpression.new(o, self, ce)
263
+ module_eval(<<-END, __FILE__, __LINE__+1)
264
+ def #{m}(o)
265
+ case o
266
+ when NumericExpression, Integer
267
+ NumericExpression.new(#{m.inspect}, self, o)
268
+ else
269
+ BooleanExpression.new(#{o.inspect}, self, o)
270
+ end
260
271
  end
261
- end
272
+ END
262
273
  end
263
274
 
264
275
  # Create a new BooleanExpression with NOT, representing the inversion of whatever self represents.
@@ -326,7 +337,7 @@ module Sequel
326
337
  # doesn't use the standard function calling convention, and it
327
338
  # doesn't work on all databases.
328
339
  def extract(datetime_part)
329
- Function.new(:extract, PlaceholderLiteralString.new("#{datetime_part} FROM ?", [self])).sql_number
340
+ NumericExpression.new(:extract, datetime_part, self)
330
341
  end
331
342
 
332
343
  # Return a BooleanExpression representation of +self+.
@@ -372,34 +383,12 @@ module Sequel
372
383
  # 'a'.lit <= :b # a <= "b"
373
384
  module InequalityMethods
374
385
  ComplexExpression::INEQUALITY_OPERATORS.each do |o|
375
- define_method(o) do |ce|
376
- case ce
377
- when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
378
- raise(Error, "cannot apply #{o} to a boolean expression")
379
- else
380
- BooleanExpression.new(o, self, ce)
381
- end
382
- end
386
+ module_eval("def #{o}(o) BooleanExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
383
387
  end
384
388
  end
385
389
 
386
- # This module augments the default initalize method for the
387
- # +ComplexExpression+ subclass it is included in, so that
388
- # attempting to use boolean input when initializing a +NumericExpression+
389
- # or +StringExpression+ results in an error. It is not expected to be
390
- # used directly.
390
+ # Only exists for backwards compatibility, ignore it.
391
391
  module NoBooleanInputMethods
392
- # Raise an +Error+ if one of the args would be boolean in an SQL
393
- # context, otherwise call super.
394
- def initialize(op, *args)
395
- args.each do |a|
396
- case a
397
- when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
398
- raise(Error, "cannot apply #{op} to a boolean expression")
399
- end
400
- end
401
- super
402
- end
403
392
  end
404
393
 
405
394
  # This module includes the standard mathematical methods (+, -, *, and /)
@@ -410,15 +399,26 @@ module Sequel
410
399
  # :a - :b # "a" - "b"
411
400
  # :a * :b # "a" * "b"
412
401
  # :a / :b # "a" / "b"
402
+ #
403
+ # One exception to this is if + is called with a +String+ or +StringExpression+,
404
+ # in which case the || operator is used instead of the + operator:
405
+ #
406
+ # :a + 'b' # "a" || 'b'
413
407
  module NumericMethods
414
408
  ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
415
- define_method(o) do |ce|
416
- case ce
417
- when BooleanExpression, StringExpression
418
- raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
419
- else
420
- NumericExpression.new(o, self, ce)
421
- end
409
+ module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__) unless o == :+
410
+ end
411
+
412
+ # Use || as the operator when called with StringExpression and String instances,
413
+ # and the + operator for LiteralStrings and all other types.
414
+ def +(ce)
415
+ case ce
416
+ when LiteralString
417
+ NumericExpression.new(:+, self, ce)
418
+ when StringExpression, String
419
+ StringExpression.new(:'||', self, ce)
420
+ else
421
+ NumericExpression.new(:+, self, ce)
422
422
  end
423
423
  end
424
424
  end
@@ -606,6 +606,21 @@ module Sequel
606
606
  BooleanExpression.new(:NOT, ce)
607
607
  end
608
608
  end
609
+
610
+ # Always use an AND operator for & on BooleanExpressions
611
+ def &(ce)
612
+ BooleanExpression.new(:AND, self, ce)
613
+ end
614
+
615
+ # Always use an OR operator for | on BooleanExpressions
616
+ def |(ce)
617
+ BooleanExpression.new(:OR, self, ce)
618
+ end
619
+
620
+ # Return self instead of creating a new object to save on memory.
621
+ def sql_boolean
622
+ self
623
+ end
609
624
  end
610
625
 
611
626
  # Represents an SQL CASE expression, used for conditional branching in SQL.
@@ -673,6 +688,21 @@ module Sequel
673
688
  include CastMethods
674
689
  include OrderMethods
675
690
  include SubscriptMethods
691
+
692
+ # Return a BooleanExpression with the same op and args.
693
+ def sql_boolean
694
+ BooleanExpression.new(self.op, *self.args)
695
+ end
696
+
697
+ # Return a NumericExpression with the same op and args.
698
+ def sql_number
699
+ NumericExpression.new(self.op, *self.args)
700
+ end
701
+
702
+ # Return a StringExpression with the same op and args.
703
+ def sql_string
704
+ StringExpression.new(self.op, *self.args)
705
+ end
676
706
  end
677
707
 
678
708
  # Represents constants or psuedo-constants (e.g. +CURRENT_DATE+) in SQL.
@@ -847,7 +877,16 @@ module Sequel
847
877
  include BitwiseMethods
848
878
  include NumericMethods
849
879
  include InequalityMethods
850
- include NoBooleanInputMethods
880
+
881
+ # Always use + for + operator for NumericExpressions.
882
+ def +(ce)
883
+ NumericExpression.new(:+, self, ce)
884
+ end
885
+
886
+ # Return self instead of creating a new object to save on memory.
887
+ def sql_number
888
+ self
889
+ end
851
890
  end
852
891
 
853
892
  # Represents a column/expression to order the result set by.
@@ -913,7 +952,6 @@ module Sequel
913
952
  include StringMethods
914
953
  include StringConcatenationMethods
915
954
  include InequalityMethods
916
- include NoBooleanInputMethods
917
955
 
918
956
  # Map of [regexp, case_insenstive] to +ComplexExpression+ operator symbol
919
957
  LIKE_MAP = {[true, true]=>:'~*', [true, false]=>:~, [false, true]=>:ILIKE, [false, false]=>:LIKE}
@@ -962,6 +1000,11 @@ module Sequel
962
1000
  end
963
1001
  end
964
1002
  private_class_method :like_element
1003
+
1004
+ # Return self instead of creating a new object to save on memory.
1005
+ def sql_string
1006
+ self
1007
+ end
965
1008
  end
966
1009
 
967
1010
  # Represents an SQL array access, with multiple possible arguments.
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 27
6
+ MINOR = 28
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ #Author: Roy L Zuo (roylzuo at gmail dot com)
4
+ #Description:
5
+
6
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
7
+
8
+ require ENV['SEQUEL_DB2_SPEC_REQUIRE'] if ENV['SEQUEL_DB2_SPEC_REQUIRE']
9
+
10
+ unless defined?(DB2_DB)
11
+ DB2_DB = Sequel.connect(ENV['SEQUEL_DB2_SPEC_DB']||DB2_URL)
12
+ end
13
+
14
+ if DB2_DB.table_exists?(:test)
15
+ DB2_DB.drop_table :test
16
+ end
17
+ INTEGRATION_DB = DB2_DB unless defined?(INTEGRATION_DB)
18
+
19
+ describe Sequel::Database do
20
+ before do
21
+ @db = DB2_DB
22
+ @db.create_table(:test){String :a}
23
+ @ds = @db[:test]
24
+ end
25
+
26
+ after do
27
+ @db.drop_table(:test)
28
+ end
29
+
30
+ specify "should provide disconnect functionality after preparing a connection" do
31
+ @ds.prepare(:first, :a).call
32
+ @db.disconnect
33
+ @db.pool.size.should == 0
34
+ end
35
+
36
+ specify "should return version correctly" do
37
+ @db.db2_version.should match(/DB2 v/i)
38
+ end
39
+ end
40
+
41
+ describe "Simple Dataset operations" do
42
+ before do
43
+ DB2_DB.create_table!(:items) do
44
+ Integer :id, :primary_key => true
45
+ Integer :number
46
+ end
47
+ @ds = DB2_DB[:items]
48
+ @ds.insert(:number=>10, :id => 1 )
49
+ end
50
+ after do
51
+ DB2_DB.drop_table(:items)
52
+ end
53
+ cspecify "should insert with a primary key specified", :mssql do
54
+ @ds.insert(:id=>100, :number=>20)
55
+ @ds.count.should == 2
56
+ @ds.order(:id).all.should == [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
57
+ end
58
+ end
59
+
60
+ describe Sequel::Database do
61
+ before do
62
+ @db = DB2_DB
63
+ end
64
+ after do
65
+ @db.drop_table(:items)
66
+ end
67
+ specify "should parse primary keys from the schema properly" do
68
+ @db.create_table!(:items){Integer :number}
69
+ @db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
70
+ @db.create_table!(:items){primary_key :number}
71
+ @db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number]
72
+ @db.create_table!(:items){Integer :number1, :null => false; Integer :number2, :null => false; primary_key [:number1, :number2]}
73
+ @db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number1, :number2]
74
+ end
75
+ end
76
+
77
+ describe "Sequel::IBMDB.convert_smallint_to_bool" do
78
+ before do
79
+ @db = DB2_DB
80
+ @db.create_table!(:booltest){column :b, 'smallint'; column :i, 'integer'}
81
+ @ds = @db[:booltest]
82
+ end
83
+ after do
84
+ Sequel::IBMDB.convert_smallint_to_bool = true
85
+ @db.drop_table(:booltest)
86
+ end
87
+
88
+ specify "should consider smallint datatypes as boolean if set, but not larger smallints" do
89
+ @db.schema(:booltest, :reload=>true).first.last[:type].should == :boolean
90
+ @db.schema(:booltest, :reload=>true).first.last[:db_type].should match /smallint/i
91
+ Sequel::IBMDB.convert_smallint_to_bool = false
92
+ @db.schema(:booltest, :reload=>true).first.last[:type].should == :integer
93
+ @db.schema(:booltest, :reload=>true).first.last[:db_type].should match /smallint/i
94
+ end
95
+
96
+ specify "should return smallints as bools and integers as integers when set" do
97
+ Sequel::IBMDB.convert_smallint_to_bool = true
98
+ @ds.delete
99
+ @ds << {:b=>true, :i=>10}
100
+ @ds.all.should == [{:b=>true, :i=>10}]
101
+ @ds.delete
102
+ @ds << {:b=>false, :i=>0}
103
+ @ds.all.should == [{:b=>false, :i=>0}]
104
+ @ds.delete
105
+ @ds << {:b=>true, :i=>1}
106
+ @ds.all.should == [{:b=>true, :i=>1}]
107
+ end
108
+
109
+ specify "should return all smallints as integers when unset" do
110
+ Sequel::IBMDB.convert_smallint_to_bool = false
111
+ @ds.delete
112
+ @ds << {:b=>true, :i=>10}
113
+ @ds.all.should == [{:b=>1, :i=>10}]
114
+ @ds.delete
115
+ @ds << {:b=>false, :i=>0}
116
+ @ds.all.should == [{:b=>0, :i=>0}]
117
+
118
+ @ds.delete
119
+ @ds << {:b=>1, :i=>10}
120
+ @ds.all.should == [{:b=>1, :i=>10}]
121
+ @ds.delete
122
+ @ds << {:b=>0, :i=>0}
123
+ @ds.all.should == [{:b=>0, :i=>0}]
124
+ end
125
+ end if DB2_DB.adapter_scheme == :ibmdb
126
+
127
+ describe "Simple Dataset operations in transactions" do
128
+ before do
129
+ DB2_DB.create_table!(:items_insert_in_transaction) do
130
+ Integer :id, :primary_key => true
131
+ integer :number
132
+ end
133
+ @ds = DB2_DB[:items_insert_in_transaction]
134
+ end
135
+ after do
136
+ DB2_DB.drop_table(:items_insert_in_transaction)
137
+ end
138
+
139
+ specify "should insert correctly with a primary key specified inside a transaction" do
140
+ DB2_DB.transaction do
141
+ @ds.insert(:id=>100, :number=>20)
142
+ @ds.count.should == 1
143
+ @ds.order(:id).all.should == [{:id=>100, :number=>20}]
144
+ end
145
+ end
146
+ end