activerecord-jdbc-adapter 1.2.5 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +5 -1
  3. data/Appraisals +5 -5
  4. data/Gemfile +9 -1
  5. data/Gemfile.lock +44 -10
  6. data/History.txt +126 -2
  7. data/README.md +246 -0
  8. data/Rakefile +34 -25
  9. data/activerecord-jdbc-adapter.gemspec +1 -1
  10. data/gemfiles/rails23.gemfile +5 -3
  11. data/gemfiles/rails23.gemfile.lock +26 -18
  12. data/gemfiles/rails30.gemfile +4 -2
  13. data/gemfiles/rails30.gemfile.lock +16 -8
  14. data/gemfiles/rails31.gemfile +4 -2
  15. data/gemfiles/rails31.gemfile.lock +16 -9
  16. data/gemfiles/rails32.gemfile +4 -2
  17. data/gemfiles/rails32.gemfile.lock +15 -8
  18. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  19. data/lib/arel/visitors/sql_server.rb +3 -0
  20. data/lib/arjdbc.rb +3 -5
  21. data/lib/arjdbc/db2.rb +1 -0
  22. data/lib/arjdbc/db2/adapter.rb +302 -196
  23. data/lib/arjdbc/db2/connection_methods.rb +18 -0
  24. data/lib/arjdbc/derby/active_record_patch.rb +12 -0
  25. data/lib/arjdbc/derby/adapter.rb +180 -158
  26. data/lib/arjdbc/derby/connection_methods.rb +5 -1
  27. data/lib/arjdbc/firebird/adapter.rb +27 -19
  28. data/lib/arjdbc/h2/adapter.rb +162 -7
  29. data/lib/arjdbc/h2/connection_methods.rb +5 -1
  30. data/lib/arjdbc/hsqldb.rb +1 -1
  31. data/lib/arjdbc/hsqldb/adapter.rb +96 -61
  32. data/lib/arjdbc/hsqldb/connection_methods.rb +5 -1
  33. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  34. data/lib/arjdbc/informix/adapter.rb +56 -55
  35. data/lib/arjdbc/jdbc/adapter.rb +173 -86
  36. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  37. data/lib/arjdbc/jdbc/column.rb +28 -23
  38. data/lib/arjdbc/jdbc/connection.rb +10 -6
  39. data/lib/arjdbc/jdbc/driver.rb +13 -5
  40. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +21 -0
  41. data/lib/arjdbc/mssql.rb +1 -1
  42. data/lib/arjdbc/mssql/adapter.rb +51 -53
  43. data/lib/arjdbc/mssql/connection_methods.rb +8 -1
  44. data/lib/arjdbc/mysql.rb +1 -1
  45. data/lib/arjdbc/mysql/adapter.rb +186 -150
  46. data/lib/arjdbc/mysql/connection_methods.rb +9 -9
  47. data/lib/arjdbc/mysql/explain_support.rb +85 -0
  48. data/lib/arjdbc/oracle.rb +1 -1
  49. data/lib/arjdbc/oracle/adapter.rb +232 -125
  50. data/lib/arjdbc/oracle/connection_methods.rb +2 -2
  51. data/lib/arjdbc/postgresql.rb +1 -1
  52. data/lib/arjdbc/postgresql/adapter.rb +134 -86
  53. data/lib/arjdbc/postgresql/connection_methods.rb +6 -4
  54. data/lib/arjdbc/postgresql/explain_support.rb +55 -0
  55. data/lib/arjdbc/sqlite3.rb +1 -1
  56. data/lib/arjdbc/sqlite3/adapter.rb +176 -108
  57. data/lib/arjdbc/sqlite3/connection_methods.rb +5 -5
  58. data/lib/arjdbc/sqlite3/explain_support.rb +32 -0
  59. data/lib/arjdbc/sybase/adapter.rb +7 -6
  60. data/lib/arjdbc/version.rb +1 -1
  61. data/pom.xml +1 -1
  62. data/rakelib/02-test.rake +9 -11
  63. data/rakelib/rails.rake +18 -10
  64. data/src/java/arjdbc/db2/DB2Module.java +70 -0
  65. data/src/java/arjdbc/derby/DerbyModule.java +24 -5
  66. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +66 -0
  67. data/src/java/arjdbc/jdbc/AdapterJavaService.java +14 -7
  68. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +111 -89
  69. data/src/java/arjdbc/mysql/MySQLModule.java +79 -70
  70. data/src/java/arjdbc/oracle/OracleModule.java +74 -0
  71. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +5 -10
  72. data/src/java/arjdbc/sqlite3/SQLite3Module.java +77 -0
  73. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +127 -0
  74. data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +25 -111
  75. data/src/java/arjdbc/util/QuotingUtils.java +104 -0
  76. data/test/abstract_db_create.rb +6 -6
  77. data/test/activerecord/connection_adapters/type_conversion_test.rb +2 -2
  78. data/test/assets/flowers.jpg +0 -0
  79. data/test/binary.rb +67 -0
  80. data/test/db/db2.rb +30 -7
  81. data/test/db/jdbc.rb +4 -2
  82. data/test/db/oracle.rb +18 -27
  83. data/test/db2_binary_test.rb +6 -0
  84. data/test/db2_serialize_test.rb +6 -0
  85. data/test/db2_simple_test.rb +20 -25
  86. data/test/db2_test.rb +71 -0
  87. data/test/derby_binary_test.rb +6 -0
  88. data/test/derby_migration_test.rb +42 -35
  89. data/test/derby_reset_column_information_test.rb +1 -0
  90. data/test/derby_row_locking_test.rb +17 -0
  91. data/test/derby_schema_dump_test.rb +9 -0
  92. data/test/derby_serialize_test.rb +6 -0
  93. data/test/derby_simple_test.rb +59 -17
  94. data/test/generic_jdbc_connection_test.rb +112 -5
  95. data/test/h2_binary_test.rb +6 -0
  96. data/test/h2_change_column_test.rb +1 -1
  97. data/test/h2_schema_dump_test.rb +25 -0
  98. data/test/h2_serialize_test.rb +6 -0
  99. data/test/h2_simple_test.rb +23 -9
  100. data/test/has_many_through.rb +18 -4
  101. data/test/hsqldb_binary_test.rb +6 -0
  102. data/test/hsqldb_schema_dump_test.rb +15 -0
  103. data/test/hsqldb_serialize_test.rb +6 -0
  104. data/test/hsqldb_simple_test.rb +1 -0
  105. data/test/informix_simple_test.rb +1 -1
  106. data/test/jdbc/db2.rb +23 -0
  107. data/test/jdbc/oracle.rb +23 -0
  108. data/test/jdbc_common.rb +3 -110
  109. data/test/jndi_callbacks_test.rb +0 -2
  110. data/test/jndi_test.rb +2 -0
  111. data/test/models/binary.rb +18 -0
  112. data/test/models/custom_pk_name.rb +1 -0
  113. data/test/models/data_types.rb +11 -2
  114. data/test/models/entry.rb +1 -1
  115. data/test/models/string_id.rb +2 -2
  116. data/test/models/thing.rb +1 -1
  117. data/test/models/topic.rb +32 -0
  118. data/test/mssql_legacy_types_test.rb +1 -1
  119. data/test/mssql_limit_offset_test.rb +13 -3
  120. data/test/mssql_serialize_test.rb +6 -0
  121. data/test/mysql_binary_test.rb +6 -0
  122. data/test/mysql_schema_dump_test.rb +220 -0
  123. data/test/mysql_serialize_test.rb +6 -0
  124. data/test/mysql_simple_test.rb +22 -2
  125. data/test/mysql_test.rb +93 -0
  126. data/test/oracle_binary_test.rb +6 -0
  127. data/test/oracle_limit_test.rb +2 -1
  128. data/test/oracle_serialize_test.rb +6 -0
  129. data/test/oracle_simple_test.rb +61 -0
  130. data/test/oracle_specific_test.rb +77 -26
  131. data/test/postgres_binary_test.rb +6 -0
  132. data/test/postgres_native_type_mapping_test.rb +12 -11
  133. data/test/postgres_nonseq_pkey_test.rb +1 -0
  134. data/test/postgres_reserved_test.rb +1 -0
  135. data/test/postgres_reset_column_information_test.rb +1 -0
  136. data/test/postgres_row_locking_test.rb +21 -0
  137. data/test/postgres_schema_dump_test.rb +88 -0
  138. data/test/postgres_schema_search_path_test.rb +1 -0
  139. data/test/postgres_simple_test.rb +62 -89
  140. data/test/postgres_table_alias_length_test.rb +1 -0
  141. data/test/postgres_test.rb +31 -0
  142. data/test/postgres_type_conversion_test.rb +16 -16
  143. data/test/row_locking.rb +69 -64
  144. data/test/schema_dump.rb +168 -0
  145. data/test/serialize.rb +277 -0
  146. data/test/simple.rb +326 -122
  147. data/test/sqlite3_serialize_test.rb +6 -0
  148. data/test/sqlite3_simple_test.rb +51 -84
  149. data/test/sqlite3_type_conversion_test.rb +101 -0
  150. data/test/test_helper.rb +224 -0
  151. metadata +325 -366
  152. data/README.rdoc +0 -214
  153. data/test/db/logger.rb +0 -3
  154. data/test/derby_multibyte_test.rb +0 -11
  155. data/test/mysql_info_test.rb +0 -123
