sequel 3.10.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. data/lib/sequel/dataset/convenience.rb +0 -326
@@ -28,8 +28,7 @@ module Sequel
28
28
  stmt = conn.createStatement
29
29
  begin
30
30
  sql = opts[:prepared] ? 'SELECT @@IDENTITY' : 'SELECT SCOPE_IDENTITY()'
31
- log_info(sql)
32
- rs = stmt.executeQuery(sql)
31
+ rs = log_yield(sql){stmt.executeQuery(sql)}
33
32
  rs.next
34
33
  rs.getInt(1)
35
34
  ensure
@@ -48,6 +48,12 @@ module Sequel
48
48
  end
49
49
  end
50
50
  end
51
+
52
+ # MySQL 5.1.12 JDBC adapter requires this to be true,
53
+ # and previous versions don't mind.
54
+ def requires_return_generated_keys?
55
+ true
56
+ end
51
57
  end
52
58
 
53
59
  # Dataset class for MySQL datasets accessed via JDBC.
@@ -63,6 +69,11 @@ module Sequel
63
69
  def replace(*args)
64
70
  execute_insert(replace_sql(*args))
65
71
  end
72
+
73
+ # MySQL on JDBC does provides an accurate number of rows matched.
74
+ def provides_accurate_rows_matched?
75
+ true
76
+ end
66
77
  end
67
78
  end
68
79
  end
@@ -20,27 +20,24 @@ module Sequel
20
20
 
21
21
  # Use JDBC connection's setAutoCommit to false to start transactions
22
22
  def begin_transaction(conn)
23
- log_info(TRANSACTION_BEGIN)
24
- conn.setAutoCommit(false)
23
+ log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
25
24
  conn
26
25
  end
27
26
 
28
27
  # Use JDBC connection's commit method to commit transactions
29
28
  def commit_transaction(conn)
30
- log_info(TRANSACTION_COMMIT)
31
- conn.commit
29
+ log_yield(TRANSACTION_COMMIT){conn.commit}
32
30
  end
33
31
 
34
32
  # Use JDBC connection's setAutoCommit to true to enable non-transactional behavior
35
33
  def remove_transaction(conn)
36
34
  conn.setAutoCommit(true) if conn
37
- super
35
+ @transactions.delete(Thread.current)
38
36
  end
39
37
 
40
38
  # Use JDBC connection's rollback method to rollback transactions
41
39
  def rollback_transaction(conn)
42
- log_info(TRANSACTION_ROLLBACK)
43
- conn.rollback
40
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback}
44
41
  end
45
42
  end
46
43
 
@@ -18,7 +18,7 @@ module Sequel
18
18
  method = block_given? ? :executeQuery : :execute
19
19
  stmt = createStatement
20
20
  begin
21
- rows = stmt.send(method, sql)
21
+ rows = @db.log_yield(sql){stmt.send(method, sql)}
22
22
  yield(rows) if block_given?
23
23
  rescue NativeException => e
24
24
  raise_error(e)
@@ -78,6 +78,13 @@ module Sequel
78
78
  def last_insert_id(conn, opts)
79
79
  insert_result(conn, opts[:table], opts[:values])
80
80
  end
81
+
82
+ # Override shared postgresql adapter method to actually log,
83
+ # since on JDBC the first argument is a statement and not a
84
+ # connection, so it wouldn't be logged otherwise.
85
+ def log_connection_execute(stmt, sql)
86
+ log_yield(sql){stmt.execute(sql)}
87
+ end
81
88
  end
82
89
 
83
90
  # Dataset subclass used for datasets that connect to PostgreSQL via JDBC.
@@ -32,6 +32,18 @@ module Sequel
32
32
  o = super
33
33
  uri == 'jdbc:sqlite::memory:' ? o.merge(:max_connections=>1) : o
34
34
  end
35
+
36
+ # Execute the connection pragmas on the connection.
37
+ def setup_connection(conn)
38
+ conn = super(conn)
39
+ begin
40
+ stmt = conn.createStatement
41
+ connection_pragmas.each{|s| log_yield(s){stmt.execute(s)}}
42
+ ensure
43
+ stmt.close if stmt
44
+ end
45
+ conn
46
+ end
35
47
  end
36
48
 
37
49
  # Dataset class for SQLite datasets accessed via JDBC.
@@ -85,6 +85,10 @@ module Sequel
85
85
  # inserted row.
86
86
  # * :charset - Same as :encoding (:encoding takes precendence)
87
87
  # * :compress - Set to false to not compress results from the server
