sequel 2.2.0 → 2.3.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 (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