@@ -5,43 +5,48 @@ module ActiveRecord
5
5
 
6
6
  def initialize(config, name, default, *args)
7
7
  call_discovered_column_callbacks(config)
8
- super(name,default_value(default),*args)
8
+ super(name, default_value(default), *args)
9
9
  init_column(name, default, *args)
10
10
  end
11
11
 
12
- def init_column(*args)
13
- end
12
+ def init_column(*args); end
13
+
14
+ def default_value(value); value; end
15
+
16
+ protected
14
17
 
15
- def default_value(val)
16
- val
18
+ def call_discovered_column_callbacks(config)
19
+ dialect = (config[:dialect] || config[:driver]).to_s
20
+ for matcher, block in self.class.column_types
21
+ block.call(config, self) if matcher === dialect
22
+ end
17
23
  end
18
24
 
25
+ public
26
+
19
27
  def self.column_types
20
- # GH #25: reset the column types if the # of constants changed
21
- # since last call
22
- if ::ArJdbc.constants.size != driver_constants.size
23
- @driver_constants = nil
24
- @column_types = nil
28
+ # reset the column types if the # of constants changed since last call
29
+ @column_types ||= begin
30
+ types = driver_constants.select { |c| c.respond_to? :column_selector }
31
+ types.map! { |c| c.column_selector }
32
+ types.inject({}) { |h, val| h[ val[0] ] = val[1]; h }
25
33
  end