88
+ # * :config_default_group - The default group to read from the in
89
+ # the MySQL config file.
90
+ # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
91
+ # option on the connection with the given value.
88
92
  # * :encoding - Set all the related character sets for this
89
93
  # connection (connection, client, database, server, and results).
90
94
  # * :socket - Use a unix socket file instead of connecting via TCP/IP.
@@ -93,8 +97,8 @@ module Sequel
93
97
  def connect(server)
94
98
  opts = server_opts(server)
95
99
  conn = Mysql.init
96
- # reads additional options defined under the [client] tag in the mysql configuration file
97
- conn.options(Mysql::READ_DEFAULT_GROUP, "client")
100
+ conn.options(Mysql::READ_DEFAULT_GROUP, opts[:config_default_group] || "client")
101
+ conn.options(Mysql::OPT_LOCAL_INFILE, opts[:config_local_infile]) if opts.has_key?(:config_local_infile)
98
102
  if encoding = opts[:encoding] || opts[:charset]
99
103
  # set charset _before_ the connect. using an option instead of "SET (NAMES|CHARACTER_SET_*)" works across reconnects
100
104
  conn.options(Mysql::SET_CHARSET_NAME, encoding)
@@ -153,8 +157,7 @@ module Sequel
153
157
  # yield the connection if a block is given.
154
158
  def _execute(conn, sql, opts)
155
159
  begin
156
- log_info(sql)
157
- r = conn.query(sql)
160
+ r = log_yield(sql){conn.query(sql)}
158
161
  if opts[:type] == :select
159
162
  yield r if r
160
163
  elsif block_given?
@@ -320,7 +323,13 @@ module Sequel
320
323
  def fetch_rows(sql, &block)
321
324
  execute(sql) do |r|
322
325
  i = -1
323
- cols = r.fetch_fields.map{|f| [output_identifier(f.name), MYSQL_TYPES[f.type], i+=1]}
326
+ cols = r.fetch_fields.map do |f|
327
+ # Pretend tinyint is another integer type if its length is not 1, to
328
+ # avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
329
+ # is set.
330
+ type_proc = f.type == 1 && f.length != 1 ? MYSQL_TYPES[2] : MYSQL_TYPES[f.type]
331
+ [output_identifier(f.name), type_proc, i+=1]
332
+ end
324
333
  @columns = cols.map{|c| c.first}
325
334
  if opts[:split_multiple_result_sets]
326
335
  s = []
@@ -45,10 +45,9 @@ module Sequel
45
45
  end
46
46
 
47
47
  def execute(sql, opts={})
48
- log_info(sql)
49
48
  synchronize(opts[:server]) do |conn|
50
49
  begin
51
- r = conn.run(sql)
50
+ r = log_yield(sql){conn.run(sql)}
52
51
  yield(r) if block_given?
53
52
  rescue ::ODBC::Error, ArgumentError => e
54
53
  raise_error(e)
@@ -60,10 +59,9 @@ module Sequel
60
59
  end
61
60
 
62
61
  def execute_dui(sql, opts={})
63
- log_info(sql)
64
62
  synchronize(opts[:server]) do |conn|
65
63
  begin
66
- conn.do(sql)
64
+ log_yield(sql){conn.do(sql)}
67
65
  rescue ::ODBC::Error, ArgumentError => e
68
66
  raise_error(e)
69
67
  end
@@ -16,13 +16,11 @@ module Sequel
16
16
 
17
17
  # Return the last inserted identity value.
18
18
  def execute_insert(sql, opts={})
19
- log_info(sql)
20
19
  synchronize(opts[:server]) do |conn|
21
20
  begin
22
- conn.do(sql)
23
- log_info(LAST_INSERT_ID_SQL)
21
+ log_yield(sql){conn.do(sql)}
24
22
  begin
25
- s = conn.run(LAST_INSERT_ID_SQL)
23
+ s = log_yield(LAST_INSERT_ID_SQL){conn.run(LAST_INSERT_ID_SQL)}
26
24
  if (rows = s.fetch_all) and (row = rows.first)
27
25
  Integer(row.first)
28
26
  end
@@ -20,9 +20,8 @@ module Sequel
20
20
  end
21
21
 
22
22
  def execute(sql, opts={})
23
- log_info(sql)
24
23
  synchronize(opts[:server]) do |conn|
25
- r = conn.execute(sql)
24
+ r = log_yield(sql){conn.execute(sql)}
26
25
  yield(r) if block_given?
27
26
  r
28
27
  end
