sequel_core 1.5.1 → 2.0.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 (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,113 +1,127 @@
1
- require 'dbi'
2
-
3
- module Sequel
4
- module DBI
5
- class Database < Sequel::Database
6
- set_adapter_scheme :dbi
7
-
8
- DBI_ADAPTERS = {
9
- :ado => "ADO",
10
- :db2 => "DB2",
11
- :frontbase => "FrontBase",
12
- :interbase => "InterBase",
13
- :msql => "Msql",
14
- :mysql => "Mysql",
15
- :odbc => "ODBC",
16
- :oracle => "Oracle",
17
- :pg => "Pg",
18
- :proxy => "Proxy",
19
- :sqlite => "SQLite",
20
- :sqlrelay => "SQLRelay"
21
- }
22
-
23
- # Converts a uri to an options hash. These options are then passed
24
- # to a newly created database object.
25
- def self.uri_to_options(uri)
26
- database = (uri.path =~ /\/(.*)/) && ($1)
27
- if uri.scheme =~ /dbi-(.+)/
28
- adapter = DBI_ADAPTERS[$1.to_sym] || $1
29
- database = "#{adapter}:#{database}"
30
- end
31
- {
32
- :user => uri.user,
33
- :password => uri.password,
34
- :host => uri.host,
35
- :port => uri.port,
36
- :database => database
37
- }
38
- end
39
-
40
-
41
- def connect
42
- dbname = @opts[:database]
43
- dbname = 'DBI:' + dbname unless dbname =~ /^DBI:/
44
- ::DBI.connect(dbname, @opts[:user], @opts[:password])
45
- end
46
-
47
- def disconnect
48
- @pool.disconnect {|c| c.disconnect}
49
- end
50
-
51
- def dataset(opts = nil)
52
- DBI::Dataset.new(self, opts)
53
- end
54
-
55
- def execute(sql)
56
- @logger.info(sql) if @logger
57
- @pool.hold do |conn|
58
- conn.execute(sql)
59
- end
60
- end
61
-
62
- def do(sql)
63
- @logger.info(sql) if @logger
64
- @pool.hold do |conn|
65
- conn.do(sql)
66
- end
67
- end
68
- end
69
-
70
- class Dataset < Sequel::Dataset
71
- def literal(v)
72
- case v
73
- when Time
74
- literal(v.iso8601)
75
- else
76
- super
77
- end
78
- end
79
-
80
- def fetch_rows(sql, &block)
81
- @db.synchronize do
82
- s = @db.execute sql
83
- begin
84
- @columns = s.column_names.map {|c| c.to_sym}
85
- s.fetch {|r| yield hash_row(s, r)}
86
- ensure
87
- s.finish rescue nil
88
- end
89
- end
90
- self
91
- end
92
-
93
- def hash_row(stmt, row)
94
- @columns.inject({}) do |m, c|
95
- m[c] = row.shift
96
- m
97
- end
98
- end
99
-
100
- def insert(*values)
101
- @db.do insert_sql(*values)
102
- end
103
-
104
- def update(*args, &block)
105
- @db.do update_sql(*args, &block)
106
- end
107
-
108
- def delete(opts = nil)
109
- @db.do delete_sql(opts)
110
- end
111
- end
112
- end
113
- end
1
+ require 'dbi'
2
+
3
+ module Sequel
4
+ module DBI
5
+ class Database < Sequel::Database
6
+ attr_writer :lowercase
7
+
8
+ set_adapter_scheme :dbi
9
+
10
+ DBI_ADAPTERS = {
11
+ :ado => "ADO",
12
+ :db2 => "DB2",
13
+ :frontbase => "FrontBase",
14
+ :interbase => "InterBase",
15
+ :msql => "Msql",
16
+ :mysql => "Mysql",
17
+ :odbc => "ODBC",
18
+ :oracle => "Oracle",
19
+ :pg => "pg",
20
+ :proxy => "Proxy",
21
+ :sqlite => "SQLite",
22
+ :sqlrelay => "SQLRelay"
23
+ }
24
+
25
+ # Converts a uri to an options hash. These options are then passed
26
+ # to a newly created database object.
27
+ def self.uri_to_options(uri)
28
+ database = (m = /\/(.*)/.match(uri.path)) && (m[1])
29
+ if m = /dbi-(.+)/.match(uri.scheme)
30
+ adapter = DBI_ADAPTERS[m[1].to_sym] || m[1]
31
+ database = "#{adapter}:dbname=#{database}"
32
+ end
33
+ {
34
+ :user => uri.user,
35
+ :password => uri.password,
36
+ :host => uri.host,
37
+ :port => uri.port,
38
+ :database => database
39
+ }
40
+ end
41
+
42
+
43
+ def connect
44
+ dbname = @opts[:database]
45
+ if dbname !~ /^DBI:/ then
46
+ dbname = "DBI:#{dbname}"
47
+ [:host, :port].each{|sym| dbname += ";#{sym}=#{@opts[sym]}" unless @opts[sym].blank?}
48
+ end
49
+ ::DBI.connect(dbname, @opts[:user], @opts[:password])
50
+ end
51
+
52
+ def disconnect
53
+ @pool.disconnect {|c| c.disconnect}
54
+ end
55
+
56
+ def dataset(opts = nil)
57
+ DBI::Dataset.new(self, opts)
58
+ end
59
+
60
+ def execute(sql)
61
+ log_info(sql)
62
+ @pool.hold do |conn|
63
+ conn.execute(sql)
64
+ end
65
+ end
66
+
67
+ def do(sql)
68
+ log_info(sql)
69
+ @pool.hold do |conn|
70
+ conn.do(sql)
71
+ end
72
+ end
73
+
74
+ # Converts all column names to lowercase
75
+ def lowercase
76
+ @lowercase ||= false
77
+ end
78
+ end
79
+
80
+ class Dataset < Sequel::Dataset
81
+ def literal(v)
82
+ case v
83
+ when Time
84
+ literal(v.iso8601)
85
+ when Date, DateTime
86
+ literal(v.to_s)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ def fetch_rows(sql, &block)
93
+ @db.synchronize do
94
+ s = @db.execute sql
95
+ begin
96
+ @columns = s.column_names.map do |c|
97
+ @db.lowercase ? c.downcase.to_sym : c.to_sym
98
+ end
99
+ s.fetch {|r| yield hash_row(s, r)}
100
+ ensure
101
+ s.finish rescue nil
102
+ end
103
+ end
104
+ self
105
+ end
106
+
107
+ def hash_row(stmt, row)
108
+ @columns.inject({}) do |m, c|
109
+ m[c] = row.shift
110
+ m
111
+ end
112
+ end
113
+
114
+ def insert(*values)
115
+ @db.do insert_sql(*values)
116
+ end
117
+
118
+ def update(*args, &block)
119
+ @db.do update_sql(*args, &block)
120
+ end
121
+
122
+ def delete(opts = nil)
123
+ @db.do delete_sql(opts)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -25,13 +25,13 @@ module Sequel
25
25
 
26
26
  # Returns number of rows affected
27
27
  def execute(sql)
28
- @logger.info(sql) if @logger
28
+ log_info(sql)
29
29
  @pool.hold {|c| c.immediate(sql)}
30
30
  end
31
31
  alias_method :do, :execute
32
32
 
33
33
  def query(sql, &block)
34
- @logger.info(sql) if @logger
34
+ log_info(sql)
35
35
  @pool.hold {|c| block[c.cursor(sql)]}
36
36
  end
37
37
  end
@@ -41,6 +41,8 @@ module Sequel
41
41
  case v
42
42
  when Time
43
43
  literal(v.iso8601)
44
+ when Date, DateTime
45
+ literal(v.to_s)
44
46
  else
45
47
  super
46
48
  end
@@ -38,7 +38,7 @@ module Sequel
38
38
  end
39
39
 
40
40
  def execute_and_forget(sql)
41
- @logger.info(sql) if @logger
41
+ log_info(sql)
42
42
  @pool.hold do |conn|
43
43
  stmt = conn.createStatement
44
44
  begin
@@ -50,7 +50,7 @@ module Sequel
50
50
  end
51
51
 
52
52
  def execute(sql)
53
- @logger.info(sql) if @logger
53
+ log_info(sql)
54
54
  @pool.hold do |conn|
55
55
  stmt = conn.createStatement
56
56
  begin
@@ -67,6 +67,8 @@ module Sequel
67
67
  case v
68
68
  when Time
69
69
  literal(v.iso8601)
70
+ when Date, DateTime
71
+ literal(v.to_s)
70
72
  else
71
73
  super
72
74
  end
@@ -105,4 +107,4 @@ module Sequel
105
107
  end
106
108
  end
107
109
  end
108
- end
110
+ end
@@ -4,7 +4,7 @@ require 'mysql'
4
4
  class Mysql::Result
5
5
  MYSQL_TYPES = {
6
6
  0 => :to_d, # MYSQL_TYPE_DECIMAL
7
- 1 => :to_i, # MYSQL_TYPE_TINY
7
+ #1 => :to_i, # MYSQL_TYPE_TINY
8
8
  2 => :to_i, # MYSQL_TYPE_SHORT
9
9
  3 => :to_i, # MYSQL_TYPE_LONG
10
10
  4 => :to_f, # MYSQL_TYPE_FLOAT
@@ -33,7 +33,17 @@ class Mysql::Result
33
33
  }
34
34
 
35
35
  def convert_type(v, type)
36
- v ? ((t = MYSQL_TYPES[type]) ? v.send(t) : v) : nil
36
+ if v
37
+ if type == 1
38
+ # We special case tinyint here to avoid adding
39
+ # a method to an ancestor of Fixnum
40
+ v.to_i == 0 ? false : true
41
+ else
42
+ (t = MYSQL_TYPES[type]) ? v.send(t) : v
43
+ end
44
+ else
45
+ nil
46
+ end
37
47
  end
38
48
 
39
49
  def columns(with_table = nil)
@@ -55,7 +65,6 @@ class Mysql::Result
55
65
  row[i] = v.send(t)
56
66
  end
57
67
  end
58
- row.keys = c
59
68
  yield row
60
69
  end
61
70
  end
@@ -80,8 +89,8 @@ module Sequel
80
89
  if conn.respond_to?(:server_version)
81
90
  pool.hold {|c| c.server_version}
82
91
  else
83
- get(:version[]) =~ /(\d+)\.(\d+)\.(\d+)/
84
- ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
92
+ m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
93
+ (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
85
94
  end
86
95
  end
87
96
  end
@@ -114,6 +123,8 @@ module Sequel
114
123
  if encoding = @opts[:encoding] || @opts[:charset]
115
124
  conn.query("set character_set_connection = '#{encoding}'")
116
125
  conn.query("set character_set_client = '#{encoding}'")
126
+ conn.query("set character_set_database = '#{encoding}'")
127
+ conn.query("set character_set_server = '#{encoding}'")
117
128
  conn.query("set character_set_results = '#{encoding}'")
118
129
  end
119
130
  conn.reconnect = true
@@ -135,10 +146,14 @@ module Sequel
135
146
  end
136
147
 
137
148
  def execute(sql, &block)
138
- @logger.info(sql) if @logger
139
- @pool.hold do |conn|
140
- conn.query(sql)
141
- block[conn] if block
149
+ begin
150
+ log_info(sql)
151
+ @pool.hold do |conn|
152
+ conn.query(sql)
153
+ block[conn] if block
154
+ end
155
+ rescue Mysql::Error => e
156
+ raise Error.new(e.message)
142
157
  end
143
158
  end
144
159
 
@@ -175,7 +190,7 @@ module Sequel
175
190
  sql = "#{literal(column[:name].to_sym)} #{TYPES[column[:type]]}"
176
191
  column[:size] ||= 255 if column[:type] == :varchar
177
192
  elements = column[:size] || column[:elements]
178
- sql << "(#{literal(elements)})" if elements
193
+ sql << literal(Array(elements)) if elements
179
194
  sql << UNSIGNED if column[:unsigned]
180
195
  sql << UNIQUE if column[:unique]
181
196
  sql << NOT_NULL if column[:null] == false
@@ -185,7 +200,7 @@ module Sequel
185
200
  sql << " #{auto_increment_sql}" if column[:auto_increment]
186
201
  if column[:table]
187
202
  sql << ", FOREIGN KEY (#{literal(column[:name].to_sym)}) REFERENCES #{column[:table]}"
188
- sql << "(#{literal(column[:key])})" if column[:key]
203
+ sql << literal(Array(column[:key])) if column[:key]
189
204
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
190
205
  end
191
206
  sql
@@ -196,13 +211,13 @@ module Sequel
196
211
  unique = "UNIQUE " if index[:unique]
197
212
  case index[:type]
198
213
  when :full_text
199
- "CREATE FULLTEXT INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
214
+ "CREATE FULLTEXT INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
200
215
  when :spatial
201
- "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
216
+ "CREATE SPATIAL INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
202
217
  when nil
203
- "CREATE #{unique}INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
218
+ "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
204
219
  else
205
- "CREATE #{unique}INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])}) USING #{index[:type]}"
220
+ "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])} USING #{index[:type]}"
206
221
  end
207
222
  end
208
223
 
@@ -212,16 +227,20 @@ module Sequel
212
227
  if @transactions.include? Thread.current
213
228
  return yield(conn)
214
229
  end
230
+ log_info(SQL_BEGIN)
215
231
  conn.query(SQL_BEGIN)
216
232
  begin
217
233
  @transactions << Thread.current
218
- result = yield(conn)
219
- conn.query(SQL_COMMIT)
220
- result
221
- rescue => e
234
+ yield(conn)
235
+ rescue ::Exception => e
236
+ log_info(SQL_ROLLBACK)
222
237
  conn.query(SQL_ROLLBACK)
223
- raise e unless Error::Rollback === e
238
+ raise (Mysql::Error === e ? Error.new(e.message) : e) unless Error::Rollback === e
224
239
  ensure
240
+ unless e
241
+ log_info(SQL_COMMIT)
242
+ conn.query(SQL_COMMIT)
243
+ end
225
244
  @transactions.delete(Thread.current)
226
245
  end
227
246
  end
@@ -231,12 +250,37 @@ module Sequel
231
250
  def use(db_name)
232
251
  disconnect
233
252
  @opts[:database] = db_name if self << "USE #{db_name}"
253
+ @schemas = nil
234
254
  self
235
255
  end
256
+
257
+ private
258
+ def connection_pool_default_options
259
+ super.merge(:pool_reuse_connections=>:last_resort, :pool_convert_exceptions=>false)
260
+ end
261
+
262
+ def schema_ds_dataset
263
+ ds = schema_utility_dataset.clone
264
+ ds.quote_identifiers = true
265
+ ds
266
+ end
267
+
268
+ def schema_ds_filter(table_name, opts)
269
+ filt = super
270
+ # Restrict it to the given or current database, unless specifically requesting :database = nil
271
+ filt = SQL::ComplexExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || self.opts[:database]}) if opts[:database] || !opts.include?(:database)
272
+ filt
273
+ end
274
+
275
+ def schema_ds_join(table_name, opts)
276
+ [:information_schema__columns, {:table_schema => :table_schema, :table_name => :table_name}, :c]
277
+ end
236
278
  end
