sequel 3.10.0 → 3.11.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 (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