26
- @column_types ||= driver_constants.select {|c|
27
- c.respond_to? :column_selector }.map {|c|
28
- c.column_selector }.inject({}) {|h,val|
29
- h[val[0]] = val[1]; h }
30
34
  end
31
35
 
32
36
  def self.driver_constants
33
- @driver_constants ||= ::ArJdbc.constants.map {|c| ::ArJdbc.const_get c }
37
+ reset_constants
38
+ @driver_constants ||= ::ArJdbc.constants.map { |c| ::ArJdbc.const_get c }
34
39
  end
35
40
 
36
- protected
37
- def call_discovered_column_callbacks(config)
38
- dialect = config[:dialect] || config[:driver]
39
- for reg, func in JdbcColumn.column_types
40
- if reg === dialect.to_s
41
- func.call(config,self)
42
- end
43
- end
41
+ def self.reset_constants!
42
+ @driver_constants = nil; @column_types = nil
43
+ end
44
+
45
+ def self.reset_constants
46
+ return false if ! defined?(@driver_constants) || ! @driver_constants
47
+ reset_constants! if ::ArJdbc.constants.size != @driver_constants.size
44
48
  end
49
+
45
50
  end
46
51
  end
47
52
  end
@@ -41,7 +41,8 @@ module ActiveRecord
41
41
  url = configure_url
42
42
  username = config[:username].to_s
43
43
  password = config[:password].to_s
44
- jdbc_driver = ( config[:driver_instance] ||= JdbcDriver.new(config[:driver].to_s) )
44
+ jdbc_driver = ( config[:driver_instance] ||=
45
+ JdbcDriver.new(config[:driver].to_s, config[:properties]) )
45
46
 
46
47
  @connection_factory = JdbcConnectionFactory.impl do
47
48
  jdbc_driver.connection(url, username, password)
@@ -84,11 +85,14 @@ module ActiveRecord
84
85
  @stmts = {}
85
86
  rescue ::ActiveRecord::ActiveRecordError
86
87
  raise
87
- rescue Exception => e
88
- raise ::ActiveRecord::JDBCError.new("The driver encountered an unknown error: #{e}").tap { |err|
89
- err.errno = 0
90
- err.sql_exception = e
91
- }
88
+ rescue Java::JavaSql::SQLException => e
89
+ e = e.cause if defined?(NativeException) && e.is_a?(NativeException) # JRuby-1.6.8
90
+ error = e.getMessage || e.getSQLState
91
+ error = error ? "#{e.java_class.name}: #{error}" : e.java_class.name
92
+ error = ::ActiveRecord::JDBCError.new("The driver encountered an unknown error: #{error}")
93
+ error.errno = e.getErrorCode
94
+ error.sql_exception = e
95
+ raise error
92
96
  end
93
97
 
94
98
  def adapter=(adapter)
@@ -1,9 +1,17 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  class JdbcDriver
4
- def initialize(name)
4
+ attr_reader :name, :properties
5
+
6
+ def initialize(name, properties = {})
5
7
  @name = name
6
8
  @driver = driver_class.new
9
+ if properties.is_a?(Java::JavaUtil::Properties)
10
+ @properties = properties # allow programmatically set properties
11
+ else
12
+ @properties = Java::JavaUtil::Properties.new
13
+ properties.each { |key, val| @properties[key] = val.to_s } if properties
14
+ end
7
15
  end
8
16
 
9
17
  def driver_class
@@ -25,10 +33,10 @@ module ActiveRecord
25
33
 
26
34
  def connection(url, user, pass)
27
35
  # bypass DriverManager to get around problem with dynamically loaded jdbc drivers
28
- props = java.util.Properties.new
29
- props.setProperty("user", user)
30
- props.setProperty("password", pass)
31
- @driver.connect(url, props)
36
+ properties = self.properties.clone
37
+ properties.setProperty("user", user) if user
38
+ properties.setProperty("password", pass) if pass
39
+ @driver.connect(url, properties)
32
40
  end
