sequel 2.12.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +3 -3
  3. data/Rakefile +7 -0
  4. data/doc/advanced_associations.rdoc +44 -0
  5. data/doc/release_notes/3.0.0.txt +221 -0
  6. data/lib/sequel/adapters/amalgalite.rb +208 -0
  7. data/lib/sequel/adapters/db2.rb +3 -0
  8. data/lib/sequel/adapters/dbi.rb +9 -0
  9. data/lib/sequel/adapters/do.rb +0 -4
  10. data/lib/sequel/adapters/firebird.rb +16 -18
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +24 -20
  13. data/lib/sequel/adapters/jdbc/h2.rb +15 -4
  14. data/lib/sequel/adapters/mysql.rb +4 -8
  15. data/lib/sequel/adapters/odbc.rb +0 -4
  16. data/lib/sequel/adapters/oracle.rb +0 -4
  17. data/lib/sequel/adapters/shared/mssql.rb +16 -5
  18. data/lib/sequel/adapters/shared/mysql.rb +87 -86
  19. data/lib/sequel/adapters/shared/oracle.rb +92 -3
  20. data/lib/sequel/adapters/shared/postgres.rb +85 -29
  21. data/lib/sequel/adapters/shared/progress.rb +8 -3
  22. data/lib/sequel/adapters/shared/sqlite.rb +53 -23
  23. data/lib/sequel/adapters/sqlite.rb +4 -7
  24. data/lib/sequel/adapters/utils/unsupported.rb +3 -3
  25. data/lib/sequel/connection_pool.rb +18 -25
  26. data/lib/sequel/core.rb +2 -21
  27. data/lib/sequel/database.rb +60 -44
  28. data/lib/sequel/database/schema_generator.rb +26 -31
  29. data/lib/sequel/database/schema_methods.rb +8 -3
  30. data/lib/sequel/database/schema_sql.rb +114 -28
  31. data/lib/sequel/dataset.rb +14 -41
  32. data/lib/sequel/dataset/convenience.rb +31 -54
  33. data/lib/sequel/dataset/graph.rb +7 -13
  34. data/lib/sequel/dataset/sql.rb +43 -54
  35. data/lib/sequel/extensions/inflector.rb +0 -5
  36. data/lib/sequel/extensions/schema_dumper.rb +238 -0
  37. data/lib/sequel/metaprogramming.rb +0 -20
  38. data/lib/sequel/model.rb +1 -2
  39. data/lib/sequel/model/base.rb +18 -16
  40. data/lib/sequel/model/inflections.rb +6 -9
  41. data/lib/sequel/plugins/caching.rb +0 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  43. data/lib/sequel/sql.rb +2 -0
  44. data/lib/sequel/version.rb +2 -2
  45. data/spec/adapters/firebird_spec.rb +35 -8
  46. data/spec/adapters/mysql_spec.rb +173 -266
  47. data/spec/adapters/oracle_spec.rb +13 -0
  48. data/spec/adapters/postgres_spec.rb +127 -227
  49. data/spec/adapters/sqlite_spec.rb +13 -171
  50. data/spec/core/connection_pool_spec.rb +15 -4
  51. data/spec/core/core_sql_spec.rb +14 -170
  52. data/spec/core/database_spec.rb +50 -132
  53. data/spec/core/dataset_spec.rb +47 -930
  54. data/spec/core/expression_filters_spec.rb +12 -0
  55. data/spec/core/schema_generator_spec.rb +37 -45
  56. data/spec/core/schema_spec.rb +26 -16
  57. data/spec/core/spec_helper.rb +0 -25
  58. data/spec/extensions/inflector_spec.rb +0 -3
  59. data/spec/extensions/schema_dumper_spec.rb +292 -0
  60. data/spec/extensions/serialization_spec.rb +9 -0
  61. data/spec/extensions/single_table_inheritance_spec.rb +6 -1
  62. data/spec/extensions/spec_helper.rb +1 -3
  63. data/spec/extensions/validation_helpers_spec.rb +4 -4
  64. data/spec/integration/database_test.rb +18 -0
  65. data/spec/integration/dataset_test.rb +112 -1
  66. data/spec/integration/eager_loader_test.rb +70 -9
  67. data/spec/integration/prepared_statement_test.rb +2 -2
  68. data/spec/integration/schema_test.rb +76 -27
  69. data/spec/integration/spec_helper.rb +0 -14
  70. data/spec/integration/transaction_test.rb +27 -0
  71. data/spec/model/associations_spec.rb +0 -36
  72. data/spec/model/base_spec.rb +18 -123
  73. data/spec/model/hooks_spec.rb +2 -235
  74. data/spec/model/inflector_spec.rb +15 -115
  75. data/spec/model/model_spec.rb +0 -120
  76. data/spec/model/plugins_spec.rb +0 -70
  77. data/spec/model/record_spec.rb +35 -93
  78. data/spec/model/spec_helper.rb +0 -27
  79. data/spec/model/validations_spec.rb +0 -931
  80. metadata +9 -14
  81. data/lib/sequel/deprecated.rb +0 -593
  82. data/lib/sequel/deprecated_migration.rb +0 -91
  83. data/lib/sequel/model/deprecated.rb +0 -204
  84. data/lib/sequel/model/deprecated_hooks.rb +0 -103
  85. data/lib/sequel/model/deprecated_inflector.rb +0 -335
  86. data/lib/sequel/model/deprecated_validations.rb +0 -388
  87. data/spec/core/core_ext_spec.rb +0 -156
  88. data/spec/core/migration_spec.rb +0 -263
  89. data/spec/core/pretty_table_spec.rb +0 -58
  90. data/spec/model/caching_spec.rb +0 -217
  91. data/spec/model/schema_spec.rb +0 -92
