sequel 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,199 @@
1
+ require 'sequel_core/adapters/shared/postgres'
2
+
3
+ begin
4
+ require 'pg'
5
+ rescue LoadError => e
6
+ begin
7
+ require 'postgres'
8
+ class PGconn
9
+ unless method_defined?(:escape_string)
10
+ if self.respond_to?(:escape)
11
+ def escape_string(str)
12
+ self.class.escape(str)
13
+ end
14
+ else
15
+ def escape_string(obj)
16
+ raise Sequel::Error, "string escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
17
+ end
18
+ end
19
+ end
20
+ unless method_defined?(:escape_bytea)
21
+ if self.respond_to?(:escape_bytea)
22
+ def escape_bytea(obj)
23
+ self.class.escape_bytea(obj)
24
+ end
25
+ else
26
+ begin
27
+ require 'postgres-pr/typeconv/conv'
28
+ require 'postgres-pr/typeconv/bytea'
29
+ extend Postgres::Conversion
30
+ def escape_bytea(obj)
31
+ self.class.encode_bytea(obj)
32
+ end
33
+ metaalias :unescape_bytea, :decode_bytea
34
+ rescue
35
+ def escape_bytea(obj)
36
+ raise Sequel::Error, "bytea escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
37
+ end
38
+ def self.unescape_bytea(obj)
39
+ raise Sequel::Error, "bytea unescaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
40
+ end
41
+ end
42
+ end
43
+ end
44
+ alias_method :finish, :close unless method_defined?(:finish)
45
+ end
46
+ class PGresult
47
+ alias_method :nfields, :num_fields unless method_defined?(:nfields)
48
+ alias_method :ntuples, :num_tuples unless method_defined?(:ntuples)
49
+ alias_method :ftype, :type unless method_defined?(:ftype)
50
+ alias_method :fname, :fieldname unless method_defined?(:fname)
51
+ alias_method :cmd_tuples, :cmdtuples unless method_defined?(:cmd_tuples)
52
+ end
53
+ rescue LoadError
54
+ raise e
55
+ end
56
+ end
57
+
58
+ module Sequel
59
+ module Postgres
60
+ CONVERTED_EXCEPTIONS << PGError
61
+ PG_TYPES = {
62
+ 16 => lambda{ |s| Postgres.string_to_bool(s) }, # boolean
63
+ 17 => lambda{ |s| Adapter.unescape_bytea(s).to_blob }, # bytea
64
+ 20 => lambda{ |s| s.to_i }, # int8
65
+ 21 => lambda{ |s| s.to_i }, # int2
66
+ 22 => lambda{ |s| s.to_i }, # int2vector
67
+ 23 => lambda{ |s| s.to_i }, # int4
68
+ 26 => lambda{ |s| s.to_i }, # oid
69
+ 700 => lambda{ |s| s.to_f }, # float4
70
+ 701 => lambda{ |s| s.to_f }, # float8
71
+ 790 => lambda{ |s| s.to_d }, # money
72
+ 1082 => lambda{ |s| s.to_date }, # date
73
+ 1083 => lambda{ |s| s.to_time }, # time without time zone
74
+ 1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
75
+ 1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
76
+ 1186 => lambda{ |s| s.to_i }, # interval
77
+ 1266 => lambda{ |s| s.to_time }, # time with time zone
78
+ 1700 => lambda{ |s| s.to_d }, # numeric
79
+ }
80
+
81
+ def self.string_to_bool(s)
82
+ if(s.blank?)
83
+ nil
84
+ elsif(s.downcase == 't' || s.downcase == 'true')
85
+ true
86
+ else
87
+ false
88
+ end
89
+ end
90
+
91
+ class Adapter < ::PGconn
92
+ include Sequel::Postgres::AdapterMethods
93
+ self.translate_results = false if respond_to?(:translate_results=)
94
+
95
+ def connected?
96
+ status == Adapter::CONNECTION_OK
97
+ end
98
+
99
+ def execute(sql, &block)
100
+ q = nil
101
+ begin
102
+ q = exec(sql)
103
+ rescue PGError => e
104
+ unless connected?
105
+ reset
106
+ q = exec(sql)
107
+ else
108
+ raise e
109
+ end
110
+ end
111
+ begin
112
+ block ? block[q] : q.cmd_tuples
113
+ ensure
114
+ q.clear
115
+ end
116
+ end
117
+
118
+ def result_set_values(r, *vals)
119
+ return if r.nil? || (r.ntuples == 0)
120
+ case vals.length
121
+ when 1
122
+ r.getvalue(0, vals.first)
123
+ else
124
+ vals.collect{|col| r.getvalue(0, col)}
125
+ end
126
+ end
127
+ end
128
+
129
+ class Database < Sequel::Database
130
+ include Sequel::Postgres::DatabaseMethods
131
+
132
+ set_adapter_scheme :postgres
133
+
134
+ def connect
135
+ conn = Adapter.connect(
136
+ @opts[:host] || 'localhost',
137
+ @opts[:port] || 5432,
138
+ '', '',
139
+ @opts[:database],
140
+ @opts[:user],
141
+ @opts[:password]
142
+ )
143
+ if encoding = @opts[:encoding] || @opts[:charset]
144
+ conn.set_client_encoding(encoding)
145
+ end
146
+ conn
147
+ end
148
+
149
+ def dataset(opts = nil)
150
+ Postgres::Dataset.new(self, opts)
151
+ end
152
+
153
+ def disconnect
154
+ @pool.disconnect {|c| c.finish}
155
+ end
156
+
157
+ def execute(sql, &block)
158
+ begin
159
+ log_info(sql)
160
+ @pool.hold {|conn| conn.execute(sql, &block)}
161
+ rescue => e
162
+ log_info(e.message)
163
+ raise convert_pgerror(e)
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ # PostgreSQL doesn't need the pool to convert exceptions, either.
170
+ def connection_pool_default_options
171
+ super.merge(:pool_convert_exceptions=>false)
172
+ end
173
+ end
174
+
175
+ class Dataset < Sequel::Dataset
176
+ include Sequel::Postgres::DatasetMethods
177
+
178
+ def fetch_rows(sql, &block)
179
+ @columns = []
180
+ @db.execute(sql) do |res|
181
+ (0...res.ntuples).each do |recnum|
182
+ converted_rec = {}
183
+ (0...res.nfields).each do |fieldnum|
184
+ fieldsym = res.fname(fieldnum).to_sym
185
+ @columns << fieldsym
186
+ converted_rec[fieldsym] = if value = res.getvalue(recnum,fieldnum)
187
+ (PG_TYPES[res.ftype(fieldnum)] || lambda{|s| s.to_s}).call(value)
188
+ else
189
+ value
190
+ end
191
+ end
192
+ yield converted_rec
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+
@@ -0,0 +1,275 @@
1
+ module Sequel
2
+ module MySQL
3
+ module DatabaseMethods
4
+ AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
5
+ NOT_NULL = Sequel::Schema::SQL::NOT_NULL
6
+ NULL = Sequel::Schema::SQL::NULL
7
+ PRIMARY_KEY = Sequel::Schema::SQL::PRIMARY_KEY
8
+ SQL_BEGIN = Sequel::Database::SQL_BEGIN
9
+ SQL_COMMIT = Sequel::Database::SQL_COMMIT
10
+ SQL_ROLLBACK = Sequel::Database::SQL_ROLLBACK
11
+ TYPES = Sequel::Schema::SQL::TYPES
12
+ UNIQUE = Sequel::Schema::SQL::UNIQUE
13
+ UNSIGNED = Sequel::Schema::SQL::UNSIGNED
14
+
15
+ def alter_table_sql(table, op)
16
+ type = type_literal(op[:type])
17
+ type << '(255)' if type == 'varchar'
18
+ case op[:op]
19
+ when :rename_column
20
+ "ALTER TABLE #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:new_name])} #{type}"
21
+ when :set_column_type
22
+ "ALTER TABLE #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:name])} #{type}"
23
+ when :drop_index
24
+ "DROP INDEX #{default_index_name(table, op[:columns])} ON #{table}"
25
+ else
26
+ super(table, op)
27
+ end
28
+ end
29
+
30
+ def auto_increment_sql
31
+ AUTO_INCREMENT
32
+ end
33
+
34
+ def column_definition_sql(column)
35
+ if column[:type] == :check
36
+ return constraint_definition_sql(column)
37
+ end
38
+ sql = "#{literal(column[:name].to_sym)} #{TYPES[column[:type]]}"
39
+ column[:size] ||= 255 if column[:type] == :varchar
40
+ elements = column[:size] || column[:elements]
41
+ sql << literal(Array(elements)) if elements
42
+ sql << UNSIGNED if column[:unsigned]
43
+ sql << UNIQUE if column[:unique]
44
+ sql << NOT_NULL if column[:null] == false
45
+ sql << NULL if column[:null] == true
46
+ sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
47
+ sql << PRIMARY_KEY if column[:primary_key]
48
+ sql << " #{auto_increment_sql}" if column[:auto_increment]
49
+ if column[:table]
50
+ sql << ", FOREIGN KEY (#{literal(column[:name].to_sym)}) REFERENCES #{column[:table]}"
51
+ sql << literal(Array(column[:key])) if column[:key]
52
+ sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
53
+ end
54
+ sql
55
+ end
56
+
57
+ def index_definition_sql(table_name, index)
58
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
59
+ unique = "UNIQUE " if index[:unique]
60
+ case index[:type]
61
+ when :full_text
62
+ "CREATE FULLTEXT INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
63
+ when :spatial
64
+ "CREATE SPATIAL INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
65
+ when nil
66
+ "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
67
+ else
68
+ "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])} USING #{index[:type]}"
69
+ end
70
+ end
71
+
72
+ def serial_primary_key_options
73
+ {:primary_key => true, :type => :integer, :auto_increment => true}
74
+ end
75
+
76
+ def server_version
77
+ m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
78
+ @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
79
+ end
80
+
81
+ # Changes the database in use by issuing a USE statement.
82
+ def use(db_name)
83
+ disconnect
84
+ @opts[:database] = db_name if self << "USE #{db_name}"
85
+ @schemas = nil
86
+ self
87
+ end
88
+
89
+ private
90
+
91
+ def schema_ds_dataset
92
+ ds = schema_utility_dataset.clone
93
+ ds.quote_identifiers = true
94
+ ds
95
+ end
96
+
97
+ def schema_ds_filter(table_name, opts)
98
+ filt = super
99
+ # Restrict it to the given or current database, unless specifically requesting :database = nil
100
+ filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || database_name}) if opts[:database] || !opts.include?(:database)
101
+ filt
102
+ end
103
+
104
+ def schema_ds_join(table_name, opts)
105
+ [:information_schema__columns, {:table_schema => :table_schema, :table_name => :table_name}, :c]
106
+ end
107
+ end
108
+
109
+ module DatasetMethods
110
+ BOOL_TRUE = '1'.freeze
111
+ BOOL_FALSE = '0'.freeze
112
+ COMMA_SEPARATOR = ', '.freeze
113
+
114
+ def complex_expression_sql(op, args)
115
+ case op
116
+ when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
117
+ "(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
118
+ when :'||'
119
+ if args.length > 1
120
+ "CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
121
+ else
122
+ literal(args.at(0))
123
+ end
124
+ else
125
+ super(op, args)
126
+ end
127
+ end
128
+
129
+ # MySQL supports ORDER and LIMIT clauses in DELETE statements.
130
+ def delete_sql(opts = nil)
131
+ sql = super
132
+ opts = opts ? @opts.merge(opts) : @opts
133
+
134
+ if order = opts[:order]
135
+ sql << " ORDER BY #{expression_list(order)}"
136
+ end
137
+ if limit = opts[:limit]
138
+ sql << " LIMIT #{limit}"
139
+ end
140
+
141
+ sql
142
+ end
143
+
144
+ def full_text_search(cols, terms, opts = {})
145
+ mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
146
+ s = if Array === terms
147
+ if mode.blank?
148
+ "MATCH #{literal(Array(cols))} AGAINST #{literal(terms)}"
149
+ else
150
+ "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)[1...-1]}#{mode})"
151
+ end
152
+ else
153
+ "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)}#{mode})"
154
+ end
155
+ filter(s)
156
+ end
157
+
158
+ # MySQL allows HAVING clause on ungrouped datasets.
159
+ def having(*cond, &block)
160
+ @opts[:having] = {}
161
+ x = filter(*cond, &block)
162
+ end
163
+
164
+ def insert_default_values_sql
165
+ "INSERT INTO #{source_list(@opts[:from])} () VALUES ()"
166
+ end
167
+
168
+ # Returns a join clause based on the specified join type
169
+ # and condition. MySQL's NATURAL join is 'semantically
170
+ # equivalent to a JOIN with a USING clause that names all
171
+ # columns that exist in both tables. The constraint
172
+ # expression may be nil, so join expression can accept two
173
+ # arguments.
174
+ #
175
+ # === Note
176
+ # Full outer joins (:full_outer) are not implemented in
177
+ # MySQL (as of v6.0), nor is there currently a work around
178
+ # implementation in Sequel. Straight joins with 'ON
179
+ # <condition>' are not yet implemented.
180
+ #
181
+ # === Example
182
+ # @ds = MYSQL_DB[:nodes]
183
+ # @ds.join_table(:natural_left_outer, :nodes)
184
+ # # join SQL is 'NATURAL LEFT OUTER JOIN nodes'
185
+ def join_table(type, table, expr=nil, table_alias=nil)
186
+ type = :inner if (type == :cross) && !expr.nil?
187
+ raise(Sequel::Error::InvalidJoinType, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
188
+ super(type, table, expr, table_alias)
189
+ end
190
+
191
+ def join_type_sql(join_type)
192
+ case join_type
193
+ when :straight then 'STRAIGHT_JOIN'
194
+ when :natural_inner then 'NATURAL LEFT JOIN'
195
+ else super
196
+ end
197
+ end
198
+
199
+ def literal(v)
200
+ case v
201
+ when true
202
+ BOOL_TRUE
203
+ when false
204
+ BOOL_FALSE
205
+ else
206
+ super
207
+ end
208
+ end
209
+
210
+ def multi_insert_sql(columns, values)
211
+ columns = column_list(columns)
212
+ values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
213
+ ["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
214
+ end
215
+
216
+ def quoted_identifier(c)
217
+ "`#{c}`"
218
+ end
219
+
220
+ def replace_sql(*values)
221
+ from = source_list(@opts[:from])
222
+ if values.empty?
223
+ "REPLACE INTO #{from} DEFAULT VALUES"
224
+ else
225
+ values = values[0] if values.size == 1
226
+
227
+ # if hash or array with keys we need to transform the values
228
+ if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
229
+ values = transform_save(values)
230
+ end
231
+
232
+ case values
233
+ when Array
234
+ if values.empty?
235
+ "REPLACE INTO #{from} DEFAULT VALUES"
236
+ else
237
+ "REPLACE INTO #{from} VALUES #{literal(values)}"
238
+ end
239
+ when Hash
240
+ if values.empty?
241
+ "REPLACE INTO #{from} DEFAULT VALUES"
242
+ else
243
+ fl, vl = [], []
244
+ values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
245
+ "REPLACE INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
246
+ end
247
+ when Dataset
248
+ "REPLACE INTO #{from} #{literal(values)}"
249
+ else
250
+ if values.respond_to?(:values)
251
+ replace_sql(values.values)
252
+ else
253
+ "REPLACE INTO #{from} VALUES (#{literal(values)})"
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
260
+ def update_sql(values, opts = nil)
261
+ sql = super
262
+ opts = opts ? @opts.merge(opts) : @opts
263
+
264
+ if order = opts[:order]
265
+ sql << " ORDER BY #{expression_list(order)}"
266
+ end
267
+ if limit = opts[:limit]
268
+ sql << " LIMIT #{limit}"
269
+ end
270
+
271
+ sql
272
+ end
273
+ end
274
+ end
275
+ end