33
41
  end
34
42
  end
@@ -0,0 +1,21 @@
1
+ module ArJdbc
2
+ module SerializedAttributesHelper
3
+
4
+ def self.dump_column_value(record, column)
5
+ value = record[ name = column.name.to_s ]
6
+ if record.class.respond_to?(:serialized_attributes)
7
+ if coder = record.class.serialized_attributes[name]
8
+ value = coder.respond_to?(:dump) ? coder.dump(value) : value.to_yaml
9
+ end
10
+ else
11
+ if record.respond_to?(:unserializable_attribute?)
12
+ value = value.to_yaml if record.unserializable_attribute?(name, column)
13
+ else
14
+ value = value.to_yaml if value.is_a?(Hash)
15
+ end
16
+ end
17
+ value
18
+ end
19
+
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  require 'arjdbc/jdbc'
2
- require 'arjdbc/mssql/connection_methods'
3
2
  require 'arjdbc/mssql/adapter'
3
+ require 'arjdbc/mssql/connection_methods'
@@ -2,34 +2,34 @@ require 'strscan'
2
2
  require 'arjdbc/mssql/tsql_helper'
3
3
  require 'arjdbc/mssql/limit_helpers'
4
4
  require 'arjdbc/mssql/lock_helpers'
5
+ require 'arjdbc/jdbc/serialized_attributes_helper'
5
6
 
6
- module ::ArJdbc
7
+ module ArJdbc
7
8
  module MsSQL
8
9
  include TSqlMethods
9
10
  include LimitHelpers
10
11
 
12
+ @@_lob_callback_added = nil
13
+
11
14
  def self.extended(mod)
12
- unless defined?(@lob_callback_added)
15
+ unless @@_lob_callback_added
13
16
  ActiveRecord::Base.class_eval do
14
17
  def after_save_with_mssql_lob
15
- self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
16
- value = self[c.name]
17
- if coder = self.class.serialized_attributes[c.name]
18
- if coder.respond_to?(:dump)
19
- value = coder.dump(value)
20
- else
21
- value = value.to_yaml
22
- end
23
- end
24
- next if value.nil? || (value == '')
25
-
26
- connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
18
+ self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |column|
19
+ value = ::ArJdbc::SerializedAttributesHelper.dump_column_value(self, column)
20
+ next if value.nil? || (value == '')
21
+
22
+ connection.write_large_object(
23
+ column.type == :binary, column.name,
24
+ self.class.table_name, self.class.primary_key,
25
+ quote_value(id), value
26
+ )
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
31
  ActiveRecord::Base.after_save :after_save_with_mssql_lob
32
- @lob_callback_added = true
32
+ @@_lob_callback_added = true
33
33
  end
34
34
  mod.add_version_specific_add_limit_offset
35
35
  end
@@ -217,9 +217,9 @@ module ::ArJdbc
217
217
  else
218
218
  super
219
219
  end
220
- when TrueClass then '1'
221
- when FalseClass then '0'
222
- else super
220
+ when TrueClass then '1'
221
+ when FalseClass then '0'
222
+ else super
223
223
  end
224
224
  end
225
225
 
@@ -319,12 +319,14 @@ module ::ArJdbc
319
319
  execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
320
320
  end
321
321
  end
322
-
323
- def remove_column(table_name, column_name)
322
+
323
+ def remove_column(table_name, *column_names) #:nodoc:
324
324
  clear_cached_table(table_name)
325
- remove_check_constraints(table_name, column_name)
326
- remove_default_constraint(table_name, column_name)
327
- execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
325
+ for column_name in column_names.flatten
326
+ remove_check_constraints(table_name, column_name)
327
+ remove_default_constraint(table_name, column_name)
328
+ execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
329
+ end
328
330
  end
329
331
 
330
332
  def remove_default_constraint(table_name, column_name)
@@ -369,36 +371,6 @@ module ::ArJdbc
369
371
  @table_columns[table_name]
370
372
  end
371
373
 