237
279
 
238
280
  class Dataset < Sequel::Dataset
239
- def quote_column_ref(c); "`#{c}`"; end
281
+ def quoted_identifier(c)
282
+ "`#{c}`"
283
+ end
240
284
 
241
285
  TRUE = '1'
242
286
  FALSE = '0'
@@ -268,7 +312,7 @@ module Sequel
268
312
  when LiteralString
269
313
  v
270
314
  when String
271
- "'#{v.gsub(/'|\\/, '\&\&')}'"
315
+ "'#{::Mysql.quote(v)}'"
272
316
  when true
273
317
  TRUE
274
318
  when false
@@ -293,27 +337,44 @@ module Sequel
293
337
  #
294
338
  # === Example
295
339
  # @ds = MYSQL_DB[:nodes]
296
- # @ds.join_expr(:natural_left_outer, :nodes)
297
- # # 'NATURAL LEFT OUTER JOIN nodes'
298
- #
299
- def join_expr(type, table, expr = nil, options = {})
300
- raise Error::InvalidJoinType, "Invalid join type: #{type}" unless join_type = JOIN_TYPES[type || :inner]
301
-
302
- server_version = @opts[:server_version] ||= @db.server_version
303
- type = :inner if type == :cross && !expr.nil?
304
-
305
- if (server_version >= 50014) && /\Anatural|cross|straight\z/.match(type.to_s)
306
- table = "( #{literal(table)} )" if table.is_a?(Array)
307
- "#{join_type} #{table}"
340
+ # @ds.join_table(:natural_left_outer, :nodes)
341
+ # # join SQL is 'NATURAL LEFT OUTER JOIN nodes'
342
+ def join_table(type, table, expr=nil, table_alias=nil)
343
+ raise(Error::InvalidJoinType, "Invalid join type: #{type}") unless join_type = JOIN_TYPES[type || :inner]
344
+
345
+ server_version = (@opts[:server_version] ||= @db.server_version)
346
+ type = :inner if (type == :cross) && !expr.nil?
347
+ return super(type, table, expr, table_alias) unless (server_version >= 50014) && /natural|cross|straight/.match(type.to_s)
348
+
349
+ table = if Array === table
350
+ "( #{table.collect{|t| quote_identifier(t)}.join(', ')} )"
308
351
  else