@@ -4,7 +4,10 @@ module Sequel
4
4
  module DB2
5
5
  class Database < Sequel::Database
6
6
  set_adapter_scheme :db2
7
+
7
8
  include DB2CLI
9
+
10
+ TEMPORARY = 'GLOBAL TEMPORARY '.freeze
8
11
 
9
12
  rc, @@env = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
10
13
  #check_error(rc, "Could not allocate DB2 environment")
@@ -19,6 +19,15 @@ module Sequel
19
19
  :sqlite => "SQLite",
20
20
  :sqlrelay => "SQLRelay"
21
21
  }
22
+
23
+ def initialize(opts)
24
+ super(opts)
25
+ case opts[:db_type]
26
+ when 'mssql'
27
+ Sequel.require 'adapters/shared/mssql'
28
+ extend Sequel::MSSQL::DatabaseMethods
29
+ end
30
+ end
22
31
 
23
32
  # Converts a uri to an options hash. These options are then passed
24
33
  # to a newly created database object.
@@ -121,10 +121,6 @@ module Sequel
121
121
  # transactions and commits them immediately after. It's wasteful,
122
122
  # but required by DataObject's API.
123
123
  def transaction(opts={})
124
- unless opts.is_a?(Hash)
125
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
126
- opts = {:server=>opts}
127
- end
128
124
  th = Thread.current
129
125
  synchronize(opts[:server]) do |conn|
130
126
  return yield(conn) if @transactions.include?(th)
@@ -9,6 +9,7 @@ module Sequel
9
9
  set_adapter_scheme :firebird
10
10
 
11
11
  AUTO_INCREMENT = ''.freeze
12
+ TEMPORARY = 'GLOBAL TEMPORARY '.freeze
12
13
 
13
14
  # Add the primary_keys and primary_key_sequences instance variables,
14
15
  # so we can get the correct return values for inserted rows.
@@ -44,13 +45,10 @@ module Sequel
44
45
  # generator for auto-incrementing primary keys.
45
46
  def create_table(name, options={}, &block)
46
47
  options = {:generator=>options} if options.is_a?(Schema::Generator)
