You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

adapter.rb 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. require 'active_record/connection_adapters/abstract/schema_definitions'
  2. module ::ArJdbc
  3. module MySQL
  4. def self.column_selector
  5. [/mysql/i, lambda {|cfg,col| col.extend(::ArJdbc::MySQL::Column)}]
  6. end
  7. def self.extended(adapter)
  8. adapter.configure_connection
  9. end
  10. def configure_connection
  11. execute("SET SQL_AUTO_IS_NULL=0")
  12. end
  13. def self.jdbc_connection_class
  14. ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
  15. end
  16. module Column
  17. def extract_default(default)
  18. if sql_type =~ /blob/i || type == :text
  19. if default.blank?
  20. return null ? nil : ''
  21. else
  22. raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
  23. end
  24. elsif missing_default_forged_as_empty_string?(default)
  25. nil
  26. else
  27. super
  28. end
  29. end
  30. def has_default?
  31. return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
  32. super
  33. end
  34. def simplified_type(field_type)
  35. case field_type
  36. when /tinyint\(1\)|bit/i then :boolean
  37. when /enum/i then :string
  38. when /decimal/i then :decimal
  39. else
  40. super
  41. end
  42. end
  43. def extract_limit(sql_type)
  44. case sql_type
  45. when /blob|text/i
  46. case sql_type
  47. when /tiny/i
  48. 255
  49. when /medium/i
  50. 16777215
  51. when /long/i
  52. 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
  53. else
  54. nil # we could return 65535 here, but we leave it undecorated by default
  55. end
  56. when /^bigint/i; 8
  57. when /^int/i; 4
  58. when /^mediumint/i; 3
  59. when /^smallint/i; 2
  60. when /^tinyint/i; 1
  61. when /^(bool|date|float|int|time)/i
  62. nil
  63. else
  64. super
  65. end
  66. end
  67. # MySQL misreports NOT NULL column default when none is given.
  68. # We can't detect this for columns which may have a legitimate ''
  69. # default (string) but we can for others (integer, datetime, boolean,
  70. # and the rest).
  71. #
  72. # Test whether the column has default '', is not null, and is not
  73. # a type allowing default ''.
  74. def missing_default_forged_as_empty_string?(default)
  75. type != :string && !null && default == ''
  76. end
  77. end
  78. def modify_types(tp)
  79. # SonarQube
  80. # Compatibility with mysql 5.7
  81. # See https://github.com/rails/rails/commit/26cea8fabe828e7b0535275044cb8316fff1c590
  82. #tp[:primary_key] = "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
  83. tp[:primary_key] = "int(11) auto_increment PRIMARY KEY"
  84. # /SonarQube
  85. tp[:decimal] = { :name => "decimal" }
  86. tp[:timestamp] = { :name => "datetime" }
  87. tp[:datetime][:limit] = nil
  88. # SonarQube
  89. # Ticket http://tools.assembla.com/sonar/ticket/200
  90. # Problem with mysql TEXT columns. ActiveRecord :text type is mapped to TEXT type (65535 characters).
  91. # But we would like the bigger MEDIUMTEXT for the snapshot_sources table (16777215 characters).
  92. # This hack works only for ActiveRecord-JDBC (Jruby use).
  93. # See http://www.headius.com/jrubywiki/index.php/Adding_Datatypes_to_ActiveRecord-JDBC
  94. tp[:text] = { :name => "longtext" }
  95. tp[:binary] = { :name => "longblob" }
  96. tp[:big_integer] = { :name => "bigint"}
  97. # /SonarQube
  98. tp
  99. end
  100. def adapter_name #:nodoc:
  101. 'MySQL'
  102. end
  103. def arel2_visitors
  104. {'jdbcmysql' => ::Arel::Visitors::MySQL}
  105. end
  106. def case_sensitive_equality_operator
  107. "= BINARY"
  108. end
  109. def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
  110. where_sql
  111. end
  112. # QUOTING ==================================================
  113. def quote(value, column = nil)
  114. return value.quoted_id if value.respond_to?(:quoted_id)
  115. if column && column.type == :primary_key
  116. value.to_s
  117. elsif column && String === value && column.type == :binary && column.class.respond_to?(:string_to_binary)
  118. s = column.class.string_to_binary(value).unpack("H*")[0]
  119. "x'#{s}'"
  120. elsif BigDecimal === value
  121. "'#{value.to_s("F")}'"
  122. else
  123. super
  124. end
  125. end
  126. def quoted_true
  127. "1"
  128. end
  129. def quoted_false
  130. "0"
  131. end
  132. def begin_db_transaction #:nodoc:
  133. @connection.begin
  134. rescue Exception
  135. # Transactions aren't supported
  136. end
  137. def commit_db_transaction #:nodoc:
  138. @connection.commit
  139. rescue Exception
  140. # Transactions aren't supported
  141. end
  142. def rollback_db_transaction #:nodoc:
  143. @connection.rollback
  144. rescue Exception
  145. # Transactions aren't supported
  146. end
  147. def supports_savepoints? #:nodoc:
  148. true
  149. end
  150. def create_savepoint
  151. execute("SAVEPOINT #{current_savepoint_name}")
  152. end
  153. def rollback_to_savepoint
  154. execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
  155. end
  156. def release_savepoint
  157. execute("RELEASE SAVEPOINT #{current_savepoint_name}")
  158. end
  159. def disable_referential_integrity(&block) #:nodoc:
  160. old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
  161. begin
  162. update("SET FOREIGN_KEY_CHECKS = 0")
  163. yield
  164. ensure
  165. update("SET FOREIGN_KEY_CHECKS = #{old}")
  166. end
  167. end
  168. # SCHEMA STATEMENTS ========================================
  169. def structure_dump #:nodoc:
  170. if supports_views?
  171. sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
  172. else
  173. sql = "SHOW TABLES"
  174. end
  175. select_all(sql).inject("") do |structure, table|
  176. table.delete('Table_type')
  177. hash = show_create_table(table.to_a.first.last)
  178. if(table = hash["Create Table"])
  179. structure += table + ";\n\n"
  180. elsif(view = hash["Create View"])
  181. structure += view + ";\n\n"
  182. end
  183. end
  184. end
  185. def jdbc_columns(table_name, name = nil)#:nodoc:
  186. sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
  187. execute(sql, :skip_logging).map do |field|
  188. ::ActiveRecord::ConnectionAdapters::MysqlColumn.new(field["Field"], field["Default"], field["Type"], field["Null"] == "YES")
  189. end
  190. end
  191. def recreate_database(name, options = {}) #:nodoc:
  192. drop_database(name)
  193. create_database(name, options)
  194. end
  195. def create_database(name, options = {}) #:nodoc:
  196. if options[:collation]
  197. execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
  198. else
  199. execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
  200. end
  201. end
  202. def drop_database(name) #:nodoc:
  203. execute "DROP DATABASE IF EXISTS `#{name}`"
  204. end
  205. def current_database
  206. select_one("SELECT DATABASE() as db")["db"]
  207. end
  208. def create_table(name, options = {}) #:nodoc:
  209. #sonar - force UTF8
  210. #super(name, {:options => "ENGINE=InnoDB"}.merge(options))
  211. super(name, {:options => "ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin"}.merge(options))
  212. #/sonar
  213. end
  214. def rename_table(name, new_name)
  215. execute "RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
  216. end
  217. def add_column(table_name, column_name, type, options = {})
  218. 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])}"
  219. add_column_options!(add_column_sql, options)
  220. add_column_position!(add_column_sql, options)
  221. execute(add_column_sql)
  222. end
  223. def change_column_default(table_name, column_name, default) #:nodoc:
  224. column = column_for(table_name, column_name)
  225. change_column table_name, column_name, column.sql_type, :default => default
  226. end
  227. def change_column_null(table_name, column_name, null, default = nil)
  228. column = column_for(table_name, column_name)
  229. unless null || default.nil?
  230. execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
  231. end
  232. change_column table_name, column_name, column.sql_type, :null => null
  233. end
  234. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  235. column = column_for(table_name, column_name)
  236. unless options_include_default?(options)
  237. options[:default] = column.default
  238. end
  239. unless options.has_key?(:null)
  240. options[:null] = column.null
  241. end
  242. 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])}"
  243. add_column_options!(change_column_sql, options)
  244. add_column_position!(change_column_sql, options)
  245. execute(change_column_sql)
  246. end
  247. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  248. options = {}
  249. if column = columns(table_name).find { |c| c.name == column_name.to_s }
  250. options[:default] = column.default
  251. options[:null] = column.null
  252. else
  253. raise ActiveRecord::ActiveRecordError, "No such column: #{table_name}.#{column_name}"
  254. end
  255. current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
  256. rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
  257. add_column_options!(rename_column_sql, options)
  258. execute(rename_column_sql)
  259. end
  260. def add_limit_offset!(sql, options) #:nodoc:
  261. limit, offset = options[:limit], options[:offset]
  262. if limit && offset
  263. sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
  264. elsif limit
  265. sql << " LIMIT #{sanitize_limit(limit)}"
  266. elsif offset
  267. sql << " OFFSET #{offset.to_i}"
  268. end
  269. sql
  270. end
  271. def show_variable(var)
  272. res = execute("show variables like '#{var}'")
  273. row = res.detect {|row| row["Variable_name"] == var }
  274. row && row["Value"]
  275. end
  276. def charset
  277. show_variable("character_set_database")
  278. end
  279. def collation
  280. show_variable("collation_database")
  281. end
  282. def type_to_sql(type, limit = nil, precision = nil, scale = nil)
  283. return super unless type.to_s == 'integer'
  284. case limit
  285. when 1; 'tinyint'
  286. when 2; 'smallint'
  287. when 3; 'mediumint'
  288. when nil, 4, 11; 'int(11)' # compatibility with MySQL default
  289. when 5..8; 'bigint'
  290. else raise(ActiveRecordError, "No integer type has byte size #{limit}")
  291. end
  292. end
  293. def add_column_position!(sql, options)
  294. if options[:first]
  295. sql << " FIRST"
  296. elsif options[:after]
  297. sql << " AFTER #{quote_column_name(options[:after])}"
  298. end
  299. end
  300. protected
  301. def translate_exception(exception, message)
  302. return super unless exception.respond_to?(:errno)
  303. case exception.errno
  304. when 1062
  305. ::ActiveRecord::RecordNotUnique.new(message, exception)
  306. when 1452
  307. ::ActiveRecord::InvalidForeignKey.new(message, exception)
  308. else
  309. super
  310. end
  311. end
  312. # SONAR - support the length parameter when creating indices
  313. # See http://jira.sonarsource.com/browse/SONAR-4137
  314. def quoted_columns_for_index(column_names, options = {})
  315. length = options[:length] if options.is_a?(Hash)
  316. quoted_column_names = case length
  317. when Hash
  318. column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
  319. when Fixnum
  320. column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
  321. else
  322. column_names.map {|name| quote_column_name(name) }
  323. end
  324. end
  325. #/SONAR
  326. private
  327. def column_for(table_name, column_name)
  328. unless column = columns(table_name).find { |c| c.name == column_name.to_s }
  329. raise "No such column: #{table_name}.#{column_name}"
  330. end
  331. column
  332. end
  333. def show_create_table(table)
  334. select_one("SHOW CREATE TABLE #{quote_table_name(table)}")
  335. end
  336. def supports_views?
  337. false
  338. end
  339. end
  340. end
  341. module ActiveRecord::ConnectionAdapters
  342. # Remove any vestiges of core/Ruby MySQL adapter
  343. remove_const(:MysqlColumn) if const_defined?(:MysqlColumn)
  344. remove_const(:MysqlAdapter) if const_defined?(:MysqlAdapter)
  345. class MysqlColumn < JdbcColumn
  346. include ArJdbc::MySQL::Column
  347. def initialize(name, *args)
  348. if Hash === name
  349. super
  350. else
  351. super(nil, name, *args)
  352. end
  353. end
  354. def call_discovered_column_callbacks(*)
  355. end
  356. end
  357. class MysqlAdapter < JdbcAdapter
  358. include ArJdbc::MySQL
  359. def initialize(*args)
  360. super
  361. configure_connection
  362. end
  363. def adapter_spec(config)
  364. # return nil to avoid extending ArJdbc::MySQL, which we've already done
  365. end
  366. def jdbc_connection_class(spec)
  367. ::ArJdbc::MySQL.jdbc_connection_class
  368. end
  369. def jdbc_column_class
  370. ActiveRecord::ConnectionAdapters::MysqlColumn
  371. end
  372. alias_chained_method :columns, :query_cache, :jdbc_columns
  373. end
  374. end
  375. module Mysql # :nodoc:
  376. remove_const(:Error) if const_defined?(:Error)
  377. class Error < ::ActiveRecord::JDBCError
  378. end
  379. def self.client_version
  380. 50400 # faked out for AR tests
  381. end
  382. end