sequel 3.1.0 → 3.2.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 (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -6,10 +6,6 @@
6
6
 
7
7
  module Sequel
8
8
  class Database
9
- POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
10
- MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
11
- STRING_DEFAULT_RE = /\A'(.*)'\z/
12
-
13
9
  # Dump indexes for all tables as a migration. This complements
14
10
  # the :indexes=>false option to dump_schema_migration. Options:
15
11
  # * :same_db - Create a dump for the same database type, so
@@ -72,65 +68,12 @@ END_MIG
72
68
  end
73
69
 
74
70
  private
75
-
76
- # Convert the given default, which should be a database specific string, into
77
- # a ruby object.
78
- def column_schema_to_ruby_default(default, type, options)
79
- return if default.nil?
80
- orig_default = default
81
- if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
82
- default = m[1] || m[2]
83
- end
84
- if [:string, :blob, :date, :datetime, :time].include?(type)
85
- if database_type == :mysql
86
- if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
87
- return column_schema_to_ruby_default_fallback(default, options)
88
- end
89
- orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
90
- end
91
- if m = STRING_DEFAULT_RE.match(default)
92
- default = m[1].gsub("''", "'")
93
- else
94
- return column_schema_to_ruby_default_fallback(default, options)
95
- end
96
- end
97
- res = begin
98
- case type
99
- when :boolean
100
- case default
101
- when /[f0]/i
102
- false
103
- when /[t1]/i
104
- true
105
- end
106
- when :string
107
- default
108
- when :blob
109
- Sequel::SQL::Blob.new(default)
110
- when :integer
111
- Integer(default)
112
- when :float
113
- Float(default)
114
- when :date
115
- Sequel.string_to_date(default)
116
- when :datetime
117
- DateTime.parse(default)
118
- when :time
119
- Sequel.string_to_time(default)
120
- when :decimal
121
- BigDecimal.new(default)
122
- end
123
- rescue
124
- nil
125
- end
126
- res.nil? ? column_schema_to_ruby_default_fallback(orig_default, options) : res
127
- end
128
71
 
129
- # If the database default can't be converted, return the string with the inspect
72
+ # If a database default exists and can't be converted, return the string with the inspect
130
73
  # method modified so that .lit is always appended after it, only if the
131
74
  # :same_db option is used.
132
75
  def column_schema_to_ruby_default_fallback(default, options)
133
- if options[:same_db]
76
+ if options[:same_db] && default.is_a?(String)
134
77
  default = default.to_s
135
78
  def default.inspect
136
79
  "#{super}.lit"
@@ -148,8 +91,12 @@ END_MIG
148
91
  col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
149
92
  type = col_opts.delete(:type)
150
93
  col_opts.delete(:size) if col_opts[:size].nil?
151
- default = column_schema_to_ruby_default(schema[:default], schema[:type], options) if schema[:default]
152
- col_opts[:default] = default unless default.nil?
94
+ col_opts[:default] = if schema[:ruby_default].nil?
95
+ column_schema_to_ruby_default_fallback(schema[:default], options)
96
+ else
97
+ schema[:ruby_default]
98
+ end
99
+ col_opts.delete(:default) if col_opts[:default].nil?
153
100
  col_opts[:null] = false if schema[:allow_null] == false
154
101
  [:column, name, type, col_opts]
155
102
  end
data/lib/sequel/model.rb CHANGED
@@ -40,13 +40,13 @@ module Sequel
40
40
  # Class methods added to model that call the method of the same name on the dataset
41
41
  DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph
42
42
  each each_page empty? except exclude filter first from from_self
43
- full_outer_join get graph group group_and_count group_by having import
43
+ full_outer_join get graph grep group group_and_count group_by having import
44
44
  inner_join insert insert_multiple intersect interval join join_table
45
45
  last left_outer_join limit map multi_insert naked order order_by
46
- order_more paginate print query range reverse_order right_outer_join
46
+ order_more paginate print qualify query range reverse_order right_outer_join
47
47
  select select_all select_more server set set_graph_aliases
48
48
  single_value to_csv to_hash union unfiltered unordered
49
- update where with_sql'.map{|x| x.to_sym}
49
+ update where with with_sql'.map{|x| x.to_sym}
50
50
 
51
51
  # Class instance variables to set to nil when a subclass is created, for -w compliance
52
52
  EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
@@ -1104,6 +1104,14 @@ module Sequel
1104
1104
  ds.eager_graph_associations(ds, model, table_name, [], *associations)
1105
1105
  end
1106
1106
 
1107
+ # Do not attempt to split the result set into associations,
1108
+ # just return results as simple objects. This is useful if you
1109
+ # want to use eager_graph as a shortcut to have all of the joins
1110
+ # and aliasing set up, but want to do something else with the dataset.
1111
+ def ungraphed
1112
+ super.clone(:eager_graph=>nil)
1113
+ end
1114
+
1107
1115
  protected
1108
1116
 
1109
1117
  # Call graph on the association with the correct arguments,
@@ -1240,7 +1248,7 @@ module Sequel
1240
1248
  end
1241
1249
  table_alias
1242
1250
  end
1243
-
1251
+
1244
1252
  private
1245
1253
 
1246
1254
  # Make sure the association is valid for this model, and return the related AssociationReflection.
@@ -563,6 +563,13 @@ module Sequel
563
563
  def associations
564
564
  @associations ||= {}
565
565
  end
566
+
567
+ # The autoincrementing primary key for this model object. Should be
568
+ # overridden if you have a composite primary key with one part of it
569
+ # being autoincrementing.
570
+ def autoincrementing_primary_key
571
+ primary_key
572
+ end
566
573
 
567
574
  # The columns that have been updated. This isn't completely accurate,
568
575
  # see Model#[]=.
@@ -794,7 +801,7 @@ module Sequel
794
801
  iid = ds.insert(@values)
795
802
  # if we have a regular primary key and it's not set in @values,
796
803
  # we assume it's the last inserted id
797
- if (pk = primary_key) && !(Array === pk) && !@values[pk]
804
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
798
805
  @values[pk] = iid
799
806
  end
800
807
  @this = nil if pk
@@ -55,7 +55,7 @@ module Sequel
55
55
  # Set the sti_key column to the name of the model.
56
56
  def before_create
57
57
  return false if super == false
58
- send("#{model.sti_key}=", model.name.to_s)
58
+ send("#{model.sti_key}=", model.name.to_s) unless send(model.sti_key)
59
59
  end
60
60
  end
61
61
  end
data/lib/sequel/sql.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  module Sequel
2
+ class LiteralString < ::String
3
+ end
4
+
2
5
  # The SQL module holds classes whose instances represent SQL fragments.
3
6
  # It also holds modules that are included in core ruby classes that
4
7
  # make Sequel a friendly DSL.
5
8
  module SQL
9
+
6
10
  ### Parent Classes ###
7
11
 
8
12
  # Classes/Modules aren't an alphabetical order due to the fact that
@@ -693,6 +697,9 @@ module Sequel
693
697
  include StringConcatenationMethods
694
698
  include InequalityMethods
695
699
  include NoBooleanInputMethods
700
+
701
+ # Map of [regexp, case_insenstive] to ComplexExpression operator
702
+ LIKE_MAP = {[true, true]=>:'~*', [true, false]=>:~, [false, true]=>:ILIKE, [false, false]=>:LIKE}
696
703
 
697
704
  # Creates a SQL pattern match exprssion. left (l) is the SQL string we
698
705
  # are matching against, and ces are the patterns we are matching.
@@ -709,13 +716,27 @@ module Sequel
709
716
  # if a case insensitive regular expression is used (//i), that particular
710
717
  # pattern which will always be case insensitive.
711
718
  def self.like(l, *ces)
712
- case_insensitive = (ces.last.is_a?(Hash) ? ces.pop : {})[:case_insensitive]
719
+ l, lre, lci = like_element(l)
720
+ lci = (ces.last.is_a?(Hash) ? ces.pop : {})[:case_insensitive] ? true : lci
713
721
  ces.collect! do |ce|
714
- op, expr = Regexp === ce ? [ce.casefold? || case_insensitive ? :'~*' : :~, ce.source] : [case_insensitive ? :ILIKE : :LIKE, ce]
715
- BooleanExpression.new(op, l, expr)
722
+ r, rre, rci = like_element(ce)
723
+ BooleanExpression.new(LIKE_MAP[[lre||rre, lci||rci]], l, r)
716
724
  end
717
725
  ces.length == 1 ? ces.at(0) : BooleanExpression.new(:OR, *ces)
718
726
  end
727
+
728
+ # An array of three parts:
729
+ # * The object to use
730
+ # * Whether it is a regular expression
731
+ # * Whether it is case insensitive
732
+ def self.like_element(re) # :nodoc:
733
+ if re.is_a?(Regexp)
734
+ [re.source, true, re.casefold?]
735
+ else
736
+ [re, false, false]
737
+ end
738
+ end
739
+ private_class_method :like_element
719
740
  end
720
741
 
721
742
  # Represents an SQL array. Added so it is possible to deal with a
@@ -769,38 +790,124 @@ module Sequel
769
790
  # the methods defined by Sequel, or if you are running on ruby 1.9.
770
791
  #
771
792
  # An instance of this class is yielded to the block supplied to filter, order, and select.
772
- # If Sequel.virtual_row_instance_eval is true and the block doesn't take an argument,
773
- # the block is instance_evaled in the context of a new instance of this class.
793
+ # If the block doesn't take an argument, the block is instance_evaled in the context of
794
+ # a new instance of this class.
795
+ #
796
+ # VirtualRow uses method_missing to return Identifiers, QualifiedIdentifiers, Functions, or WindowFunctions,
797
+ # depending on how it is called. If a block is not given, creates one of the following objects:
798
+ # * Function - returned if any arguments are supplied, using the method name
799
+ # as the function name, and the arguments as the function arguments.
800
+ # * QualifiedIdentifier - returned if the method name contains __, with the
801
+ # table being the part before __, and the column being the part after.
802
+ # * Identifier - returned otherwise, using the method name.
803
+ # If a block is given, it returns either a Function or WindowFunction, depending on the first
804
+ # argument to the method. Note that the block is currently not called by the code, though
805
+ # this may change in a future version. If the first argument is:
806
+ # * no arguments given - uses a Function with no arguments.
807
+ # * :* - uses a Function with a literal wildcard argument (*), mostly useful for COUNT.
808
+ # * :distinct - uses a Function that prepends DISTINCT to the rest of the arguments, mostly
809
+ # useful for aggregate functions.
810
+ # * :over - uses a WindowFunction. If a second argument is provided, it should be a hash
811
+ # of options which are passed to Window (e.g. :window, :partition, :order, :frame). The
812
+ # arguments to the function itself should be specified as :*=>true for a wildcard, or via
813
+ # the :args option.
774
814
  #
775
815
  # Examples:
776
816
  #
777
817
  # ds = DB[:t]
818
+ # # Argument yielded to block
778
819
  # ds.filter{|r| r.name < 2} # SELECT * FROM t WHERE (name < 2)
779
- # ds.filter{|r| r.table__column + 1 < 2} # SELECT * FROM t WHERE ((table.column + 1) < 2)
780
- # ds.filter{|r| r.is_active(1, 'arg2')} # SELECT * FROM t WHERE is_active(1, 'arg2')
820
+ # # Block without argument (instance_eval)
821
+ # ds.filter{name < 2} # SELECT * FROM t WHERE (name < 2)
822
+ # # Qualified identifiers
823
+ # ds.filter{table__column + 1 < 2} # SELECT * FROM t WHERE ((table.column + 1) < 2)
824
+ # # Functions
825
+ # ds.filter{is_active(1, 'arg2')} # SELECT * FROM t WHERE is_active(1, 'arg2')
826
+ # ds.select{version{}} # SELECT version() FROM t
827
+ # ds.select{count(:*){}} # SELECT count(*) FROM t
828
+ # ds.select{count(:distinct, col1){}} # SELECT count(DISTINCT col1) FROM t
829
+ # # Window Functions
830
+ # ds.select{rank(:over){}} # SELECT rank() OVER () FROM t
831
+ # ds.select{count(:over, :*=>true){}} # SELECT count(*) OVER () FROM t
832
+ # ds.select{sum(:over, :args=>col1, :partition=>col2, :order=>col3){}} # SELECT sum(col1) OVER (PARTITION BY col2 ORDER BY col3) FROM t
781
833
  class VirtualRow
782
- # Can return Identifiers, QualifiedIdentifiers, or Functions:
783
- #
784
- # * Function - returned if any arguments are supplied, using the method name
785
- # as the function name, and the arguments as the function arguments.
786
- # * QualifiedIdentifier - returned if the method name contains __, with the
787
- # table being the part before __, and the column being the part after.
788
- # * Identifier - returned otherwise, using the method name.
789
- def method_missing(m, *args)
790
- if args.empty?
791
- table, column = m.to_s.split('__', 2)
834
+ WILDCARD = LiteralString.new('*').freeze
835
+ QUESTION_MARK = LiteralString.new('?').freeze
836
+ COMMA_SEPARATOR = LiteralString.new(', ').freeze
837
+ DOUBLE_UNDERSCORE = '__'.freeze
838
+
839
+ # Return Identifiers, QualifiedIdentifiers, Functions, or WindowFunctions, depending
840
+ # on arguments and whether a block is provided. Does not currently call the block.
841
+ # See the class level documentation.
842
+ def method_missing(m, *args, &block)
843
+ if block
844
+ if args.empty?
845
+ Function.new(m)
846
+ else
847
+ case arg = args.shift
848
+ when :*
849
+ Function.new(m, WILDCARD)
850
+ when :distinct
851
+ Function.new(m, PlaceholderLiteralString.new("DISTINCT #{args.map{QUESTION_MARK}.join(COMMA_SEPARATOR)}", args))
852
+ when :over
853
+ opts = args.shift || {}
854
+ fun_args = ::Kernel.Array(opts[:*] ? WILDCARD : opts[:args])
855
+ WindowFunction.new(Function.new(m, *fun_args), Window.new(opts))
856
+ else
857
+ raise Error, 'unsupported VirtualRow method argument used with block'
858
+ end
859
+ end
860
+ elsif args.empty?
861
+ table, column = m.to_s.split(DOUBLE_UNDERSCORE, 2)
792
862
  column ? QualifiedIdentifier.new(table, column) : Identifier.new(m)
793
863
  else
794
864
  Function.new(m, *args)
795
865
  end
796
866
  end
797
867
  end
868
+
869
+ # A window is part of a window function specifying the window over which the function operates.
870
+ # It is separated from the WindowFunction class because it also can be used separately on
871
+ # some databases.
872
+ class Window < Expression
873
+ # The options for this window. Options currently used are:
874
+ # * :frame - if specified, should be :all or :rows. :all always operates over all rows in the
875
+ # partition, while :rows excludes the current row's later peers. The default is to include
876
+ # all previous rows in the partition up to the current row's last peer.
877
+ # * :order - order on the column(s) given
878
+ # * :partition - partition/group on the column(s) given
879
+ # * :window - base results on a previously specified named window
880
+ attr_reader :opts
881
+
882
+ # Set the options to the options given
883
+ def initialize(opts={})
884
+ @opts = opts
885
+ end
886
+
887
+ to_s_method :window_sql, '@opts'
888
+ end
889
+
890
+ # A WindowFunction is a grouping of a function with a window over which it operates.
891
+ class WindowFunction < Expression
892
+ # The function to use, should be an SQL::Function.
893
+ attr_reader :function
894
+
895
+ # The window to use, should be an SQL::Window.
896
+ attr_reader :window
897
+
898
+ # Set the function and window.
899
+ def initialize(function, window)
900
+ @function, @window = function, window
901
+ end
902
+
903
+ to_s_method :window_function_sql, '@function, @window'
904
+ end
798
905
  end
799
906
 
800
907
  # LiteralString is used to represent literal SQL expressions. A
801
908
  # LiteralString is copied verbatim into an SQL statement. Instances of
802
909
  # LiteralString can be created by calling String#lit.
803
- class LiteralString < ::String
910
+ class LiteralString
804
911
  include SQL::OrderMethods
805
912
  include SQL::ComplexExpressionMethods
806
913
  include SQL::BooleanMethods
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  MAJOR = 3
3
- MINOR = 1
3
+ MINOR = 2
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -3,6 +3,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
3
3
  unless defined?(ADO_DB)
4
4
  ADO_DB = Sequel.ado(:host => 'MY_SQL_SERVER', :database => 'MyDB', :user => 'my_usr', :password => 'my_pwd')
5
5
  end
6
+ INTEGRATION_DB = ADO_DB unless defined?(INTEGRATION_DB)
6
7
 
7
8
  context "An ADO dataset" do
8
9
  before(:each) do
@@ -4,6 +4,7 @@ unless defined?(FIREBIRD_DB)
4
4
  FIREBIRD_URL = 'firebird://sysdba:masterkey@localhost/reality_spec' unless defined? FIREBIRD_URL
5
5
  FIREBIRD_DB = Sequel.connect(ENV['SEQUEL_FB_SPEC_DB']||FIREBIRD_URL)
6
6
  end
7
+ INTEGRATION_DB = FIREBIRD_DB unless defined?(INTEGRATION_DB)
7
8
 
8
9
  def FIREBIRD_DB.sqls
9
10
  (@sqls ||= [])
@@ -3,6 +3,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
3
3
  unless defined?(INFORMIX_DB)
4
4
  INFORMIX_DB = Sequel.connect('informix://localhost/mydb')
5
5
  end
6
+ INTEGRATION_DB = INFORMIX_DB unless defined?(INTEGRATION_DB)
6
7
 
7
8
  if INFORMIX_DB.table_exists?(:test)
8
9
  INFORMIX_DB.drop_table :test
@@ -10,6 +10,7 @@ end
10
10
  unless defined?(MYSQL_SOCKET_FILE)
11
11
  MYSQL_SOCKET_FILE = '/tmp/mysql.sock'
12
12
  end
13
+ INTEGRATION_DB = MYSQL_DB unless defined?(INTEGRATION_DB)
13
14
 
14
15
  MYSQL_URI = URI.parse(MYSQL_DB.uri)
15
16
 
@@ -78,10 +79,10 @@ context "A MySQL database" do
78
79
  end
79
80
 
80
81
  specify "should correctly parse the schema" do
81
- @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
82
+ @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}]]
82
83
 