@@ -60,10 +60,9 @@ module Sequel
60
60
  end
61
61
 
62
62
  def execute(sql, opts={})
63
- log_info(sql)
64
63
  synchronize(opts[:server]) do |conn|
65
64
  begin
66
- r = conn.exec(sql)
65
+ r = log_yield(sql){conn.exec(sql)}
67
66
  yield(r) if block_given?
68
67
  r
69
68
  rescue OCIException => e
@@ -76,14 +75,12 @@ module Sequel
76
75
  private
77
76
 
78
77
  def begin_transaction(conn)
79
- log_info(TRANSACTION_BEGIN)
80
- conn.autocommit = false
78
+ log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
81
79
  conn
82
80
  end
83
81
 
84
82
  def commit_transaction(conn)
85
- log_info(TRANSACTION_COMMIT)
86
- conn.commit
83
+ log_yield(TRANSACTION_COMMIT){conn.commit}
87
84
  end
88
85
 
89
86
  def disconnect_connection(c)
@@ -96,8 +93,7 @@ module Sequel
96
93
  end
97
94
 
98
95
  def rollback_transaction(conn)
99
- log_info(TRANSACTION_ROLLBACK)
100
- conn.rollback
96
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback}
101
97
  end
102
98
  end
103
99
 
@@ -133,7 +133,6 @@ module Sequel
133
133
  super
134
134
  if Postgres.use_iso_date_format
135
135
  sql = "SET DateStyle = 'ISO'"
136
- @db.log_info(sql)
137
136
  execute(sql)
138
137
  end
139
138
  @prepared_statements = {} if SEQUEL_POSTGRES_USES_PG
@@ -160,7 +159,7 @@ module Sequel
160
159
  # Execute the given SQL with this connection. If a block is given,
161
160
  # yield the results, otherwise, return the number of changed rows.
162
161
  def execute(sql, args=nil)
163
- q = check_disconnect_errors{args ? async_exec(sql, args) : async_exec(sql)}
162
+ q = check_disconnect_errors{@db.log_yield(sql, args){args ? async_exec(sql, args) : async_exec(sql)}}
164
163
  begin
165
164
  block_given? ? yield(q) : q.cmd_tuples
166
165
  ensure
@@ -225,7 +224,6 @@ module Sequel
225
224
  def execute(sql, opts={}, &block)
226
225
  check_database_errors do
227
226
  return execute_prepared_statement(sql, opts, &block) if Symbol === sql
228
- log_info(sql, opts[:arguments])
229
227
  synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
230
228
  end
231
229
  end
@@ -235,7 +233,6 @@ module Sequel
235
233
  def execute_insert(sql, opts={})
236
234
  return execute(sql, opts) if Symbol === sql
237
235
  check_database_errors do
238
- log_info(sql, opts[:arguments])
239
236
  synchronize(opts[:server]) do |conn|
240
237
  conn.execute(sql, opts[:arguments])
241
238
  insert_result(conn, opts[:table], opts[:values])
@@ -278,16 +275,12 @@ module Sequel
278
275
  synchronize(opts[:server]) do |conn|
279
276
  unless conn.prepared_statements[ps_name] == sql
280
277
  if conn.prepared_statements.include?(ps_name)
281
- s = "DEALLOCATE #{ps_name}"
282
- log_info(s)
283
- conn.execute(s) unless conn.prepared_statements[ps_name] == sql
278
+ conn.execute("DEALLOCATE #{ps_name}") unless conn.prepared_statements[ps_name] == sql
284
279
  end
285
280
  conn.prepared_statements[ps_name] = sql
286
- log_info("PREPARE #{ps_name} AS #{sql}")
287
- conn.check_disconnect_errors{conn.prepare(ps_name, sql)}
281
+ conn.check_disconnect_errors{log_yield("PREPARE #{ps_name} AS #{sql}"){conn.prepare(ps_name, sql)}}
288
282
  end
289
- log_info("EXECUTE #{ps_name}", args)
290
- q = conn.check_disconnect_errors{conn.exec_prepared(ps_name, args)}
283
+ q = conn.check_disconnect_errors{log_yield("EXECUTE #{ps_name}", args){conn.exec_prepared(ps_name, args)}}
291
284
  if opts[:table] && opts[:values]
292
285
  insert_result(conn, opts[:table], opts[:values])
293
286
  else
@@ -12,6 +12,10 @@ module Sequel
12
12
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
13
13
  TEMPORARY = "#".freeze
14
14
 