372
- def _execute(sql, name = nil)
373
- # Match the start of the sql to determine appropriate behaviour. Be aware of
374
- # multi-line sql which might begin with 'create stored_proc' and contain 'insert into ...' lines.
375
- # Possible improvements include ignoring comment blocks prior to the first statement.
376
- if sql.lstrip =~ /\Ainsert/i
377
- if query_requires_identity_insert?(sql)
378
- table_name = get_table_name(sql)
379
- with_identity_insert_enabled(table_name) do
380
- id = @connection.execute_insert(sql)
381
- end
382
- else
383
- @connection.execute_insert(sql)
384
- end
385
- elsif sql.lstrip =~ /\A(create|exec)/i
386
- @connection.execute_update(sql)
387
- elsif sql.lstrip =~ /\A\(?\s*(select|show)/i
388
- repair_special_columns(sql)
389
- @connection.execute_query(sql)
390
- else
391
- @connection.execute_update(sql)
392
- end
393
- end
394
-
395
- def select(sql, name = nil, binds = [])
396
- sql = substitute_binds(sql, binds)
397
- log(sql, name) do
398
- @connection.execute_query(sql)
399
- end
400
- end
401
-
402
374
  # Turns IDENTITY_INSERT ON for table during execution of the block
403
375
  # N.B. This sets the state of IDENTITY_INSERT to OFF after the
404
376
  # block has been executed without regard to its previous state
@@ -478,6 +450,32 @@ module ::ArJdbc
478
450
  def reset_column_information
479
451
  @table_columns = nil
480
452
  end
453
+
454
+ private
455
+
456
+ def _execute(sql, name = nil)
457
+ # Match the start of the SQL to determine appropriate behavior.
458
+ # Be aware of multi-line SQL which might begin with 'create stored_proc'
459
+ # and contain 'insert into ...' lines.
460
+ # TODO test and refactor using `self.class.insert?(sql)` etc
461
+ # NOTE: ignoring comment blocks prior to the first statement ?!
462
+ if sql.lstrip =~ /\Ainsert/i # self.class.insert?(sql)
463
+ if query_requires_identity_insert?(sql)
464
+ table_name = get_table_name(sql)
465
+ with_identity_insert_enabled(table_name) do
466
+ @connection.execute_insert(sql)
467
+ end
468
+ else
469
+ @connection.execute_insert(sql)
470
+ end
471
+ elsif sql.lstrip =~ /\A\(?\s*(select|show)/i # self.class.select?(sql)
472
+ repair_special_columns(sql)
473
+ @connection.execute_query(sql)
474
+ else # sql.lstrip =~ /\A(create|exec)/i
475
+ @connection.execute_update(sql)
476
+ end
477
+ end
478
+
481
479
  end
482
480
  end
483
481
 
@@ -1,7 +1,14 @@
1
1
  class ActiveRecord::Base
2
2
  class << self
3
3
  def mssql_connection(config)
4
- require 'active_record/connection_adapters/jdbcmssql_adapter'
4
+ begin
5
+ require 'jdbc/jtds'
6
+ # NOTE: the adapter has only support for working with the
7
+ # open-source jTDS driver (won't work with MS's driver) !
8
+ ::Jdbc::JTDS.load_driver(:require) if defined?(::Jdbc::JTDS.load_driver)
9
+ rescue LoadError => e # assuming driver.jar is on the class-path
10
+ raise e unless e.message.to_s.index('no such file to load')
11
+ end
5
12
 
6
13
  config[:host] ||= "localhost"
7
14
  config[:port] ||= 1433
@@ -1,3 +1,3 @@
1
1
  require 'arjdbc/jdbc'
2
- require 'arjdbc/mysql/connection_methods'
3
2
  require 'arjdbc/mysql/adapter'
3
+ require 'arjdbc/mysql/connection_methods'
@@ -1,11 +1,9 @@
1
1
  require 'bigdecimal'
2
2
  require 'active_record/connection_adapters/abstract/schema_definitions'
3
+ require 'arjdbc/mysql/explain_support'
3
4
 
4
- module ::ArJdbc
5
+ module ArJdbc
5
6
  module MySQL
6
- def self.column_selector
7
- [/mysql/i, lambda {|cfg,col| col.extend(::ArJdbc::MySQL::ColumnExtensions)}]
8
- end
9
7
 
10
8
  def self.extended(adapter)
11
9
  adapter.configure_connection
@@ -15,11 +13,16 @@ module ::ArJdbc
15
13
  execute("SET SQL_AUTO_IS_NULL=0")
16
14
  end
17
15
 
16
+ def self.column_selector
17
+ [ /mysql/i, lambda { |_,column| column.extend(::ArJdbc::MySQL::Column) } ]
18
+ end
19
+
18
20
  def self.jdbc_connection_class
19
21
  ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
20
22
  end
21
23
 
22
- module ColumnExtensions
24
+ module Column
25
+
23
26
  def extract_default(default)
24
27
  if sql_type =~ /blob/i || type == :text
25
28
  if default.blank?
@@ -60,14 +63,15 @@ module ::ArJdbc
60
63
  when /long/i
61
64
  2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
62
65
  else
63
- nil # we could return 65535 here, but we leave it undecorated by default
66
+ super # we could return 65535 here, but we leave it undecorated by default
64
67
  end
65
- when /^enum/i; 255
66
68
  when /^bigint/i; 8
67
69
  when /^int/i; 4
68
70
  when /^mediumint/i; 3
69
71
  when /^smallint/i; 2
70
72
  when /^tinyint/i; 1
73
+ when /^enum\((.+)\)/i # 255
74
+ $1.split(',').map{ |enum| enum.strip.length - 2 }.max
71
75
  when /^(bool|date|float|int|time)/i
72
76
  nil
73
77
  else
@@ -85,23 +89,51 @@ module ::ArJdbc
85
89
  def missing_default_forged_as_empty_string?(default)
86
90
  type != :string && !null && default == ''
87
91
  end
88
- end
89
-
90
- def modify_types(tp)
91
- tp[:primary_key] = "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
92
- tp[:integer] = { :name => 'int', :limit => 4 }
93
- tp[:decimal] = { :name => "decimal" }
94
- tp[:timestamp] = { :name => "datetime" }
95
- tp[:datetime][:limit] = nil
96
- tp
97
- end
98
-
92
+
93
+ end
94
+
95
+ ColumnExtensions = Column # :nodoc: backwards-compatibility
96
+
97
+ NATIVE_DATABASE_TYPES = {
98
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
99
+ :string => { :name => "varchar", :limit => 255 },
100
+ :text => { :name => "text" },
101
+ :integer => { :name => "int", :limit => 4 },
102
+ :float => { :name => "float" },
103
+ :decimal => { :name => "decimal" },
104
+ :datetime => { :name => "datetime" },
105
+ :timestamp => { :name => "datetime" },
106
+ :time => { :name => "time" },
107
+ :date => { :name => "date" },
108
+ :binary => { :name => "blob" },
109
+ :boolean => { :name => "tinyint", :limit => 1 }
110
+ }
111
+
112
+ def native_database_types
113
+ NATIVE_DATABASE_TYPES
114
+ end
115
+
116
+ def modify_types(types)
117
+ types[:primary_key] = "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
118
+ types[:integer] = { :name => 'int', :limit => 4 }
119
+ types[:decimal] = { :name => "decimal" }
120
+ types[:timestamp] = { :name => "datetime" }
121
+ types[:datetime][:limit] = nil
122
+ types
123
+ end
124
+
125
+ ADAPTER_NAME = 'MySQL'.freeze
126
+
99
127
  def adapter_name #:nodoc:
100
- 'MySQL'
128
+ ADAPTER_NAME
101
129
  end
102
130
 
103
131
  def self.arel2_visitors(config)
104
- {}.tap {|v| %w(mysql mysql2 jdbcmysql).each {|a| v[a] = ::Arel::Visitors::MySQL } }
132
+ {
133
+ 'mysql' => ::Arel::Visitors::MySQL,
134
+ 'mysql2' => ::Arel::Visitors::MySQL,
135
+ 'jdbcmysql' => ::Arel::Visitors::MySQL
136
+ }
105
137
  end
106
138
 
107
139
  def case_sensitive_equality_operator
@@ -120,32 +152,57 @@ module ::ArJdbc
120
152
 
121
153
  def quote(value, column = nil)
122
154
  return value.quoted_id if value.respond_to?(:quoted_id)
123
-
124
- if column && column.type == :primary_key
125
- value.to_s
126
- elsif column && String === value && column.type == :binary && column.class.respond_to?(:string_to_binary)
127
- s = column.class.string_to_binary(value).unpack("H*")[0]
128
- "x'#{s}'"
129
- elsif BigDecimal === value
130
- "'#{value.to_s("F")}'"
155
+ return value.to_s if column && column.type == :primary_key
156
+
157
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
158
+ "x'#{column.class.string_to_binary(value).unpack("H*")[0]}'"
159
+ elsif value.kind_of?(BigDecimal)
160
+ value.to_s("F")
131
161
  else
132
162
  super
133
163
  end
134
164
  end
135
-
136
- def quote_column_name(name)
165
+
166
+ def quote_column_name(name) # :nodoc:
137
167
  "`#{name.to_s.gsub('`', '``')}`"
138
168
  end
169
+
170
+ def quote_table_name(name) # :nodoc:
171
+ quote_column_name(name).gsub('.', '`.`')
172
+ end
173
+
174
+ # Returns true, since this connection adapter supports migrations.
175
+ def supports_migrations?
176
+ true
177
+ end
139
178
 
140
- def quoted_true
141
- "1"
179
+ def supports_primary_key? # :nodoc:
180
+ true
142
181
  end
143
182
 
144
- def quoted_false
145
- "0"
183
+ def supports_bulk_alter? # :nodoc:
184
+ true
146
185
  end
147
186
 
148
- def supports_savepoints? #:nodoc:
187
+ # Technically MySQL allows to create indexes with the sort order syntax
188
+ # but at the moment (5.5) it doesn't yet implement them
189
+ def supports_index_sort_order? # :nodoc:
190
+ true
191
+ end
192
+
193
+ # MySQL 4 technically support transaction isolation, but it is affected by a bug
194
+ # where the transaction level gets persisted for the whole session:
195
+ #
196
+ # http://bugs.mysql.com/bug.php?id=39170
197
+ def supports_transaction_isolation? # :nodoc:
198
+ version[0] && version[0] >= 5
199
+ end
200
+
201
+ def supports_views? # :nodoc:
202
+ version[0] && version[0] >= 5
203
+ end
204
+
205
+ def supports_savepoints? # :nodoc:
149
206
  true
150
207
  end
151
208
 
@@ -161,18 +218,31 @@ module ::ArJdbc
161
218
  execute("RELEASE SAVEPOINT #{current_savepoint_name}")
162
219
  end
163
220
 
164
- def disable_referential_integrity(&block) #:nodoc:
165
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
221
+ def disable_referential_integrity # :nodoc:
222
+ fk_checks = select_value("SELECT @@FOREIGN_KEY_CHECKS")
166
223
  begin
167
224
  update("SET FOREIGN_KEY_CHECKS = 0")
168
225
  yield
169
226
  ensure
170
- update("SET FOREIGN_KEY_CHECKS = #{old}")
227
+ update("SET FOREIGN_KEY_CHECKS = #{fk_checks}")
171
228
  end
172
229
  end
173
230
 
231
+ # DATABASE STATEMENTS ======================================
232
+
233
+ def exec_insert(sql, name, binds)
234
+ execute sql, name, binds
235
+ end
236
+ alias :exec_update :exec_insert
237
+ alias :exec_delete :exec_insert
238
+
239
+ # Make it public just like native MySQL adapter does.
240
+ def update_sql(sql, name = nil) # :nodoc:
241
+ super
242
+ end
243
+
174
244
  # SCHEMA STATEMENTS ========================================
175
-
245
+
176
246
  def structure_dump #:nodoc:
177
247
  if supports_views?
178
248
  sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
@@ -270,6 +340,11 @@ module ::ArJdbc
270
340
  execute "RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
271
341
  end
272
342
 
343
+ def remove_index!(table_name, index_name) #:nodoc:
344
+ # missing table_name quoting in AR-2.3
345
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
346
+ end
347
+
273
348
  def add_column(table_name, column_name, type, options = {})
274
349
  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])}"