309
- super
352
+ quote_identifier(table)
310
353
  end
354
+ clone(:join => "#{@opts[:join]} #{join_type} #{table}")
311
355
  end
312
356
 
313
357
  def insert_default_values_sql
314
358
  "INSERT INTO #{source_list(@opts[:from])} () VALUES ()"
315
359
  end
316
360
 
361
+ def complex_expression_sql(op, args)
362
+ case op
363
+ when :~, :'!~'
364
+ "#{'NOT ' if op == :'!~'}(#{literal(args.at(0))} REGEXP BINARY #{literal(args.at(1))})"
365
+ when :'~*', :'!~*'
366
+ "#{'NOT ' if op == :'!~*'}(#{literal(args.at(0))} REGEXP #{literal(args.at(1))})"
367
+ when :'||'
368
+ if args.length > 1
369
+ "CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
370
+ else
371
+ literal(args.at(0))
372
+ end
373
+ else
374
+ super(op, args)
375
+ end
376
+ end
377
+
317
378
  def match_expr(l, r)
318
379
  case r
319
380
  when Regexp
@@ -352,7 +413,7 @@ module Sequel
352
413
  end
353
414
 
354
415
  if where = opts[:where]
355
- sql << " WHERE #{where}"
416
+ sql << " WHERE #{literal(where)}"
356
417
  end