47
- statements = create_table_sql_list(name, *((options[:generator] ||= Schema::Generator.new(self, &block)).create_info << options))
48
- begin
49
- execute_ddl(statements[1])
50
- rescue
51
- nil
52
- end if statements[1]
53
- statements[0].flatten.each {|sql| execute_ddl(sql)}
48
+ generator = options[:generator] || Schema::Generator.new(self, &block)
49
+ drop_statement, create_statements = create_table_sql_list(name, generator, options)
50
+ (execute_ddl(drop_statement) rescue nil) if drop_statement
51
+ (create_statements + index_sql_list(name, generator.indexes)).each{|sql| execute_ddl(sql)}
54
52
  end
55
53
 
56
54
  def create_trigger(*args)
@@ -106,10 +104,6 @@ module Sequel
106
104
  end
107
105
 
108
106
  def transaction(opts={})
109
- unless opts.is_a?(Hash)
110
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
111
- opts = {:server=>opts}
112
- end
113
107
  synchronize(opts[:server]) do |conn|
114
108
  return yield(conn) if @transactions.include?(Thread.current)
115
109
  log_info("Transaction.begin")
@@ -157,10 +151,10 @@ module Sequel
157
151
  "CREATE SEQUENCE #{quote_identifier(name)}"
158
152
  end
159
153
 
160
- def create_table_sql_list(name, columns, indexes = nil, options={})
161
- statements = super
154
+ def create_table_sql_list(name, generator, options={})
155
+ statements = [create_table_sql(name, generator, options)]
162
156
  drop_seq_statement = nil
163
- columns.each do |c|
157
+ generator.columns.each do |c|
164
158
  if c[:auto_increment]
165
159
  c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
166
160
  unless c[:create_sequence] == false
@@ -183,7 +177,7 @@ module Sequel
183
177
  end
184
178
  end
185
179
  end
186
- [statements, drop_seq_statement]
180
+ [drop_seq_statement, statements]
187
181
  end
188
182
 
189
183
  def create_trigger_sql(table, name, definition, opts={})
@@ -211,6 +205,10 @@ module Sequel
211
205
  seq_name = quote_identifier(name)
212
206
  "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
213
207
  end
208
+
209
+ def type_literal_generic_string(column)
210
+ column[:text] ? :"BLOB SUBTYPE TEXT" : super
211
+ end
214
212
  end
215
213
 
216
214
  # Dataset class for Firebird datasets
@@ -273,9 +271,9 @@ module Sequel
273
271
  SELECT_CLAUSE_ORDER
274
272
  end
275
273
 
276
- def select_limit_sql(sql, opts)
277
- sql << " FIRST #{opts[:limit]}" if opts[:limit]
278
- sql << " SKIP #{opts[:offset]}" if opts[:offset]
274
+ def select_limit_sql(sql)
275
+ sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
276
+ sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
279
277
  end
280
278
 
281
279
  private
@@ -6,6 +6,8 @@ module Sequel
6
6
  class Database < Sequel::Database
7
7
  set_adapter_scheme :informix
8
8
 
9
+ TEMPORARY = 'TEMP '.freeze
10
+
9
11
  def connect(server)
10
12
  opts = server_opts(server)
11
13
  ::Informix.connect(opts[:database], opts[:user], opts[:password])
@@ -66,9 +68,9 @@ module Sequel
66
68
  SELECT_CLAUSE_ORDER
67
69
  end
68
70
 
69
- def select_limit_sql(sql, opts)
70
- sql << " SKIP #{opts[:offset]}" if opts[:offset]
71
- sql << " FIRST #{opts[:limit]}" if opts[:limit]
71
+ def select_limit_sql(sql)
72
+ sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
73
+ sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
72
74
  end
73
75
  end
74
76
  end
@@ -185,11 +185,27 @@ module Sequel
185
185
  execute(sql, {:type=>:insert}.merge(opts))
186
186
  end
187
187
 
188
+ # Return a hash containing index information. Hash keys are index name symbols.
189
+ # Values are subhashes with two keys, :columns and :unique. The value of :columns
190
+ # is an array of symbols of column names. The value of :unique is true or false
191
+ # depending on if the index is unique.
192
+ def indexes(table)
193
+ indexes = {}
194
+ m = output_identifier_meth
195
+ metadata(:getIndexInfo, nil, nil, input_identifier_meth.call(table), false, true) do |r|
196
+ next unless name = r[:column_name]
197
+ next if respond_to?(:primary_key_index_re, true) and r[:index_name] =~ primary_key_index_re
198
+ i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>!r[:non_unique]}
199
+ i[:columns] << m.call(name)
200
+ end
201
+ indexes
202
+ end
203
+
188
204
  # All tables in this database