83
84
  Sequel.convert_tinyint_to_bool = false
84
- @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
85
+ @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}]]
85
86
  end
86
87
 
87
88
  specify "should accept and return tinyints as bools or integers when configured to do so" do
@@ -425,11 +426,11 @@ context "A MySQL database" do
425
426
  end
426
427
 
427
428
  specify "should have rename_column support keep existing options" do
428
- @db.create_table(:items){Integer :id, :null=>false, :default=>5}
429
+ @db.create_table(:items){String :id, :null=>false, :default=>'blah'}
429
430
  @db.alter_table(:items){rename_column :id, :nid}
430
- @db.sqls.should == ["CREATE TABLE items (id integer NOT NULL DEFAULT 5)", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id nid int(11) NOT NULL DEFAULT 5"]
431
+ @db.sqls.should == ["CREATE TABLE items (id varchar(255) NOT NULL DEFAULT 'blah')", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id nid varchar(255) NOT NULL DEFAULT 'blah'"]
431
432
  @db[:items].insert
432
- @db[:items].all.should == [{:nid=>5}]
433
+ @db[:items].all.should == [{:nid=>'blah'}]
433
434
  proc{@db[:items].insert(:nid=>nil)}.should raise_error(Sequel::DatabaseError)
434
435
  end
435
436
 
@@ -635,6 +636,21 @@ context "MySQL::Dataset#insert and related methods" do
635
636
  ]
636
637
  end
637
638
 
639
+ specify "#on_duplicate_key_update should work with regular inserts" do
640
+ MYSQL_DB.add_index :items, :name, :unique=>true
641
+ MYSQL_DB.sqls.clear
642
+ @d.insert(:name => 'abc', :value => 1)
643
+ @d.on_duplicate_key_update(:name, :value => 6).insert(:name => 'abc', :value => 1)
644
+ @d.on_duplicate_key_update(:name, :value => 6).insert(:name => 'def', :value => 2)
645
+
646
+ MYSQL_DB.sqls.length.should == 3
647
+ MYSQL_DB.sqls[0].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('abc'|1), (1|'abc')\)\z/
648
+ MYSQL_DB.sqls[1].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('abc'|1), (1|'abc')\) ON DUPLICATE KEY UPDATE name=VALUES\(name\), value=6\z/
649
+ MYSQL_DB.sqls[2].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('def'|2), (2|'def')\) ON DUPLICATE KEY UPDATE name=VALUES\(name\), value=6\z/
650
+
651
+ @d.all.should == [{:name => 'abc', :value => 6}, {:name => 'def', :value => 2}]
652
+ end
653
+
638
654
  specify "#multi_insert should insert multiple records in a single statement" do
639
655
  @d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
640
656
 
@@ -727,11 +743,10 @@ context "MySQL::Dataset#insert and related methods" do
727
743
  end
728
744
 
729
745
  specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and ALL columns when no args given" do
730
- @d.on_duplicate_key_update.import([:name,:value],
731
- [['abc', 1], ['def',2]]
732
- )
746
+ @d.on_duplicate_key_update.import([:name,:value], [['abc', 1], ['def',2]])
733
747
 
734
748
  MYSQL_DB.sqls.should == [
749
+ "SELECT * FROM items LIMIT 1",
735
750
  SQL_BEGIN,
736
751
  "INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2) ON DUPLICATE KEY UPDATE name=VALUES(name), value=VALUES(value)",
737
752
  SQL_COMMIT