activerecord-jdbc-adapter 5.0.pre1 → 51.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.travis.yml +15 -416
  4. data/Gemfile +35 -37
  5. data/README.md +23 -118
  6. data/RUNNING_TESTS.md +31 -26
  7. data/Rakefile +2 -3
  8. data/activerecord-jdbc-adapter.gemspec +1 -2
  9. data/lib/arjdbc/abstract/connection_management.rb +21 -0
  10. data/lib/arjdbc/abstract/core.rb +62 -0
  11. data/lib/arjdbc/abstract/database_statements.rb +46 -0
  12. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  13. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  14. data/lib/arjdbc/derby/adapter.rb +6 -1
  15. data/lib/arjdbc/discover.rb +0 -7
  16. data/lib/arjdbc/firebird/adapter.rb +2 -2
  17. data/lib/arjdbc/jdbc/adapter.rb +10 -252
  18. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  19. data/lib/arjdbc/jdbc/connection.rb +6 -0
  20. data/lib/arjdbc/jdbc.rb +2 -2
  21. data/lib/arjdbc/mysql/adapter.rb +87 -944
  22. data/lib/arjdbc/mysql/connection_methods.rb +4 -2
  23. data/lib/arjdbc/postgresql/adapter.rb +288 -1023
  24. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  25. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  26. data/lib/arjdbc/postgresql/base/pgconn.rb +8 -5
  27. data/lib/arjdbc/postgresql/column.rb +10 -599
  28. data/lib/arjdbc/postgresql/connection_methods.rb +9 -0
  29. data/lib/arjdbc/postgresql/name.rb +24 -0
  30. data/lib/arjdbc/postgresql/oid_types.rb +25 -110
  31. data/lib/arjdbc/sqlite3/adapter.rb +171 -170
  32. data/lib/arjdbc/tasks/database_tasks.rb +1 -3
  33. data/lib/arjdbc/tasks/db2_database_tasks.rb +2 -2
  34. data/lib/arjdbc/version.rb +1 -1
  35. data/pom.xml +3 -3
  36. data/rakelib/02-test.rake +0 -12
  37. data/rakelib/compile.rake +1 -1
  38. data/rakelib/db.rake +7 -5
  39. data/rakelib/rails.rake +63 -64
  40. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +1 -17
  41. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +518 -1260
  42. data/src/java/arjdbc/mysql/MySQLModule.java +3 -3
  43. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +53 -134
  44. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +214 -240
  45. data/src/java/arjdbc/sqlite3/SQLite3Module.java +0 -20
  46. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +85 -10
  47. metadata +20 -34
  48. data/Appraisals +0 -41
  49. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -1
  50. data/lib/arjdbc/common_jdbc_methods.rb +0 -89
  51. data/lib/arjdbc/mysql/bulk_change_table.rb +0 -150
  52. data/lib/arjdbc/mysql/column.rb +0 -162
  53. data/lib/arjdbc/mysql/explain_support.rb +0 -82
  54. data/lib/arjdbc/mysql/schema_creation.rb +0 -58
  55. data/lib/arjdbc/oracle/adapter.rb +0 -952
  56. data/lib/arjdbc/oracle/column.rb +0 -126
  57. data/lib/arjdbc/oracle/connection_methods.rb +0 -21
  58. data/lib/arjdbc/oracle.rb +0 -4
  59. data/lib/arjdbc/postgresql/_bc_time_cast_patch.rb +0 -21
  60. data/lib/arjdbc/postgresql/base/oid.rb +0 -412
  61. data/lib/arjdbc/postgresql/base/schema_definitions.rb +0 -131
  62. data/lib/arjdbc/postgresql/explain_support.rb +0 -53
  63. data/lib/arjdbc/postgresql/oid/bytea.rb +0 -2
  64. data/lib/arjdbc/postgresql/schema_creation.rb +0 -60
  65. data/lib/arjdbc/tasks/oracle/enhanced_structure_dump.rb +0 -297
  66. data/lib/arjdbc/tasks/oracle_database_tasks.rb +0 -65
  67. data/src/java/arjdbc/oracle/OracleModule.java +0 -75
  68. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +0 -465
@@ -1,1001 +1,144 @@
1
1
  ArJdbc.load_java_part :MySQL
2
2
 
3
3
  require 'bigdecimal'
4
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
4
5
  require 'active_record/connection_adapters/abstract/schema_definitions'
6
+ require 'arjdbc/abstract/core'
7
+ require 'arjdbc/abstract/connection_management'
8
+ require 'arjdbc/abstract/database_statements'
9
+ require 'arjdbc/abstract/statement_cache'
10
+ require 'arjdbc/abstract/transaction_support'
5
11
 