189
205
  def tables
190
206
  ts = []
191
- ds = dataset
192
- metadata(:getTables, nil, nil, nil, ['TABLE'].to_java(:string)){|h| ts << ds.send(:output_identifier, h[:table_name])}
207
+ m = output_identifier_meth
208
+ metadata(:getTables, nil, nil, nil, ['TABLE'].to_java(:string)){|h| ts << m.call(h[:table_name])}
193
209
  ts
194
210
  end
195
211
 
@@ -197,10 +213,6 @@ module Sequel
197
213
  # databases. Does not use the JDBC transaction methods, uses
198
214
  # SQL BEGIN/ROLLBACK/COMMIT statements instead.
199
215
  def transaction(opts={})
200
- unless opts.is_a?(Hash)
201
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
202
- opts = {:server=>opts}
203
- end
204
216
  synchronize(opts[:server]) do |conn|
205
217
  return yield(conn) if @transactions.include?(Thread.current)
206
218
  stmt = conn.createStatement
@@ -308,9 +320,7 @@ module Sequel
308
320
 
309
321
  # Yield the metadata for this database
310
322
  def metadata(*args, &block)
311
- ds = dataset
312
- ds.identifier_output_method = :downcase
313
- synchronize{|c| ds.send(:process_result_set, c.getMetaData.send(*args), &block)}
323
+ synchronize{|c| metadata_dataset.send(:process_result_set, c.getMetaData.send(*args), &block)}
314
324
  end
315
325
 
316
326
  # Java being java, you need to specify the type of each argument
@@ -347,16 +357,18 @@ module Sequel
347
357
 
348
358
  # All tables in this database
349
359
  def schema_parse_table(table, opts={})
360
+ m = output_identifier_meth
361
+ im = input_identifier_meth
350
362
  ds = dataset
351
363
  schema, table = schema_and_table(table)
352
- schema = ds.send(:input_identifier, schema) if schema
353
- table = ds.send(:input_identifier, table)
364
+ schema = im.call(schema) if schema
365
+ table = im.call(table)
354
366
  pks, ts = [], []
355
367
  metadata(:getPrimaryKeys, nil, schema, table) do |h|
356
368
  pks << h[:column_name]
357
369
  end
358
370
  metadata(:getColumns, nil, schema, table, nil) do |h|
359
- ts << [ds.send(:output_identifier, h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name])}]
371
+ ts << [m.call(h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name])}]
360
372
  end
361
373
  ts
362
374
  end
@@ -476,11 +488,3 @@ module Sequel
476
488
  end
477
489
  end
478
490
  end
479
-
480
- class Java::JavaSQL::Timestamp
481
- # Add a usec method in order to emulate Time values.
482
- def usec
483
- Deprecation.deprecate('Java::JavaSQL::Timestamp#usec', 'Use timestamp.getNanos/1000')
484
- getNanos/1000
485
- end
486
- end
@@ -6,6 +6,13 @@ module Sequel
6
6
  module H2
7
7
  # Instance methods for H2 Database objects accessed via JDBC.
8
8
  module DatabaseMethods
9
+ PRIMARY_KEY_INDEX_RE = /\Aprimary_key/i.freeze
10
+
11
+ # H2 uses the :h2 database type.
12
+ def database_type
13
+ :h2
14
+ end
15
+
9
16
  # Return Sequel::JDBC::H2::Dataset object with the given opts.
10
17
  def dataset(opts=nil)
11
18
  Sequel::JDBC::H2::Dataset.new(self, opts)
@@ -32,6 +39,12 @@ module Sequel
32
39
  super(table, op)
33
40
  end
34
41
  end