275
350
  add_column_options!(add_column_sql, options)
@@ -373,15 +448,33 @@ module ::ArJdbc
373
448
  end
374
449
 
375
450
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
376
- return super unless type.to_s == 'integer'
377
-
378
- case limit
379
- when 1; 'tinyint'
380
- when 2; 'smallint'
381
- when 3; 'mediumint'
382
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
383
- when 5..8; 'bigint'
384
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
451
+ case type.to_s
452
+ when 'binary'
453
+ case limit
454
+ when 0..0xfff; "varbinary(#{limit})"
455
+ when nil; "blob"
456
+ when 0x1000..0xffffffff; "blob(#{limit})"
457
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
458
+ end
459
+ when 'integer'
460
+ case limit
461
+ when 1; 'tinyint'
462
+ when 2; 'smallint'
463
+ when 3; 'mediumint'
464
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
465
+ when 5..8; 'bigint'
466
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
467
+ end
468
+ when 'text'
469
+ case limit
470
+ when 0..0xff; 'tinytext'
471
+ when nil, 0x100..0xffff; 'text'
472
+ when 0x10000..0xffffff; 'mediumtext'
473
+ when 0x1000000..0xffffffff; 'longtext'
474
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
475
+ end
476
+ else
477
+ super
385
478
  end