6
- module ArJdbc
7
- module MySQL
8
-
9
- require 'arjdbc/mysql/column'
10
- require 'arjdbc/mysql/bulk_change_table'
11
- require 'arjdbc/mysql/explain_support'
12
- require 'arjdbc/mysql/schema_creation' # AR 4.x
13
-
14
- include BulkChangeTable if const_defined? :BulkChangeTable
15
-
16
- # @private
17
- ActiveRecordError = ::ActiveRecord::ActiveRecordError
18
-
19
- # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
20
- def self.jdbc_connection_class
21
- ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
22
- end
23
-
24
- def jdbc_column_class
25
- ::ActiveRecord::ConnectionAdapters::MysqlAdapter::Column
26
- end
27
-
28
- # @private
29
- def init_connection(jdbc_connection)
30
- meta = jdbc_connection.meta_data
31
- if meta.driver_major_version == 1 # TODO check in driver code
32
- # assumes MariaDB 1.x currently
33
- elsif meta.driver_major_version < 5
34
- raise ::ActiveRecord::ConnectionNotEstablished,
35
- "MySQL adapter requires driver >= 5.0 got: '#{meta.driver_version}'"
36
- elsif meta.driver_major_version == 5 && meta.driver_minor_version < 1
37
- config[:connection_alive_sql] ||= 'SELECT 1' # need 5.1 for JDBC 4.0
38
- else
39
- # NOTE: since the loaded Java driver class can't change :
40
- MySQL.send(:remove_method, :init_connection) rescue nil
41
- end
42
- end
43
-
44
- def configure_connection
45
- variables = config[:variables] || {}
46
- # By default, MySQL 'where id is null' selects the last inserted id. Turn this off.
47
- variables[:sql_auto_is_null] = 0 # execute "SET SQL_AUTO_IS_NULL=0"
48
-
49
- # Increase timeout so the server doesn't disconnect us.
50
- wait_timeout = config[:wait_timeout]
51
- wait_timeout = self.class.type_cast_config_to_integer(wait_timeout)
52
- variables[:wait_timeout] = wait_timeout.is_a?(Fixnum) ? wait_timeout : 2147483
53
-
54
- # Make MySQL reject illegal values rather than truncating or blanking them, see
55
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
56
- # If the user has provided another value for sql_mode, don't replace it.
57
- if strict_mode? && ! variables.has_key?(:sql_mode)
58
- variables[:sql_mode] = 'STRICT_ALL_TABLES' # SET SQL_MODE='STRICT_ALL_TABLES'
59
- end
60
-
61
- # NAMES does not have an equals sign, see
62
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
63
- # (trailing comma because variable_assignments will always have content)
64
- encoding = "NAMES #{config[:encoding]}, " if config[:encoding]
65
-
66
- # Gather up all of the SET variables...
67
- variable_assignments = variables.map do |k, v|
68
- if v == ':default' || v == :default
69
- "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
70
- elsif ! v.nil?
71
- "@@SESSION.#{k.to_s} = #{quote(v)}"
72
- end
73
- # or else nil; compact to clear nils out
74
- end.compact.join(', ')
75
-
76
- # ...and send them all in one query
77
- execute("SET #{encoding} #{variable_assignments}", :skip_logging)
78
- end
79
-
80
- def strict_mode?
81
- config.key?(:strict) ?
82
- self.class.type_cast_config_to_boolean(config[:strict]) :
83
- AR40 # strict_mode is default since AR 4.0
84
- end
85
-
86
- # @private
87
- @@emulate_booleans = true
88
-
89
- # Boolean emulation can be disabled using (or using the adapter method) :
90
- #
91
- # ArJdbc::MySQL.emulate_booleans = false
92
- #
93
- # @see ActiveRecord::ConnectionAdapters::MysqlAdapter#emulate_booleans
94
- def self.emulate_booleans?; @@emulate_booleans; end
95
- # @deprecated Use {#emulate_booleans?} instead.
96
- def self.emulate_booleans; @@emulate_booleans; end
97
- # @see #emulate_booleans?
98
- def self.emulate_booleans=(emulate); @@emulate_booleans = emulate; end
99
-
100
- NATIVE_DATABASE_TYPES = {
101
- :primary_key => "int(11) auto_increment PRIMARY KEY",
102
- :string => { :name => "varchar", :limit => 255 },
103
- :text => { :name => "text" },
104
- :integer => { :name => "int", :limit => 4 },
105
- :float => { :name => "float" },
106
- # :double => { :name=>"double", :limit=>17 }
107
- # :real => { :name=>"real", :limit=>17 }
108
- :numeric => { :name => "numeric" }, # :limit => 65
109
- :decimal => { :name => "decimal" }, # :limit => 65
110
- :datetime => { :name => "datetime" },
111
- # TIMESTAMP has varying properties depending on MySQL version (SQL mode)
112
- :timestamp => { :name => "datetime" },
113
- :time => { :name => "time" },
114
- :date => { :name => "date" },
115
- :binary => { :name => "blob" },
116
- :boolean => { :name => "tinyint", :limit => 1 },
117
- # AR-JDBC added :
118
- :bit => { :name => "bit" }, # :limit => 1
119
- :enum => { :name => "enum" },
120
- :set => { :name => "set" }, # :limit => 64
121
- :char => { :name => "char" }, # :limit => 255
122
- }
123
-
124
- # @override
125
- def native_database_types
126
- NATIVE_DATABASE_TYPES
127
- end
128
-
129
- ADAPTER_NAME = 'MySQL'.freeze
130
-
131
- # @override
132
- def adapter_name
133
- ADAPTER_NAME
134
- end
135
-
136
- def case_sensitive_equality_operator
137
- "= BINARY"
138
- end
139
-
140
- def case_sensitive_modifier(node)
141
- Arel::Nodes::Bin.new(node)
142
- end unless AR42
143
-
144
- def case_sensitive_modifier(node, table_attribute)
145
- node = Arel::Nodes.build_quoted node, table_attribute
146
- Arel::Nodes::Bin.new(node)
147
- end if AR42
148
-
149
- def case_sensitive_comparison(table, attribute, column, value)
150
- if column.case_sensitive?
151
- table[attribute].eq(value)
152
- else
153
- super
154
- end
155
- end if AR42
156
-
157
- def case_insensitive_comparison(table, attribute, column, value)
158
- if column.case_sensitive?
159
- super
160
- else
161
- table[attribute].eq(value)
162
- end
163
- end if AR42
164
-
165
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
166
- where_sql
167
- end
168
-
169
- def initialize_schema_migrations_table
170
- if @config[:encoding] == 'utf8mb4'
171
- ActiveRecord::SchemaMigration.create_table(191)
172
- else
173
- ActiveRecord::SchemaMigration.create_table
174
- end
175
- end if AR40
176
-
177
- # HELPER METHODS ===========================================
178
-
179
- # @private Only for Rails core compatibility.
180
- def new_column(field, default, type, null, collation, extra = "")
181
- jdbc_column_class.new(field, default, type, null, collation, strict_mode?, extra)
182
- end unless AR42
183
-
184
- # @private Only for Rails core compatibility.
185
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "")
186
- jdbc_column_class.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
187
- end if AR42
188
-
189
- # @private Only for Rails core compatibility.
190
- def error_number(exception)
191
- exception.error_code if exception.respond_to?(:error_code)
192
- end
193
-
194
- # QUOTING ==================================================
195
-
196
- # @override
197
- def quote(value, column = nil)
198
- return value.quoted_id if value.respond_to?(:quoted_id)
199
- return value if sql_literal?(value)
200
- return value.to_s if column && column.type == :primary_key
201
-
202
- if value.kind_of?(String) && column && column.type == :binary
203
- "x'#{value.unpack("H*")[0]}'"
204
- elsif value.kind_of?(BigDecimal)
205
- value.to_s("F")
206
- else
207
- super
208
- end
209
- end unless AR42
210
-
211
- # @private since AR 4.2
212
- def _quote(value)
213
- if value.is_a?(Type::Binary::Data)
214
- "x'#{value.hex}'"
215
- else
216
- super
217
- end
218
- end if AR42
219
-
220
- # @override
221
- def quote_column_name(name)
222
- "`#{name.to_s.gsub('`', '``')}`"
223
- end
224
-
225
- # @override
226
- def quote_table_name(name)
227
- quote_column_name(name).gsub('.', '`.`')
228
- end
229
-
230
- # @override
231
- def supports_migrations?
232
- true
233
- end
234
-
235
- # @override
236
- def supports_primary_key?
237
- true
238
- end
239
-
240
- # @override
241
- def supports_index_sort_order?
242
- # Technically MySQL allows to create indexes with the sort order syntax
243
- # but at the moment (5.5) it doesn't yet implement them.
244
- true
245
- end
246
-
247
- # @override
248
- def supports_indexes_in_create?
249
- true
250
- end
251
-
252
- # @override
253
- def supports_transaction_isolation?
254
- # MySQL 4 technically support transaction isolation, but it is affected by
255
- # a bug where the transaction level gets persisted for the whole session:
256
- # http://bugs.mysql.com/bug.php?id=39170
257
- version[0] && version[0] >= 5
258
- end
259
-
260
- # @override
261
- def supports_views?
262
- version[0] && version[0] >= 5
263
- end
264
-
265
- def supports_rename_index?
266
- return false if mariadb? || ! version[0]
267
- (version[0] == 5 && version[1] >= 7) || version[0] >= 6
268
- end
269
-
270
- def index_algorithms
271
- { :default => 'ALGORITHM = DEFAULT', :copy => 'ALGORITHM = COPY', :inplace => 'ALGORITHM = INPLACE' }
272
- end if AR42
273
-
274
- # @override
275
- def supports_transaction_isolation?(level = nil)
276
- version[0] && version[0] >= 5 # MySQL 5+
277
- end
278
-
279
- # NOTE: handled by JdbcAdapter only to have statements in logs :
280
-
281
- # @override
282
- def supports_savepoints?
283
- true
284
- end
285
-
286
- # @override
287
- def create_savepoint(name = current_savepoint_name(true))
288
- log("SAVEPOINT #{name}", 'Savepoint') { super }
289
- end
290
-
291
- # @override
292
- def rollback_to_savepoint(name = current_savepoint_name(true))
293
- log("ROLLBACK TO SAVEPOINT #{name}", 'Savepoint') { super }
294
- end
295
-
296
- # @override
297
- def release_savepoint(name = current_savepoint_name(false))
298
- log("RELEASE SAVEPOINT #{name}", 'Savepoint') { super }
299
- end
300
-
301
- def disable_referential_integrity
302
- fk_checks = select_value("SELECT @@FOREIGN_KEY_CHECKS")
303
- begin
304
- update("SET FOREIGN_KEY_CHECKS = 0")
305
- yield
306
- ensure
307
- update("SET FOREIGN_KEY_CHECKS = #{fk_checks}")
308
- end
309
- end
310
-
311
- # @override make it public just like native MySQL adapter does
312
- def update_sql(sql, name = nil)
313
- super
314
- end
315
-
316
- # SCHEMA STATEMENTS ========================================
317
-
318
- # @deprecated no longer used - handled with (AR built-in) Rake tasks
319
- def structure_dump
320
- # NOTE: due AR (2.3-3.2) compatibility views are not included
321
- if supports_views?
322
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
323
- else
324
- sql = "SHOW TABLES"
325
- end
326
-
327
- @connection.execute_query_raw(sql).map do |table|
328
- # e.g. { "Tables_in_arjdbc_test"=>"big_fields", "Table_type"=>"BASE TABLE" }
329
- table.delete('Table_type')
330
- table_name = table.to_a.first.last
331
-
332
- create_table = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")
333
-
334
- "#{create_table['Create Table']};\n\n"
335
- end.join
336
- end
337
-
338
- # Returns just a table's primary key.
339
- # @override
340
- def primary_key(table)
341
- #pk_and_sequence = pk_and_sequence_for(table)
342
- #pk_and_sequence && pk_and_sequence.first
343
- @connection.primary_keys(table).first
344
- end
345
-
346
- # Returns a table's primary key and belonging sequence.
347
- # @note Not used, only here for potential compatibility with native adapter.
348
- # @override
349
- def pk_and_sequence_for(table)
350
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA').first
351
- if result['Create Table'].to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
352
- keys = $1.split(","); keys.map! { |key| key.gsub(/[`"]/, "") }
353
- return keys.length == 1 ? [ keys.first, nil ] : nil
354
- else
355
- return nil
356
- end
357
- end
358
-
359
- # @private
360
- IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
361
-
362
- INDEX_TYPES = [ :fulltext, :spatial ] if AR40
363
- INDEX_USINGS = [ :btree, :hash ] if AR40
364
-
365
- # Returns an array of indexes for the given table.
366
- # @override
367
- def indexes(table_name, name = nil)
368
- indexes = []
369
- current_index = nil
370
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name || 'SCHEMA')
371
- result.each do |row|
372
- key_name = row['Key_name']
373
- if current_index != key_name
374
- next if key_name == 'PRIMARY' # skip the primary key
375
- current_index = key_name
376
- indexes <<
377
- if self.class.const_defined?(:INDEX_TYPES) # AR 4.0
378
- mysql_index_type = row['Index_type'].downcase.to_sym
379
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
380
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
381
- IndexDefinition.new(row['Table'], key_name, row['Non_unique'].to_i == 0, [], [], nil, nil, index_type, index_using)
382
- else
383
- IndexDefinition.new(row['Table'], key_name, row['Non_unique'].to_i == 0, [], [])
384
- end
385
- end
386
-
387
- indexes.last.columns << row["Column_name"]
388
- indexes.last.lengths << row["Sub_part"]
389
- end
390
- indexes
391
- end
392
-
393
- # Returns an array of `Column` objects for the table specified.
394
- # @override
395
- def columns(table_name, name = nil)
396
- sql = "SHOW FULL #{AR40 ? 'FIELDS' : 'COLUMNS'} FROM #{quote_table_name(table_name)}"
397
- columns = execute(sql, name || 'SCHEMA')
398
- strict = strict_mode?
399
- pass_cast_type = respond_to?(:lookup_cast_type)
400
- columns.map! do |field|
401
- sql_type = field['Type']
402
- null = field['Null'] == "YES"
403
- if pass_cast_type
404
- cast_type = lookup_cast_type(sql_type)
405
- jdbc_column_class.new(field['Field'], field['Default'], cast_type, sql_type, null, field['Collation'], strict, field['Extra'])
406
- else
407
- jdbc_column_class.new(field['Field'], field['Default'], sql_type, null, field['Collation'], strict, field['Extra'])
408
- end
409
- end
410
- columns
411
- end
412
-
413
- if defined? ::ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
414
-
415
- class SchemaCreation < ::ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
416
-
417
- # @private
418
- def visit_AddColumn(o)
419
- add_column_position!(super, column_options(o))
420
- end
421
-
422
- # @private re-defined since AR 4.1
423
- def visit_ChangeColumnDefinition(o)
424
- column = o.column
425
- options = o.options
426
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
427
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
428
- add_column_options!(change_column_sql, options.merge(:column => column))
429
- add_column_position!(change_column_sql, options)
430
- end
431
-
432
- # @private since AR 4.2
433
- def visit_DropForeignKey(name)
434
- "DROP FOREIGN KEY #{name}"
435
- end
436
-
437
- # @private since AR 4.2
438
- def visit_TableDefinition(o)
439
- name = o.name
440
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
441
-
442
- statements = o.columns.map { |c| accept c }
443
- statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
444
-
445
- create_sql << "(#{statements.join(', ')}) " if statements.present?
446
- create_sql << "#{o.options}"
447
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
448
- create_sql
449
- end if AR42
450
-
451
- private
452
-
453
- def add_column_position!(sql, options)
454
- if options[:first]
455
- sql << " FIRST"
456
- elsif options[:after]
457
- sql << " AFTER #{quote_column_name(options[:after])}"
458
- end
459
- sql
460
- end
461
-
462
- def column_options(o)
463
- column_options = {}
464
- column_options[:null] = o.null unless o.null.nil?
465
- column_options[:default] = o.default unless o.default.nil?
466
- column_options[:column] = o
467
- column_options[:first] = o.first
468
- column_options[:after] = o.after
469
- column_options
470
- end
471
-
472
- def index_in_create(table_name, column_name, options)
473
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
474
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
475
- end
476
-
477
- end
478
-
479
- def schema_creation; SchemaCreation.new self end
480
-
481
- end
482
-
483
- # @private
484
- def recreate_database(name, options = {})
485
- drop_database(name)
486
- create_database(name, options)
487
- reconnect!
488
- end
489
-
490
- # @override
491
- def create_database(name, options = {})
492
- if options[:collation]
493
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
494
- else
495
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
496
- end
497
- end
498
-
499
- # @override
500
- def drop_database(name)
501
- execute "DROP DATABASE IF EXISTS `#{name}`"
502
- end
503
-
504
- def current_database
505
- select_one("SELECT DATABASE() as db")['db']
506
- end
507
-
508
- def truncate(table_name, name = nil)
509
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
510
- end
511
-
512
- # @override
513
- def create_table(name, options = {})
514
- super(name, { :options => "ENGINE=InnoDB" }.merge(options))
515
- end
516
-
517
- def drop_table(table_name, options = {})
518
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
519
- end
520
-
521
- # @override
522
- def rename_table(table_name, new_name)
523
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
524
- rename_table_indexes(table_name, new_name) if respond_to?(:rename_table_indexes) # AR-4.0 SchemaStatements
525
- end
526
-
527
- # @override
528
- def remove_index!(table_name, index_name)
529
- # missing table_name quoting in AR-2.3
530
- execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
531
- end
532
-
533
- # @override
534
- def rename_index(table_name, old_name, new_name)
535
- if supports_rename_index?
536
- validate_index_length!(table_name, new_name) if respond_to?(:validate_index_length!)
537
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
538
- else
539
- super
540
- end
541
- end
542
-
543
- # @private
544
- ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition if ::ActiveRecord::ConnectionAdapters.const_defined? :ForeignKeyDefinition
545
-
546
- # @override
547
- def supports_foreign_keys?; true end
548
-
549
- def foreign_keys(table_name)
550
- fk_info = select_all "" <<
551
- "SELECT fk.referenced_table_name as 'to_table' " <<
552
- ",fk.referenced_column_name as 'primary_key' " <<
553
- ",fk.column_name as 'column' " <<
554
- ",fk.constraint_name as 'name' " <<
555
- "FROM information_schema.key_column_usage fk " <<
556
- "WHERE fk.referenced_column_name is not null " <<
557
- "AND fk.table_schema = '#{current_database}' " <<
558
- "AND fk.table_name = '#{table_name}'"
559
-
560
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
561
-
562
- fk_info.map! do |row|
563
- options = {
564
- :column => row['column'], :name => row['name'], :primary_key => row['primary_key']
565
- }
566
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
567
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
568
-
569
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
570
- end
571
- end if defined? ForeignKeyDefinition
572
-
573
- def extract_foreign_key_action(structure, name, action)
574
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
575
- case $1
576
- when 'CASCADE'; :cascade
577
- when 'SET NULL'; :nullify
578
- end
579
- end
12
+ module ActiveRecord
13
+ class ConnectionAdapters::AbstractMysqlAdapter
14
+ # FIXME: this is to work around abstract mysql having 4 arity but core wants to pass 3
15
+ # FIXME: Missing the logic from original module.
16
+ def initialize(connection, logger, config)
17
+ super(connection, logger, config)
580
18
  end
581
- private :extract_foreign_key_action
582
-
583
- # @override
584
- def add_column(table_name, column_name, type, options = {})
585
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
586
- add_column_options!(add_column_sql, options)
587
- add_column_position!(add_column_sql, options)
588
- execute(add_column_sql)
589
- end unless const_defined? :SchemaCreation
590
-
591
- def change_column_default(table_name, column_name, default)
592
- column = column_for(table_name, column_name)
593
- change_column table_name, column_name, column.sql_type, :default => default
594
- end # unless const_defined? :SchemaCreation
19
+ end
20
+ module ConnectionAdapters
21
+ # Remove any vestiges of core/Ruby MySQL adapter
22
+ remove_const(:Mysql2Adapter) if const_defined?(:Mysql2Adapter)
595
23
 
596
- def change_column_null(table_name, column_name, null, default = nil)
597
- column = column_for(table_name, column_name)
24
+ class Mysql2Adapter < AbstractMysqlAdapter
25
+ ADAPTER_NAME = 'Mysql2'.freeze
598
26
 
599
- unless null || default.nil?
600
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
601
- end
27
+ include ArJdbc::Abstract::Core
28
+ include ArJdbc::Abstract::ConnectionManagement
29
+ include ArJdbc::Abstract::DatabaseStatements
30
+ include ArJdbc::Abstract::StatementCache
31
+ include ArJdbc::Abstract::TransactionSupport
602
32
 
603
- change_column table_name, column_name, column.sql_type, :null => null
604
- end # unless const_defined? :SchemaCreation
33
+ def initialize(connection, logger, connection_options, config)
34
+ super(connection, logger, config)
605
35
 
606
- # @override
607
- def change_column(table_name, column_name, type, options = {})
608
- column = column_for(table_name, column_name)
36
+ @prepared_statements = false unless config.key?(:prepared_statements)
609
37
 
610
- unless options_include_default?(options)
611
- # NOTE: no defaults for BLOB/TEXT columns with MySQL
612
- options[:default] = column.default if type != :text && type != :binary
38
+ configure_connection
613
39
  end
614
40
 
615
- unless options.has_key?(:null)
616
- options[:null] = column.null
41
+ def supports_json?
42
+ !mariadb? && version >= '5.7.8'
617
43
  end
618
44
 
619
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
620
- add_column_options!(change_column_sql, options)
621
- add_column_position!(change_column_sql, options)
622
- execute(change_column_sql)
623
- end
624
-
625
- # @private
626
- def change_column(table_name, column_name, type, options = {})
627
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
628
- end if AR42
629
-
630
- # @override
631
- def rename_column(table_name, column_name, new_column_name)
632
- options = {}
633
-
634
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
635
- type = column.type
636
- options[:default] = column.default if type != :text && type != :binary
637
- options[:null] = column.null
638
- else
639
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
640
- end
641
-
642
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
643
-
644
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
645
- add_column_options!(rename_column_sql, options)
646
- execute(rename_column_sql)
647
- rename_column_indexes(table_name, column_name, new_column_name) if respond_to?(:rename_column_indexes) # AR-4.0 SchemaStatements
648
- end
649
-
650
- def add_column_position!(sql, options)
651
- if options[:first]
652
- sql << " FIRST"
653
- elsif options[:after]
654
- sql << " AFTER #{quote_column_name(options[:after])}"
45
+ def supports_comments?
46
+ true
655
47
  end
656
- end unless const_defined? :SchemaCreation
657
48
 
658
- # @note Only used with (non-AREL) ActiveRecord **2.3**.
659
- # @see Arel::Visitors::MySQL
660
- def add_limit_offset!(sql, options)
661
- limit, offset = options[:limit], options[:offset]
662
- if limit && offset
663
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
664
- elsif limit
665
- sql << " LIMIT #{sanitize_limit(limit)}"
666
- elsif offset
667
- sql << " OFFSET #{offset.to_i}"
49
+ def supports_comments_in_create?
50
+ true
668
51
  end
669
- sql
670
- end if ::ActiveRecord::VERSION::MAJOR < 3
671
-
672
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
673
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
674
- # these, we must use a subquery. However, MySQL is too stupid to create a
675
- # temporary table for this automatically, so we have to give it some prompting
676
- # in the form of a subsubquery. Ugh!
677
- # @private based on mysql_adapter.rb from 3.1-stable
678
- def join_to_update(update, select)
679
- if select.limit || select.offset || select.orders.any?
680
- subsubselect = select.clone
681
- subsubselect.projections = [update.key]
682
52
 
683
- subselect = Arel::SelectManager.new(select.engine)
684
- subselect.project Arel.sql(update.key.name)
685
- subselect.from subsubselect.as('__active_record_temp')
686
-
687
- update.where update.key.in(subselect)
688
- else
689
- update.table select.source
690
- update.wheres = select.constraints
53
+ def supports_savepoints?
54
+ true
691
55
  end
692
- end
693
-
694
- def show_variable(var)
695
- res = execute("show variables like '#{var}'")
696
- result_row = res.detect {|row| row["Variable_name"] == var }
697
- result_row && result_row["Value"]
698
- end
699
-
700
- def charset
701
- show_variable("character_set_database")
702
- end
703
-
704
- def collation
705
- show_variable("collation_database")
706
- end
707
56
 
708
- # Maps logical Rails types to MySQL-specific data types.
709
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
710
- case type.to_s
711
- when 'binary'
712
- case limit
713
- when 0..0xfff; "varbinary(#{limit})"
714
- when nil; "blob"
715
- when 0x1000..0xffffffff; "blob(#{limit})"
716
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
717
- end
718
- when 'integer'
719
- case limit
720
- when 1; 'tinyint'
721
- when 2; 'smallint'
722
- when 3; 'mediumint'
723
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
724
- when 5..8; 'bigint'
725
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
726
- end
727
- when 'text'
728
- case limit
729
- when 0..0xff; 'tinytext'
730
- when nil, 0x100..0xffff; 'text'
731
- when 0x10000..0xffffff; 'mediumtext'
732
- when 0x1000000..0xffffffff; 'longtext'
733
- else raise(ActiveRecordError, "No text type has character length #{limit}")
734
- end
735
- when 'datetime'
736
- return super unless precision
57
+ # HELPER METHODS ===========================================
737
58
 
738
- case precision
739
- when 0..6; "datetime(#{precision})"
740
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
741
- end
742
- else
59
+ # Reloading the type map in abstract/statement_cache.rb blows up postgres
60
+ def clear_cache!
61
+ reload_type_map
743
62
  super
744
63
  end
745
- end
746
-
747
- # @override
748
- def empty_insert_statement_value
749
- "VALUES ()"
750
- end
751
-
752
- # @note since AR 4.2
753
- def valid_type?(type)
754
- ! native_database_types[type].nil?
755
- end
756
-
757
- def clear_cache!
758
- super
759
- reload_type_map
760
- end if AR42
761
-
762
- # @private since AR 4.2
763
- def prepare_column_options(column, types)
764
- spec = super
765
- spec.delete(:limit) if column.type == :boolean
766
- spec
767
- end if AR42
768
-
769
- # @private
770
- Type = ActiveRecord::Type if AR42
771
-
772
- protected
773
-
774
- # @private
775
- def initialize_type_map(m)
776
- super
777
-
778
- register_class_with_limit m, %r(char)i, MysqlString
779
-
780
- m.register_type %r(tinytext)i, Type::Text.new(:limit => 2**8 - 1)
781
- m.register_type %r(tinyblob)i, Type::Binary.new(:limit => 2**8 - 1)
782
- m.register_type %r(text)i, Type::Text.new(:limit => 2**16 - 1)
783
- m.register_type %r(blob)i, Type::Binary.new(:limit => 2**16 - 1)
784
- m.register_type %r(mediumtext)i, Type::Text.new(:limit => 2**24 - 1)
785
- m.register_type %r(mediumblob)i, Type::Binary.new(:limit => 2**24 - 1)
786
- m.register_type %r(longtext)i, Type::Text.new(:limit => 2**32 - 1)
787
- m.register_type %r(longblob)i, Type::Binary.new(:limit => 2**32 - 1)
788
- m.register_type %r(^float)i, Type::Float.new(:limit => 24)
789
- m.register_type %r(^double)i, Type::Float.new(:limit => 53)
790
-
791
- register_integer_type m, %r(^bigint)i, :limit => 8
792
- register_integer_type m, %r(^int)i, :limit => 4
793
- register_integer_type m, %r(^mediumint)i, :limit => 3
794
- register_integer_type m, %r(^smallint)i, :limit => 2
795
- register_integer_type m, %r(^tinyint)i, :limit => 1
796
64
 
797
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
798
- m.alias_type %r(set)i, 'varchar'
799
- m.alias_type %r(year)i, 'integer'
800
- m.alias_type %r(bit)i, 'binary'
801
-
802
- m.register_type(%r(datetime)i) do |sql_type|
803
- precision = extract_precision(sql_type)
804
- MysqlDateTime.new(:precision => precision)
805
- end
806
-
807
- m.register_type(%r(enum)i) do |sql_type|
808
- limit = sql_type[/^enum\((.+)\)/i, 1].split(',').
809
- map{|enum| enum.strip.length - 2}.max
810
- MysqlString.new(:limit => limit)
811
- end
812
- end if AR42
813
-
814
- # @private
815
- def register_integer_type(mapping, key, options)
816
- mapping.register_type(key) do |sql_type|
817
- if /unsigned/i =~ sql_type
818
- Type::UnsignedInteger.new(options)
65
+ def each_hash(result) # :nodoc:
66
+ if block_given?
67
+ # FIXME: This is C in mysql2 gem and I just made simplest Ruby
68
+ result.each do |row|
69
+ new_hash = {}
70
+ row.each { |k, v| new_hash[k.to_sym] = v }
71
+ yield new_hash
72
+ end
819
73
  else
820
- Type::Integer.new(options)
74
+ to_enum(:each_hash, result)
821
75
  end
822
76
  end
823
- end if AR42
824
-
825
- # MySQL is too stupid to create a temporary table for use subquery, so we have
826
- # to give it some prompting in the form of a subsubquery. Ugh!
827
- # @note since AR 4.2
828
- def subquery_for(key, select)
829
- subsubselect = select.clone
830
- subsubselect.projections = [key]
831
-
832
- subselect = Arel::SelectManager.new(select.engine)
833
- subselect.project Arel.sql(key.name)
834
- subselect.from subsubselect.as('__active_record_temp')
835
- end if AR42
836
-
837
- def quoted_columns_for_index(column_names, options = {})
838
- length = options[:length] if options.is_a?(Hash)
839
-
840
- case length
841
- when Hash
842
- column_names.map { |name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
843
- when Fixnum
844
- column_names.map { |name| "#{quote_column_name(name)}(#{length})" }
845
- else
846
- column_names.map { |name| quote_column_name(name) }
847
- end
848
- end
849
77
 
850
- # @override
851
- def translate_exception(exception, message)
852
- return super unless exception.respond_to?(:errno)
853
-
854
- case exception.errno
855
- when 1062
856
- ::ActiveRecord::RecordNotUnique.new(message, exception)
857
- when 1452
858
- ::ActiveRecord::InvalidForeignKey.new(message, exception)
859
- else
860
- super
78
+ def error_number(exception)
79
+ exception.errno if exception.respond_to? :errno
861
80
  end
862
- end
863
-
864
- private
865
81
 
866
- def column_for(table_name, column_name)
867
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
868
- raise "No such column: #{table_name}.#{column_name}"
82
+ # FIXME: #833 This is wrong...it should not always pass utf8 and native adapter never does...
83
+ def create_table(table_name, **options) #:nodoc:
84
+ super(table_name, options: 'ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci', **options)
869
85
  end
870
- column
871
- end
872
86
 
873
- def mariadb?; !! ( full_version =~ /mariadb/i ) end
87
+ #--
88
+ # QUOTING ==================================================
89
+ #+
874
90
 
875
- def version
876
- return @version ||= begin
877
- version = []
878
- java_connection = jdbc_connection(true)
879
- if java_connection.java_class.name == 'com.mysql.jdbc.ConnectionImpl'
880
- version << jdbc_connection.serverMajorVersion
881
- version << jdbc_connection.serverMinorVersion
882
- version << jdbc_connection.serverSubMinorVersion
883
- else
884
- if match = full_version.match(/^(\d+)\.(\d+)\.(\d+)/)
885
- version << match[1].to_i
886
- version << match[2].to_i
887
- version << match[3].to_i
888
- end
889
- end
890
- version.freeze
91
+ # FIXME: 5.1 crashes without this. I think this is Arel hitting a fallback path in to_sql.rb.
92
+ # So maybe an untested code path in their source. Still means we are doing something wrong to
93
+ # even hit it.
94
+ def quote(value, comment=nil)
95
+ super(value)
891
96
  end
892
- end
893
97
 
894
- def full_version
895
- @full_version ||= begin
896
- result = execute 'SELECT VERSION()', 'SCHEMA'
897
- result.first.values.first # [{"VERSION()"=>"5.5.37-0ubuntu..."}]
98
+ def quote_string(string)
99
+ string.gsub(/[\x00\n\r\\\'\"]/, '\\\\\0')
898
100
  end
899
- end
900
-
901
- # @private
902
- def emulate_booleans; ::ArJdbc::MySQL.emulate_booleans?; end # due AR 4.2
903
- public :emulate_booleans
904
-
905
- # @private
906
- class MysqlDateTime < Type::DateTime
907
- private
908
101
 
909
- def has_precision?
910
- precision || 0
911
- end
912
- end if AR42
913
-
914
- # @private
915
- class MysqlString < Type::String
916
- def type_cast_for_database(value)
917
- case value
918
- when true then "1"
919
- when false then "0"
920
- else super
921
- end
102
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
103
+ last_id = if without_prepared_statement?(binds)
104
+ log(sql, name) { @connection.execute_insert(sql) }
105
+ else
106
+ log(sql, name, binds) { @connection.execute_insert(sql, binds) }
107
+ end
108
+ # FIXME: execute_insert and executeUpdate mapping key results is very varied and I am wondering
109
+ # if AR is now much more consistent. I worked around by manually making a result here.
110
+ ::ActiveRecord::Result.new(nil, [[last_id]])
922
111
  end
112
+ alias insert_sql exec_insert
113
+ deprecate insert_sql: :insert
923
114
 
924
115
  private
925
116
 
926
- def cast_value(value)
927
- case value
928
- when true then "1"
929
- when false then "0"
930
- else super
117
+ def full_version
118
+ @full_version ||= begin
119
+ result = execute 'SELECT VERSION()', 'SCHEMA'
120
+ result.first.values.first # [{"VERSION()"=>"5.5.37-0ubuntu..."}]
931
121
  end
932
122
  end
933
- end if AR42
934
-
935
- end
936
- end
937
-
938
- module ActiveRecord
939
- module ConnectionAdapters
940
- # Remove any vestiges of core/Ruby MySQL adapter
941
- remove_const(:MysqlAdapter) if const_defined?(:MysqlAdapter)
942
-
943
- class MysqlAdapter < JdbcAdapter
944
- include ::ArJdbc::MySQL
945
- include ::ArJdbc::MySQL::ExplainSupport
946
-
947
- def arel_visitor # :nodoc:
948
- Arel::Visitors::MySQL.new(self)
949
- end
950
-
951
- # By default, the MysqlAdapter will consider all columns of type
952
- # __tinyint(1)__ as boolean. If you wish to disable this :
953
- # ```
954
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
955
- # ```
956
- def self.emulate_booleans?; ::ArJdbc::MySQL.emulate_booleans?; end
957
- def self.emulate_booleans; ::ArJdbc::MySQL.emulate_booleans?; end # native adapter
958
- def self.emulate_booleans=(emulate); ::ArJdbc::MySQL.emulate_booleans = emulate; end
959
-
960
- class Column < JdbcColumn
961
- include ::ArJdbc::MySQL::Column
962
-
963
- # @note {#ArJdbc::MySQL::Column} uses this to check for boolean emulation
964
- def adapter
965
- MysqlAdapter
966
- end
967
-
968
- end
969
-
970
- #def initialize(*args)
971
- # super # configure_connection happens in super
972
- #end
973
123
 
974
124
  def jdbc_connection_class(spec)
975
- ::ArJdbc::MySQL.jdbc_connection_class
125
+ ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
976
126
  end
977
127
 
978
128
  def jdbc_column_class
979
- Column
129
+ ::ActiveRecord::ConnectionAdapters::MySQL::Column
980
130
  end
981
131
 
982
132
  end
133
+ end
983
134
 
984
- if ActiveRecord::VERSION::MAJOR < 3 ||
985
- ( ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR <= 1 )
986
- remove_const(:MysqlColumn) if const_defined?(:MysqlColumn)
987
- MysqlColumn = MysqlAdapter::Column
988
- end
989
-
990
- if ActiveRecord::VERSION::MAJOR > 3 ||
991
- ( ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR >= 1 )
992
- remove_const(:Mysql2Adapter) if const_defined?(:Mysql2Adapter)
993
- Mysql2Adapter = MysqlAdapter
994
- if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 1
995
- remove_const(:Mysql2Column) if const_defined?(:Mysql2Column)
996
- Mysql2Column = MysqlAdapter::Column
135
+ # FIXME: #834 Not sure how this is scoped or whether we should use it or just alias it to our
136
+ # JDBCError.
137
+ class ::Mysql2
138
+ class Error < Exception
139
+ def initialize(*)
140
+ super("error")
997
141
  end
998
142
  end
999
-
1000
143
  end
1001
144
  end