42
+
43
+ # Default to a single connection for a memory database.
44
+ def connection_pool_default_options
45
+ o = super
46
+ uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o
47
+ end
35
48
 
36
49
  # Use IDENTITY() to get the last inserted id.
37
50
  def last_insert_id(conn, opts={})
@@ -45,10 +58,8 @@ module Sequel
45
58
  end
46
59
  end
47
60
 
48
- # Default to a single connection for a memory database.
49
- def connection_pool_default_options
50
- o = super
51
- uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o
61
+ def primary_key_index_re
62
+ PRIMARY_KEY_INDEX_RE
52
63
  end
53
64
  end
54
65
 
@@ -135,10 +135,6 @@ module Sequel
135
135
 
136
136
  # Support single level transactions on MySQL.
137
137
  def transaction(opts={})
138
- unless opts.is_a?(Hash)
139
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
140
- opts = {:server=>opts}
141
- end
142
138
  synchronize(opts[:server]) do |conn|
143
139
  return yield(conn) if @transactions.include?(Thread.current)
144
140
  log_info(begin_transaction_sql)
@@ -297,8 +293,8 @@ module Sequel
297
293
  end
298
294
 
299
295
  # Delete rows matching this dataset
300
- def delete(opts = (defarg=true;nil))
301
- execute_dui(defarg ? delete_sql : delete_sql(opts)){|c| c.affected_rows}
296
+ def delete
297
+ execute_dui(delete_sql){|c| c.affected_rows}
302
298
  end
303
299
 
304
300
  # Yield all rows matching this dataset
@@ -338,8 +334,8 @@ module Sequel
338
334
  end
339
335
 
340
336
  # Update the matching rows.
341
- def update(values={}, opts=(defarg=true;nil))
342
- execute_dui(defarg ? update_sql(values) : update_sql(values, opts)){|c| c.affected_rows}
337
+ def update(values={})
338
+ execute_dui(update_sql(values)){|c| c.affected_rows}
343
339
  end
344
340
 
345
341
  private
@@ -65,10 +65,6 @@ module Sequel
65
65
 
66
66
  # Support single level transactions on ODBC
67
67
  def transaction(opts={})
68
- unless opts.is_a?(Hash)
69
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
70
- opts = {:server=>opts}
71
- end
72
68
  synchronize(opts[:server]) do |conn|
73
69
  return yield(conn) if @transactions.include?(Thread.current)
74
70
  log_info(begin_transaction_sql)
@@ -78,10 +78,6 @@ module Sequel
78
78
  alias do execute
79
79
 
80
80
  def transaction(opts={})
81
- unless opts.is_a?(Hash)
82
- Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
83
- opts = {:server=>opts}
84
- end
85
81
  synchronize(opts[:server]) do |conn|
86
82
  return yield(conn) if @transactions.include?(Thread.current)
87
83
  conn.autocommit = false
@@ -7,7 +7,13 @@ module Sequel
7
7
  SQL_BEGIN = "BEGIN TRANSACTION".freeze
8
8
  SQL_COMMIT = "COMMIT TRANSACTION".freeze
9
9
  SQL_ROLLBACK = "ROLLBACK TRANSACTION".freeze
10
+ TEMPORARY = "#".freeze
10
11
 
12
+ # Microsoft SQL Server uses the :mssql type.
13
+ def database_type
14
+ :mssql
15
+ end
16
+
11
17
  def dataset(opts = nil)
12
18
  ds = super
13
19
  ds.extend(DatasetMethods)
@@ -34,6 +40,11 @@ module Sequel
34
40
  def rollback_transaction_sql
35
41
  SQL_ROLLBACK
36
42
  end
43
+
44
+ # SQL fragment for marking a table as temporary
45
+ def temporary_table_sql
46
+ TEMPORARY
47
+ end
37
48
  end
38
49
 
39
50
  module DatasetMethods
@@ -79,14 +90,14 @@ module Sequel
79
90
  end
80
91
 
81
92
  # MSSQL uses TOP for limit, with no offset support