386
479
  end
387
480
 
@@ -421,6 +514,7 @@ module ::ArJdbc
421
514
  end
422
515
 
423
516
  private
517
+
424
518
  def column_for(table_name, column_name)
425
519
  unless column = columns(table_name).find { |c| c.name == column_name.to_s }
426
520
  raise "No such column: #{table_name}.#{column_name}"
@@ -431,10 +525,22 @@ module ::ArJdbc
431
525
  def show_create_table(table)
432
526
  select_one("SHOW CREATE TABLE #{quote_table_name(table)}")
433
527
  end
434
-
435
- def supports_views?
436
- false
528
+
529
+ def version
530
+ return @version ||= begin
531
+ version = []
532
+ java_connection = jdbc_connection(true)
533
+ if java_connection.is_a?(Java::ComMysqlJdbc::ConnectionImpl)
534
+ version << jdbc_connection.serverMajorVersion
535
+ version << jdbc_connection.serverMinorVersion
536
+ version << jdbc_connection.serverSubMinorVersion
537
+ else
538
+ warn "INFO: failed to resolve MySQL server version using: #{java_connection}"
539
+ end
540
+ version
541
+ end
437
542
  end
543
+
438
544
  end
439
545
  end
440
546
 
@@ -445,7 +551,7 @@ module ActiveRecord
445
551
  remove_const(:MysqlAdapter) if const_defined?(:MysqlAdapter)
446
552
 
447
553
  class MysqlColumn < JdbcColumn
448
- include ArJdbc::MySQL::ColumnExtensions
554
+ include ::ArJdbc::MySQL::Column
449
555
 
450
556
  def initialize(name, *args)
451
557
  if Hash === name
@@ -460,128 +566,58 @@ module ActiveRecord
460
566
  end
461
567
 
462
568
  class MysqlAdapter < JdbcAdapter