357
418
 
358
419
  if group = opts[:group]
@@ -360,7 +421,7 @@ module Sequel
360
421
  end
361
422
 
362
423
  if having = opts[:having]
363
- sql << " HAVING #{having}"
424
+ sql << " HAVING #{literal(having)}"
364
425
  end
365
426
 
366
427
  if order = opts[:order]
@@ -391,13 +452,22 @@ module Sequel
391
452
 
392
453
  def full_text_search(cols, terms, opts = {})
393
454
  mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
394
- filter("MATCH (#{literal(cols)}) AGAINST (#{literal(terms)}#{mode})")
455
+ s = if Array === terms
456
+ if mode.blank?
457
+ "MATCH #{literal(Array(cols))} AGAINST #{literal(terms)}"
458
+ else
459
+ "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)[1...-1]}#{mode})"
460
+ end
461
+ else
462
+ "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)}#{mode})"
463
+ end
464
+ filter(s)
395
465
  end
396
466
 
397
467
  # MySQL allows HAVING clause on ungrouped datasets.
398
468
  def having(*cond, &block)
399
469
  @opts[:having] = {}
400
- filter(*cond, &block)
470
+ x = filter(*cond, &block)
401
471
  end
402
472
 
403
473
  # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
@@ -431,12 +501,8 @@ module Sequel
431
501
  when Array
