sequel 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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