sequel 3.27.0 → 3.28.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 (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