432
502
  if values.empty?
433
503
  "REPLACE INTO #{from} DEFAULT VALUES"
434
- elsif values.keys
435
- fl = values.keys.map {|f| literal(f.is_a?(String) ? f.to_sym : f)}
436
- vl = values.values.map {|v| literal(v)}
437
- "REPLACE INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
438
504
  else
439
- "REPLACE INTO #{from} VALUES (#{literal(values)})"
505
+ "REPLACE INTO #{from} VALUES #{literal(values)}"
440
506
  end
441
507
  when Hash
442
508
  if values.empty?
@@ -498,8 +564,8 @@ module Sequel
498
564
  end
499
565
 
500
566
  def multi_insert_sql(columns, values)
501
- columns = literal(columns)
502
- values = values.map {|r| "(#{literal(r)})"}.join(COMMA_SEPARATOR)
567
+ columns = column_list(columns)
568
+ values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
503
569
  ["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
504
570
  end
505
571
  end
@@ -46,14 +46,14 @@ module Sequel
46
46
  # fetch_rows method source code for an example of how to drop
47
47
  # the statements.
48
48
  def execute(sql)
49
- @logger.info(sql) if @logger
49
+ log_info(sql)
50
50
  @pool.hold do |conn|
51
51
  conn.run(sql)
52
52
  end
53
53
  end
54
54
 
55
55
  def do(sql)
56
- @logger.info(sql) if @logger
56
+ log_info(sql)
57
57
  @pool.hold do |conn|
58
58
  conn.do(sql)
59
59
  end
@@ -74,12 +74,10 @@ module Sequel
74
74
  BOOL_TRUE
75
75
  when false
76
76
  BOOL_FALSE
77
- when Time
77
+ when Time, DateTime
78
78
  formatted = v.strftime(ODBC_TIMESTAMP_FORMAT)
79
- if v.usec >= 1000
80
- msec = ( v.usec.to_f / 1000 ).round
81
- formatted.insert ODBC_TIMESTAMP_AFTER_SECONDS, ".#{msec}"
82
- end
79
+ usec = (Time === v ? v.usec : (v.sec_fraction * 86400000000))
80
+ formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(usec.to_f/1000).round}") if usec >= 1000
83
81
  formatted
84
82
  when Date
85
83
  v.strftime(ODBC_DATE_FORMAT)
@@ -58,7 +58,7 @@ module Sequel
58
58
  end
59
59
 
60
60
  if where = opts[:where]
61
- sql << " WHERE #{where}"
61
+ sql << " WHERE #{literal(where)}"
62
62
  end
63
63
 
64
64
  if group = opts[:group]
@@ -70,7 +70,7 @@ module Sequel
70
70
  end
71
71
 
72
72
  if having = opts[:having]
73
- sql << " HAVING #{having}"
73
+ sql << " HAVING #{literal(having)}"
74
74
  end
75
75
 
76
76
  if union = opts[:union]
@@ -91,7 +91,16 @@ module Sequel
91
91
  def full_text_search(cols, terms, opts = {})
92
92
  filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
93
93
  end
94
+
95
+ def complex_expression_sql(op, args)
96
+ case op
97
+ when :'||'
98
+ super(:+, args)
99
+ else
100
+ super(op, args)
101
+ end
102
+ end
94
103
  end
95
104
  end
96
105
  end
97
- end
106
+ end