82
- def select_limit_sql(sql, opts)
83
- raise(Error, "OFFSET not supported") if opts[:offset]
84
- sql << " TOP #{opts[:limit]}" if opts[:limit]
93
+ def select_limit_sql(sql)
94
+ raise(Error, "OFFSET not supported") if @opts[:offset]
95
+ sql << " TOP #{@opts[:limit]}" if @opts[:limit]
85
96
  end
86
97
 
87
98
  # MSSQL uses the WITH statement to lock tables
88
- def select_with_sql(sql, opts)
89
- sql << " WITH #{opts[:with]}" if opts[:with]
99
+ def select_with_sql(sql)
100
+ sql << " WITH #{@opts[:with]}" if @opts[:with]
90
101
  end
91
102
  end
92
103
  end
@@ -15,14 +15,37 @@ module Sequel
15
15
  # currently supported by the native and JDBC adapters.
16
16
  module DatabaseMethods
17
17
  AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
18
- NOT_NULL = Sequel::Database::NOT_NULL
19
- NULL = Sequel::Database::NULL
20
- PRIMARY_KEY = Sequel::Database::PRIMARY_KEY
21
- TYPES = Sequel::Database::TYPES.merge(DateTime=>'datetime', \
22
- TrueClass=>'tinyint', FalseClass=>'tinyint')
23
- UNIQUE = Sequel::Database::UNIQUE
24
- UNSIGNED = Sequel::Database::UNSIGNED
18
+ CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
19
+ PRIMARY = 'PRIMARY'.freeze
25
20
 
21
+ # MySQL's cast rules are restrictive in that you can't just cast to any possible
22
+ # database type.
23
+ def cast_type_literal(type)
24
+ CAST_TYPES[type] || super
25
+ end
26
+
27
+ # MySQL uses the :mysql database type
28
+ def database_type
29
+ :mysql
30
+ end
31
+
32
+ # Return a hash containing index information. Hash keys are index name symbols.
33
+ # Values are subhashes with two keys, :columns and :unique. The value of :columns
34
+ # is an array of symbols of column names. The value of :unique is true or false
35
+ # depending on if the index is unique.
36
+ def indexes(table)
37
+ indexes = {}
38
+ m = output_identifier_meth
39
+ im = input_identifier_meth
40
+ metadata_dataset.with_sql("SHOW INDEX FROM ?", SQL::Identifier.new(im.call(table))).each do |r|
41
+ name = r[:Key_name]
42
+ next if name == PRIMARY
43
+ i = indexes[m.call(name)] ||= {:columns=>[], :unique=>r[:Non_unique] != 1}
44
+ i[:columns] << m.call(r[:Column_name])
45
+ end
46
+ indexes
47
+ end
48
+
26
49
  # Get version of MySQL server, used for determined capabilities.
27
50
  def server_version