15
+ # The types to check for 0 scale to transform :decimal types
16
+ # to :integer.
17
+ DECIMAL_TYPE_RE = /number|numeric|decimal/io
18
+
15
19
  # Microsoft SQL Server uses the :mssql type.
16
20
  def database_type
17
21
  :mssql
@@ -64,9 +68,11 @@ module Sequel
64
68
  "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
65
69
  when :set_column_null
66
70
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
67
- type = {:type=>sch[:db_type]}
68
- type[:size] = sch[:max_chars] if sch[:max_chars]
69
- "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(type)} #{'NOT ' unless op[:null]}NULL"
71
+ type = sch[:db_type]
72
+ if [:string, :decimal].include?(sch[:type]) and size = (sch[:max_chars] || sch[:column_size])
73
+ type += "(#{size}#{", #{sch[:scale]}" if sch[:scale] && sch[:scale].to_i > 0})"
74
+ end
75
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(:type=>type)} #{'NOT ' unless op[:null]}NULL"
70
76
  when :set_column_default
71
77
  "ALTER TABLE #{quote_schema_table(table)} ADD CONSTRAINT #{quote_identifier("sequel_#{table}_#{op[:name]}_def")} DEFAULT #{literal(op[:default])} FOR #{quote_identifier(op[:name])}"
72
78
  else
@@ -107,6 +113,11 @@ module Sequel
107
113
  ds
108
114
  end
109
115
 
116
+ # Use SP_RENAME to rename the table
117
+ def rename_table_sql(name, new_name)
118
+ "SP_RENAME #{quote_schema_table(name)}, #{quote_schema_table(new_name)}"
119
+ end
120
+
110
121
  # SQL to rollback to a savepoint
111
122
  def rollback_savepoint_sql(depth)
112
123
  SQL_ROLLBACK_TO_SAVEPOINT % depth
@@ -125,7 +136,7 @@ module Sequel
125
136
  ds = metadata_dataset.from(:information_schema__tables___t).
126
137
  join(:information_schema__columns___c, :table_catalog=>:table_catalog,
127
138
  :table_schema => :table_schema, :table_name => :table_name).
128
- select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null).
139
+ select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null, :numeric_precision___column_size, :numeric_scale___scale).
129
140
  filter(:c__table_name=>m2.call(table_name.to_s))
130
141
  if schema = opts[:schema] || default_schema
131
142
  ds.filter!(:c__table_schema=>schema)
@@ -133,7 +144,11 @@ module Sequel
133
144
  ds.map do |row|
134
145
  row[:allow_null] = row[:allow_null] == 'YES' ? true : false
135
146
  row[:default] = nil if blank_object?(row[:default])
136
- row[:type] = schema_column_type(row[:db_type])
147
+ row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
148
+ :integer
149
+ else
150
+ schema_column_type(row[:db_type])
151
+ end
137
152
  [m.call(row.delete(:column)), row]
138
153
  end
139
154
  end
@@ -183,7 +198,7 @@ module Sequel
183
198
  COMMA_SEPARATOR = ', '.freeze
184
199
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
185
200
  INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
186
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns into from lock join where group having order compounds')
201
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns into from lock join where group having order compounds')
187
202
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
188
203
  NOLOCK = ' WITH (NOLOCK)'.freeze
189
204
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
@@ -301,11 +316,9 @@ module Sequel
301
316
  raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
302
317
  dsa1 = dataset_alias(1)
303
318
  rn = row_number_column
304
- sel = [Sequel::SQL::WindowFunction.new(SQL::Function.new(:ROW_NUMBER), Sequel::SQL::Window.new(:order=>order)).as(rn)]
305
- sel.unshift(WILDCARD) unless osel = @opts[:select] and !osel.empty?
306
319
  subselect_sql(unlimited.
307
320
  unordered.
308
- select_more(*sel).
321
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
309
322
  from_self(:alias=>dsa1).
310
323
  limit(@opts[:limit]).
311
324
  where(SQL::Identifier.new(rn) > o))
@@ -131,9 +131,9 @@ module Sequel
131
131
 
132
132
  # Use MySQL specific syntax for engine type and character encoding
133
133
  def create_table_sql(name, generator, options = {})
134
- engine = options.include?(:engine) ? options[:engine] : Sequel::MySQL.default_engine
135
- charset = options.include?(:charset) ? options[:charset] : Sequel::MySQL.default_charset
136
- collate = options.include?(:collate) ? options[:collate] : Sequel::MySQL.default_collate
134
+ engine = options.fetch(:engine, Sequel::MySQL.default_engine)
135
+ charset = options.fetch(:charset, Sequel::MySQL.default_charset)
136
+ collate = options.fetch(:collate, Sequel::MySQL.default_collate)
137
137
  generator.columns.each do |c|