463
- include ArJdbc::MySQL
569
+ include ::ArJdbc::MySQL
570
+ include ::ArJdbc::MySQL::ExplainSupport
464
571
 
465
572
  def initialize(*args)
466
573
  super
467
574
  configure_connection
468
575
  end
469
576
 
470
- ## EXPLAIN support lifted from the mysql2 gem with slight modifications
471
- ## to work in the JDBC adapter gem.
472
- def supports_explain?
473
- true
474
- end
475
-
476
- def explain(arel, binds = [])
477
- sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
478
- start = Time.now.to_f
479
- raw_result = execute(sql, "EXPLAIN")
480
- ar_result = ActiveRecord::Result.new(raw_result[0].keys, raw_result)
481
- elapsed = Time.now.to_f - start
482
- ExplainPrettyPrinter.new.pp(ar_result, elapsed)
483
- end
484
-
485
- class ExplainPrettyPrinter # :nodoc:
486
- # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
487
- # MySQL shell:
488
- #
489
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
490
- # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
491
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
492
- # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
493
- # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
494
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
495
- # 2 rows in set (0.00 sec)
496
- #
497
- # This is an exercise in Ruby hyperrealism :).
498
- def pp(result, elapsed)
499
- widths = compute_column_widths(result)
500
- separator = build_separator(widths)
501
-
502
- pp = []
503
-
504
- pp << separator
505
- pp << build_cells(result.columns, widths)
506
- pp << separator
507
-
508
- result.rows.each do |row|
509
- pp << build_cells(row.values, widths)
510
- end
511
-
512
- pp << separator
513
- pp << build_footer(result.rows.length, elapsed)
514
-
515
- pp.join("\n") + "\n"
516
- end
517
-
518
- private
519
-
520
- def compute_column_widths(result)
521
- [].tap do |widths|
522
- result.columns.each do |col|
523
- cells_in_column = [col] + result.rows.map {|r| r[col].nil? ? 'NULL' : r[col].to_s}
524
- widths << cells_in_column.map(&:length).max
525
- end
526
- end
527
-
528
- end
529
-
530
- def build_separator(widths)
531
- padding = 1
532
- '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
533
- end
534
-
535
- def build_cells(items, widths)
536
- cells = []
537
- items.each_with_index do |item, i|
538
- item = 'NULL' if item.nil?
539
- justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
540
- cells << item.to_s.send(justifier, widths[i])
541
- end
542
- '| ' + cells.join(' | ') + ' |'
543
- end
544
-
545
- def build_footer(nrows, elapsed)
546
- rows_label = nrows == 1 ? 'row' : 'rows'
547
- "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
548
- end
549
- end
550
-
551
577
  def jdbc_connection_class(spec)
552
578
  ::ArJdbc::MySQL.jdbc_connection_class
553
579
  end
554
580
 
555
581
  def jdbc_column_class
556
- ActiveRecord::ConnectionAdapters::MysqlColumn
582
+ MysqlColumn
557
583
  end
558
-
559
584
  alias_chained_method :columns, :query_cache, :jdbc_columns
560
585
 
561
- protected
562
- def exec_insert(sql, name, binds)
563
- binds = binds.dup
586
+ # some QUOTING caching :
564
587
 
565
- # Pretend to support bind parameters
566
- unless binds.empty?
567
- sql = sql.gsub('?') { quote(*binds.shift.reverse) }
588
+ @@quoted_table_names = {}
589
+
590
+ def quote_table_name(name)
591
+ unless quoted = @@quoted_table_names[name]
592
+ quoted = super
593
+ @@quoted_table_names[name] = quoted.freeze
568
594
  end
569
- execute sql, name
595
+ quoted
570
596
  end
571
- alias :exec_update :exec_insert
572
- alias :exec_delete :exec_insert
573
597
 
598
+ @@quoted_column_names = {}
599
+
600
+ def quote_column_name(name)
601
+ unless quoted = @@quoted_column_names[name]
602
+ quoted = super
603
+ @@quoted_column_names[name] = quoted.freeze
604
+ end
605
+ quoted
606
+ end
607
+
574
608
  end
575
609
  end
576
610
  end
577
611
 
578
- module Mysql # :nodoc:
579
- remove_const(:Error) if const_defined?(:Error)
612
+ # Don't need to load native mysql adapter
613
+ $LOADED_FEATURES << 'active_record/connection_adapters/mysql_adapter.rb'
614
+ $LOADED_FEATURES << 'active_record/connection_adapters/mysql2_adapter.rb'
580
615
 
581
- class Error < ::ActiveRecord::JDBCError
582
- end
616
+ module Mysql # :nodoc:
617
+ remove_const(:Error) if const_defined?(:Error)
618
+ class Error < ::ActiveRecord::JDBCError; end
583
619
 
584
620
  def self.client_version
585
- 50400 # faked out for AR tests
621
+ 50400 # faked out for AR tests
586
622
  end
587
623
  end