28
51
  m = /(\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
@@ -34,10 +57,8 @@ module Sequel
34
57
  # Options:
35
58
  # * :server - Set the server to use
36
59
  def tables(opts={})
37
- ds = self['SHOW TABLES'].server(opts[:server])
38
- ds.identifier_output_method = nil
39
- ds2 = dataset
40
- ds.map{|r| ds2.send(:output_identifier, r.values.first)}
60
+ m = output_identifier_meth
61
+ metadata_dataset.with_sql('SHOW TABLES').server(opts[:server]).map{|r| m.call(r.values.first)}
41
62
  end
42
63
 
43
64
  # Changes the database in use by issuing a USE statement. I would be
@@ -63,10 +84,15 @@ module Sequel
63
84
  else
64
85
  super(table, op)
65
86
  end
66
- when :rename_column
67
- "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
68
- when :set_column_type
69
- "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:name])} #{type_literal(op)}"
87
+ when :rename_column, :set_column_type, :set_column_null, :set_column_default
88
+ o = op[:op]
89
+ opts = schema(table).find{|x| x.first == op[:name]}
90
+ old_opts = opts ? opts.last : {}
91
+ name = o == :rename_column ? op[:new_name] : op[:name]
92
+ type = o == :set_column_type ? op[:type] : old_opts[:db_type]
93
+ null = o == :set_column_null ? op[:null] : old_opts[:allow_null]
94
+ default = o == :set_column_default ? op[:default] : (old_opts[:default].lit if old_opts[:default])
95
+ "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(:name=>name, :type=>type, :null=>null, :default=>default)}"
70
96
  when :drop_index
71
97
  "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
72
98
  else
@@ -85,13 +111,11 @@ module Sequel
85
111
  end
86
112
 
87
113
  # Use MySQL specific syntax for engine type and character encoding
88
- def create_table_sql_list(name, columns, indexes = nil, options = {})
89
- options[:engine] = Sequel::MySQL.default_engine unless options.include?(:engine)
90
- options[:charset] = Sequel::MySQL.default_charset unless options.include?(:charset)
91
- options[:collate] = Sequel::MySQL.default_collate unless options.include?(:collate)
92
- sql = ["CREATE TABLE #{quote_schema_table(name)} (#{column_list_sql(columns)})#{" ENGINE=#{options[:engine]}" if options[:engine]}#{" DEFAULT CHARSET=#{options[:charset]}" if options[:charset]}#{" DEFAULT COLLATE=#{options[:collate]}" if options[:collate]}"]
93
- sql.concat(index_list_sql_list(name, indexes)) if indexes && !indexes.empty?
94
- sql
114
+ def create_table_sql(name, generator, options = {})
115
+ engine = options.include?(:engine) ? options[:engine] : Sequel::MySQL.default_engine
116
+ charset = options.include?(:charset) ? options[:charset] : Sequel::MySQL.default_charset
117
+ collate = options.include?(:collate) ? options[:collate] : Sequel::MySQL.default_collate
118
+ "#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
95
119
  end
96
120
 
97
121
  # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
@@ -116,15 +140,19 @@ module Sequel
116
140
  using = " USING #{index[:type]}" unless index[:type] == nil
117
141
  "UNIQUE " if index[:unique]
118
142
  end
119
- "CREATE #{index_type}INDEX #{index_name} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}#{using}"
143
+ "CREATE #{index_type}INDEX #{index_name}#{using} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
120
144
  end
121
145
 
146
+ # MySQL treats integer primary keys as autoincrementing.
147
+ def schema_autoincrementing_primary_key?(schema)
148
+ super and schema[:db_type] =~ /int/io
149
+ end
150
+
122
151
  # Use the MySQL specific DESCRIBE syntax to get a table description.
123
152
  def schema_parse_table(table_name, opts)
124
- ds = self["DESCRIBE ?", SQL::Identifier.new(table_name)]
125
- ds.identifier_output_method = nil
126
- ds2 = dataset
127
- ds.map do |row|
153
+ m = output_identifier_meth
154
+ im = input_identifier_meth
155
+ metadata_dataset.with_sql("DESCRIBE ?", SQL::Identifier.new(im.call(table_name))).map do |row|
128
156
  row.delete(:Extra)
129
157
  row[:allow_null] = row.delete(:Null) == 'YES'
130
158
  row[:default] = row.delete(:Default)
@@ -132,13 +160,26 @@ module Sequel
132
160
  row[:default] = nil if blank_object?(row[:default])
133
161
  row[:db_type] = row.delete(:Type)
134
162
  row[:type] = schema_column_type(row[:db_type])
135
- [ds2.send(:output_identifier, row.delete(:Field)), row]
163
+ [m.call(row.delete(:Field)), row]
136
164
  end
137
165
  end
138
166
 
139
- # Override the standard type conversions with MySQL specific ones
140
- def type_literal_base(column)
141
- TYPES[column[:type]]
167
+ # MySQL has both datetime and timestamp classes, most people are going
168
+ # to want datetime
169
+ def type_literal_generic_datetime(column)
170
+ :datetime
171
+ end
172
+
173
+ # MySQL has both datetime and timestamp classes, most people are going
174
+ # to want datetime
175
+ def type_literal_generic_time(column)
176
+ column[:only_time] ? :time : :datetime
177
+ end
178
+
179
+ # MySQL doesn't have a true boolean class, so it uses tinyint
180
+ # MySQL doesn't have a true boolean class, so it uses tinyint
181
+ def type_literal_generic_trueclass(column)
182
+ :tinyint
142
183
  end
143
184
  end
144
185
 
@@ -148,15 +189,9 @@ module Sequel
148
189
 
149
190
  BOOL_TRUE = '1'.freeze
150
191
  BOOL_FALSE = '0'.freeze
151
- CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
152
192
  TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S'".freeze
153
193
  COMMA_SEPARATOR = ', '.freeze
154
194
 
155
- # MySQL can't use the varchar type in a cast.
156
- def cast_sql(expr, type)
157
- "CAST(#{literal(expr)} AS #{CAST_TYPES[type] || db.send(:type_literal_base, :type=>type)})"
158
- end
159
-
160
195
  # MySQL specific syntax for LIKE/REGEXP searches, as well as
161
196
  # string concatenation.
162
197
  def complex_expression_sql(op, args)
@@ -175,22 +210,10 @@ module Sequel
175
210
  end
176
211
 
177
212
  # MySQL supports ORDER and LIMIT clauses in DELETE statements.
178
- def delete_sql(opts = (defarg=true;nil))
179
- if defarg
180
- sql = super()
181
- opts = @opts
182
- else
183
- sql = super
184
- opts = opts ? @opts.merge(opts) : @opts
185
- end
186
-
187
- if order = opts[:order]
188
- sql << " ORDER BY #{expression_list(order)}"
189
- end
190
- if limit = opts[:limit]
191
- sql << " LIMIT #{limit}"
192
- end
193
-
213
+ def delete_sql
214
+ sql = super
215
+ sql << " ORDER BY #{expression_list(opts[:order])}" if opts[:order]
216
+ sql << " LIMIT #{opts[:limit]}" if opts[:limit]
194
217
  sql
195
218
  end
196
219
 
@@ -200,19 +223,14 @@ module Sequel
200
223
  super
201
224
  end
202
225
 
203
- # MySQL specific full text search syntax.
226
+ # Adds full text filter
204
227
  def full_text_search(cols, terms, opts = {})
205
- mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
206
- s = if Array === terms
207
- if mode.empty?
208
- "MATCH #{literal(Array(cols))} AGAINST #{literal(terms)}"
209
- else
210
- "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)[1...-1]}#{mode})"
211
- end
212
- else
213
- "MATCH #{literal(Array(cols))} AGAINST (#{literal(terms)}#{mode})"
214
- end
215
- filter(s)
228
+ filter(full_text_sql(cols, terms, opts))
229
+ end
230
+
231
+ # MySQL specific full text search syntax.
232
+ def full_text_sql(cols, term, opts = {})
233
+ "MATCH #{literal(Array(cols))} AGAINST (#{literal(Array(term).join(' '))}#{" IN BOOLEAN MODE" if opts[:boolean]})"
216
234
  end
217
235
 
218
236
  # MySQL allows HAVING clause on ungrouped datasets.
@@ -311,11 +329,6 @@ module Sequel
311
329
  else
312
330
  values = values[0] if values.size == 1
313
331
 
314
- # if hash or array with keys we need to transform the values
315
- if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
316
- values = transform_save(values)
317
- end
318
-
319
332
  case values
320
333
  when Array
321
334
  if values.empty?
@@ -344,22 +357,10 @@ module Sequel
344
357
  end
345
358
 
346
359
  # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
347
- def update_sql(values, opts = (defarg=true;nil))
348
- if defarg
349
- sql = super(values)
350
- opts = @opts
351
- else
352
- sql = super
353
- opts = opts ? @opts.merge(opts) : @opts
354
- end
355
-
356
- if order = opts[:order]
357
- sql << " ORDER BY #{expression_list(order)}"
358
- end
359
- if limit = opts[:limit]
360
- sql << " LIMIT #{limit}"
361
- end
362
-
360
+ def update_sql(values)
361
+ sql = super
362
+ sql << " ORDER BY #{expression_list(opts[:order])}" if opts[:order]
363
+ sql << " LIMIT #{opts[:limit]}" if opts[:limit]
363
364
  sql
364
365
  end
365
366