sequel 2.12.0 → 3.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 (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