138
138
  if t = c.delete(:table)
139
139
  generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key))
@@ -250,6 +250,11 @@ module Sequel
250
250
  end
251
251
  end
252
252
 
253
+ # Use GROUP BY instead of DISTINCT ON if arguments are provided.
254
+ def distinct(*args)
255
+ args.empty? ? super : group(*args)
256
+ end
257
+
253
258
  # Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
254
259
  def for_share
255
260
  lock_style(:share)
@@ -288,23 +293,19 @@ module Sequel
288
293
  end
289
294
  end
290
295
 
291
- # Sets up multi_insert or import to use INSERT IGNORE.
296
+ # Sets up the insert methods to use INSERT IGNORE.
292
297
  # Useful if you have a unique key and want to just skip
293
298
  # inserting rows that violate the unique key restriction.
294
299
  #
295
- # Example:
296
- #
297
- # dataset.insert_ignore.multi_insert(
298
- # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
299
- # )
300
- #
301
- # INSERT IGNORE INTO tablename (name, value) VALUES (a, 1), (b, 2)
302
- #
300
+ # dataset.insert_ignore.multi_insert(
301
+ # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
302
+ # )
303
+ # # INSERT IGNORE INTO tablename (name, value) VALUES (a, 1), (b, 2)
303
304
  def insert_ignore
304
305
  clone(:insert_ignore=>true)
305
306
  end
306
307
 
307
- # Sets up multi_insert or import to use ON DUPLICATE KEY UPDATE
308
+ # Sets up the insert methods to use ON DUPLICATE KEY UPDATE
308
309
  # If you pass no arguments, ALL fields will be
309
310
  # updated with the new values. If you pass the fields you
310
311
  # want then ONLY those field will be updated.
@@ -312,22 +313,17 @@ module Sequel
312
313
  # Useful if you have a unique key and want to update
313
314
  # inserting rows that violate the unique key restriction.
314
315
  #
315
- # Examples:
316
- #
317
- # dataset.on_duplicate_key_update.multi_insert(
318
- # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
319
- # )
320
- #
321
- # INSERT INTO tablename (name, value) VALUES (a, 1), (b, 2)
322
- # ON DUPLICATE KEY UPDATE name=VALUES(name), value=VALUES(value)
323
- #
324
- # dataset.on_duplicate_key_update(:value).multi_insert(
325
- # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
326
- # )
327
- #
328
- # INSERT INTO tablename (name, value) VALUES (a, 1), (b, 2)
329
- # ON DUPLICATE KEY UPDATE value=VALUES(value)
316
+ # dataset.on_duplicate_key_update.multi_insert(
317
+ # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
318
+ # )
319
+ # # INSERT INTO tablename (name, value) VALUES (a, 1), (b, 2)
320
+ # # ON DUPLICATE KEY UPDATE name=VALUES(name), value=VALUES(value)
330
321
  #
322
+ # dataset.on_duplicate_key_update(:value).multi_insert(
323
+ # [{:name => 'a', :value => 1}, {:name => 'b', :value => 2}]
324
+ # )
325
+ # # INSERT INTO tablename (name, value) VALUES (a, 1), (b, 2)
326
+ # # ON DUPLICATE KEY UPDATE value=VALUES(value)
331
327
  def on_duplicate_key_update(*args)
332
328
  clone(:on_duplicate_key_update => args)
333
329
  end
@@ -337,6 +333,12 @@ module Sequel
337
333
  [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
338
334
  end
339
335
 
336
+ # MySQL uses the number of rows actually modified in the update,
337
+ # instead of the number of matched by the filter.
338
+ def provides_accurate_rows_matched?
339
+ false
340
+ end
341
+
340
342
  # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
341
343
  def quoted_identifier(c)
342
344
  "`#{c}`"
@@ -348,9 +350,10 @@ module Sequel
348
350
  clone(:replace=>true).insert_sql(*values)
349
351
  end
350
352
 
351
- # does not support DISTINCT ON
353
+ # MySQL can emulate DISTINCT ON with its non-standard GROUP BY implementation,
354
+ # though the rows returned cannot be made deterministic through ordering.
352
355
  def supports_distinct_on?
353
- false
356
+ true
354
357
  end
355
358
 
356
359
  # MySQL does not support INTERSECT or EXCEPT