activerecord-jdbc-alt-adapter 50.3.0-java

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 (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.travis.yml +100 -0
  4. data/.yardopts +4 -0
  5. data/CONTRIBUTING.md +50 -0
  6. data/Gemfile +92 -0
  7. data/History.md +1191 -0
  8. data/LICENSE.txt +26 -0
  9. data/README.md +240 -0
  10. data/RUNNING_TESTS.md +127 -0
  11. data/Rakefile +336 -0
  12. data/Rakefile.jdbc +20 -0
  13. data/activerecord-jdbc-adapter.gemspec +55 -0
  14. data/activerecord-jdbc-alt-adapter.gemspec +56 -0
  15. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  16. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  18. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  19. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  20. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  21. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  22. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  23. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
  25. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  31. data/lib/activerecord-jdbc-adapter.rb +1 -0
  32. data/lib/arel/visitors/compat.rb +60 -0
  33. data/lib/arel/visitors/db2.rb +137 -0
  34. data/lib/arel/visitors/derby.rb +112 -0
  35. data/lib/arel/visitors/firebird.rb +79 -0
  36. data/lib/arel/visitors/h2.rb +25 -0
  37. data/lib/arel/visitors/hsqldb.rb +32 -0
  38. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  39. data/lib/arel/visitors/sql_server.rb +225 -0
  40. data/lib/arel/visitors/sql_server/ng42.rb +294 -0
  41. data/lib/arel/visitors/sqlserver.rb +214 -0
  42. data/lib/arjdbc.rb +19 -0
  43. data/lib/arjdbc/abstract/connection_management.rb +35 -0
  44. data/lib/arjdbc/abstract/core.rb +74 -0
  45. data/lib/arjdbc/abstract/database_statements.rb +64 -0
  46. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  47. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  48. data/lib/arjdbc/db2.rb +4 -0
  49. data/lib/arjdbc/db2/adapter.rb +789 -0
  50. data/lib/arjdbc/db2/as400.rb +130 -0
  51. data/lib/arjdbc/db2/column.rb +167 -0
  52. data/lib/arjdbc/db2/connection_methods.rb +44 -0
  53. data/lib/arjdbc/derby.rb +3 -0
  54. data/lib/arjdbc/derby/active_record_patch.rb +13 -0
  55. data/lib/arjdbc/derby/adapter.rb +540 -0
  56. data/lib/arjdbc/derby/connection_methods.rb +20 -0
  57. data/lib/arjdbc/derby/schema_creation.rb +15 -0
  58. data/lib/arjdbc/discover.rb +104 -0
  59. data/lib/arjdbc/firebird.rb +4 -0
  60. data/lib/arjdbc/firebird/adapter.rb +434 -0
  61. data/lib/arjdbc/firebird/connection_methods.rb +23 -0
  62. data/lib/arjdbc/h2.rb +3 -0
  63. data/lib/arjdbc/h2/adapter.rb +303 -0
  64. data/lib/arjdbc/h2/connection_methods.rb +27 -0
  65. data/lib/arjdbc/hsqldb.rb +3 -0
  66. data/lib/arjdbc/hsqldb/adapter.rb +297 -0
  67. data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
  68. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  69. data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
  70. data/lib/arjdbc/informix.rb +5 -0
  71. data/lib/arjdbc/informix/adapter.rb +162 -0
  72. data/lib/arjdbc/informix/connection_methods.rb +9 -0
  73. data/lib/arjdbc/jdbc.rb +59 -0
  74. data/lib/arjdbc/jdbc/adapter.rb +475 -0
  75. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  76. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  77. data/lib/arjdbc/jdbc/callbacks.rb +53 -0
  78. data/lib/arjdbc/jdbc/column.rb +97 -0
  79. data/lib/arjdbc/jdbc/connection.rb +14 -0
  80. data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
  81. data/lib/arjdbc/jdbc/error.rb +65 -0
  82. data/lib/arjdbc/jdbc/extension.rb +59 -0
  83. data/lib/arjdbc/jdbc/java.rb +13 -0
  84. data/lib/arjdbc/jdbc/railtie.rb +2 -0
  85. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
  86. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
  87. data/lib/arjdbc/jdbc/type_cast.rb +166 -0
  88. data/lib/arjdbc/jdbc/type_converter.rb +142 -0
  89. data/lib/arjdbc/mssql.rb +7 -0
  90. data/lib/arjdbc/mssql/adapter.rb +384 -0
  91. data/lib/arjdbc/mssql/column.rb +29 -0
  92. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  93. data/lib/arjdbc/mssql/database_statements.rb +134 -0
  94. data/lib/arjdbc/mssql/errors.rb +6 -0
  95. data/lib/arjdbc/mssql/explain_support.rb +129 -0
  96. data/lib/arjdbc/mssql/extensions.rb +36 -0
  97. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  98. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  99. data/lib/arjdbc/mssql/old_adapter.rb +804 -0
  100. data/lib/arjdbc/mssql/old_column.rb +200 -0
  101. data/lib/arjdbc/mssql/quoting.rb +101 -0
  102. data/lib/arjdbc/mssql/schema_creation.rb +31 -0
  103. data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
  104. data/lib/arjdbc/mssql/schema_statements.rb +329 -0
  105. data/lib/arjdbc/mssql/transaction.rb +69 -0
  106. data/lib/arjdbc/mssql/types.rb +52 -0
  107. data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
  108. data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
  109. data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
  110. data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
  111. data/lib/arjdbc/mssql/types/string_types.rb +56 -0
  112. data/lib/arjdbc/mssql/utils.rb +66 -0
  113. data/lib/arjdbc/mysql.rb +3 -0
  114. data/lib/arjdbc/mysql/adapter.rb +140 -0
  115. data/lib/arjdbc/mysql/connection_methods.rb +166 -0
  116. data/lib/arjdbc/oracle/adapter.rb +863 -0
  117. data/lib/arjdbc/postgresql.rb +3 -0
  118. data/lib/arjdbc/postgresql/adapter.rb +687 -0
  119. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  120. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  121. data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
  122. data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
  123. data/lib/arjdbc/postgresql/column.rb +51 -0
  124. data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
  125. data/lib/arjdbc/postgresql/name.rb +24 -0
  126. data/lib/arjdbc/postgresql/oid_types.rb +266 -0
  127. data/lib/arjdbc/railtie.rb +11 -0
  128. data/lib/arjdbc/sqlite3.rb +3 -0
  129. data/lib/arjdbc/sqlite3/adapter.rb +678 -0
  130. data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
  131. data/lib/arjdbc/sybase.rb +2 -0
  132. data/lib/arjdbc/sybase/adapter.rb +47 -0
  133. data/lib/arjdbc/tasks.rb +13 -0
  134. data/lib/arjdbc/tasks/database_tasks.rb +31 -0
  135. data/lib/arjdbc/tasks/databases.rake +48 -0
  136. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  137. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  138. data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
  139. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  140. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
  141. data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
  142. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  143. data/lib/arjdbc/util/serialized_attributes.rb +98 -0
  144. data/lib/arjdbc/util/table_copier.rb +110 -0
  145. data/lib/arjdbc/version.rb +3 -0
  146. data/lib/generators/jdbc/USAGE +9 -0
  147. data/lib/generators/jdbc/jdbc_generator.rb +17 -0
  148. data/lib/jdbc_adapter.rb +2 -0
  149. data/lib/jdbc_adapter/rake_tasks.rb +4 -0
  150. data/lib/jdbc_adapter/version.rb +4 -0
  151. data/pom.xml +114 -0
  152. data/rails_generators/jdbc_generator.rb +15 -0
  153. data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
  154. data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
  155. data/rakelib/01-tomcat.rake +51 -0
  156. data/rakelib/02-test.rake +132 -0
  157. data/rakelib/bundler_ext.rb +11 -0
  158. data/rakelib/db.rake +75 -0
  159. data/rakelib/rails.rake +223 -0
  160. data/src/java/arjdbc/ArJdbcModule.java +276 -0
  161. data/src/java/arjdbc/db2/DB2Module.java +76 -0
  162. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
  163. data/src/java/arjdbc/derby/DerbyModule.java +178 -0
  164. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
  165. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
  166. data/src/java/arjdbc/h2/H2Module.java +50 -0
  167. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
  168. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
  169. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
  170. data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
  171. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  172. data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
  173. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
  174. data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
  175. data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
  176. data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
  177. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  178. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
  179. data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
  180. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
  181. data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
  182. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
  183. data/src/java/arjdbc/oracle/OracleModule.java +80 -0
  184. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
  185. data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
  186. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  187. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
  188. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
  189. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
  190. data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
  191. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
  192. data/src/java/arjdbc/util/CallResultSet.java +826 -0
  193. data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
  194. data/src/java/arjdbc/util/ObjectSupport.java +65 -0
  195. data/src/java/arjdbc/util/QuotingUtils.java +137 -0
  196. data/src/java/arjdbc/util/StringCache.java +63 -0
  197. data/src/java/arjdbc/util/StringHelper.java +145 -0
  198. metadata +269 -0
@@ -0,0 +1,66 @@
1
+ # NOTE: file contains code adapted from **sqlserver** adapter, license follows
2
+ =begin
3
+ Copyright (c) 2008-2015
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ =end
24
+
25
+ module ArJdbc
26
+ module MSSQL
27
+ module Utils
28
+
29
+ module_function
30
+
31
+ def unquote_table_name(table_name)
32
+ remove_identifier_delimiters(table_name)
33
+ end
34
+
35
+ def unquote_column_name(column_name)
36
+ remove_identifier_delimiters(column_name)
37
+ end
38
+
39
+ def unquote_string(string)
40
+ string.to_s.gsub("''", "'")
41
+ end
42
+
43
+ def unqualify_table_name(table_name)
44
+ remove_identifier_delimiters(table_name.to_s.split('.').last)
45
+ end
46
+
47
+ def unqualify_table_schema(table_name)
48
+ schema_name = table_name.to_s.split('.')[-2]
49
+ schema_name.nil? ? nil : remove_identifier_delimiters(schema_name)
50
+ end
51
+
52
+ def unqualify_db_name(table_name)
53
+ table_names = table_name.to_s.split('.')
54
+ table_names.length == 3 ? remove_identifier_delimiters(table_names.first) : nil
55
+ end
56
+
57
+ # private
58
+
59
+ # See "Delimited Identifiers": http://msdn.microsoft.com/en-us/library/ms176027.aspx
60
+ def remove_identifier_delimiters(keyword)
61
+ keyword.to_s.tr("\]\[\"", '')
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ require 'arjdbc'
2
+ require 'arjdbc/mysql/adapter'
3
+ require 'arjdbc/mysql/connection_methods'
@@ -0,0 +1,140 @@
1
+ ArJdbc.load_java_part :MySQL
2
+
3
+ require 'bigdecimal'
4
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
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'
11
+
12
+ module ActiveRecord
13
+ module ConnectionAdapters
14
+ AbstractMysqlAdapter.class_eval do
15
+ include ArJdbc::Abstract::Core # to have correct initialize() super
16
+ end
17
+
18
+ # Remove any vestiges of core/Ruby MySQL adapter
19
+ remove_const(:Mysql2Adapter) if const_defined?(:Mysql2Adapter)
20
+
21
+ class Mysql2Adapter < AbstractMysqlAdapter
22
+ ADAPTER_NAME = 'Mysql2'.freeze
23
+
24
+ include Jdbc::ConnectionPoolCallbacks
25
+
26
+ include ArJdbc::Abstract::ConnectionManagement
27
+ include ArJdbc::Abstract::DatabaseStatements
28
+ # NOTE: do not include MySQL::DatabaseStatements
29
+ include ArJdbc::Abstract::StatementCache
30
+ include ArJdbc::Abstract::TransactionSupport
31
+
32
+ include ArJdbc::MySQL
33
+
34
+ def initialize(connection, logger, connection_parameters, config)
35
+ # workaround to skip version check on JNDI to be lazy, dummy version is high enough for Rails 5.0 - 6.0
36
+ is_jndi = ::ActiveRecord::ConnectionAdapters::JdbcConnection.jndi_config?(config)
37
+ @version = '8.1.5' if is_jndi
38
+
39
+ super
40
+
41
+ # set to nil to have it lazy-load the real value when required
42
+ @version = nil if is_jndi
43
+
44
+ @prepared_statements = false unless config.key?(:prepared_statements)
45
+ # configure_connection taken care of at ArJdbc::Abstract::Core
46
+ end
47
+
48
+ def supports_json?
49
+ !mariadb? && version >= '5.7.8'
50
+ end
51
+
52
+ def supports_comments?
53
+ true
54
+ end
55
+
56
+ def supports_comments_in_create?
57
+ true
58
+ end
59
+
60
+ def supports_savepoints?
61
+ true
62
+ end
63
+
64
+ # HELPER METHODS ===========================================
65
+
66
+ # Reloading the type map in abstract/statement_cache.rb blows up postgres
67
+ def clear_cache!
68
+ reload_type_map
69
+ super
70
+ end
71
+
72
+ def each_hash(result) # :nodoc:
73
+ if block_given?
74
+ # FIXME: This is C in mysql2 gem and I just made simplest Ruby
75
+ result.each do |row|
76
+ new_hash = {}
77
+ row.each { |k, v| new_hash[k.to_sym] = v }
78
+ yield new_hash
79
+ end
80
+ else
81
+ to_enum(:each_hash, result)
82
+ end
83
+ end
84
+
85
+ def error_number(exception)
86
+ exception.error_code if exception.is_a?(JDBCError)
87
+ end
88
+
89
+ def create_table(table_name, **options) #:nodoc:
90
+ super(table_name, options: "ENGINE=InnoDB", **options)
91
+ end
92
+
93
+ #--
94
+ # QUOTING ==================================================
95
+ #++
96
+
97
+ # NOTE: quote_string(string) provided by ArJdbc::MySQL (native code),
98
+ # this piece is also native (mysql2) under MRI: `@connection.escape(string)`
99
+
100
+ def quoted_date(value)
101
+ if supports_datetime_with_precision?
102
+ super
103
+ else
104
+ super.sub(/\.\d{6}\z/, '')
105
+ end
106
+ end
107
+
108
+ def _quote(value)
109
+ if value.is_a?(Type::Binary::Data)
110
+ "x'#{value.hex}'"
111
+ else
112
+ super
113
+ end
114
+ end
115
+ private :_quote
116
+
117
+ #--
118
+ # CONNECTION MANAGEMENT ====================================
119
+ #++
120
+
121
+ alias :reset! :reconnect!
122
+
123
+ #
124
+
125
+ private
126
+
127
+ # e.g. "5.7.20-0ubuntu0.16.04.1"
128
+ def full_version; @full_version ||= @connection.full_version end
129
+
130
+ def jdbc_connection_class(spec)
131
+ ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
132
+ end
133
+
134
+ def jdbc_column_class
135
+ ::ActiveRecord::ConnectionAdapters::MySQL::Column
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+ ArJdbc::ConnectionMethods.module_eval do
3
+ def mysql_connection(config)
4
+ # NOTE: this isn't "really" necessary but Rails (in tests) assumes being able to :
5
+ # ActiveRecord::Base.mysql2_connection ActiveRecord::Base.configurations['arunit'].merge(database: ...)
6
+ config = symbolize_keys_if_necessary(config)
7
+
8
+ config[:adapter_spec] ||= ::ArJdbc::MySQL
9
+ config[:adapter_class] = ActiveRecord::ConnectionAdapters::Mysql2Adapter unless config.key?(:adapter_class)
10
+
11
+ return jndi_connection(config) if jndi_config?(config)
12
+
13
+ driver = config[:driver] ||=
14
+ defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
15
+
16
+ mysql_driver = driver.start_with?('com.mysql.')
17
+ mariadb_driver = ! mysql_driver && driver.start_with?('org.mariadb.')
18
+
19
+ begin
20
+ require 'jdbc/mysql'
21
+ ::Jdbc::MySQL.load_driver(:require) if defined?(::Jdbc::MySQL.load_driver)
22
+ rescue LoadError # assuming driver.jar is on the class-path
23
+ end if mysql_driver
24
+
25
+ config[:username] = 'root' unless config.key?(:username)
26
+ # jdbc:mysql://[host][,failoverhost...][:port]/[database]
27
+ # - if the host name is not specified, it defaults to 127.0.0.1
28
+ # - if the port is not specified, it defaults to 3306
29
+ # - alternate fail-over syntax: [host:port],[host:port]/[database]
30
+ unless config[:url]
31
+ host = config[:host]
32
+ host ||= 'localhost' if mariadb_driver
33
+ host = host.join(',') if host.respond_to?(:join)
34
+ config[:url] = "jdbc:mysql://#{host}#{ config[:port] ? ":#{config[:port]}" : nil }/#{config[:database]}"
35
+ end
36
+
37
+ properties = ( config[:properties] ||= {} )
38
+ if mysql_driver
39
+ properties['zeroDateTimeBehavior'] ||= 'convertToNull'
40
+ properties['jdbcCompliantTruncation'] ||= false
41
+ # NOTE: this is "better" than passing what users are used to set on MRI
42
+ # e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
43
+ # ... it's smart enough to detect utf8mb4 from server variables :
44
+ # "character_set_client" && "character_set_connection" (thus UTF-8)
45
+ if encoding = config.key?(:encoding) ? config[:encoding] : 'utf8'
46
+ charset_name = convert_mysql_encoding(encoding)
47
+ if charset_name.eql?(false) # do not set characterEncoding
48
+ properties['character_set_server'] = encoding
49
+ else
50
+ properties['characterEncoding'] = charset_name || encoding
51
+ end
52
+ # driver also executes: "SET NAMES " + (useutf8mb4 ? "utf8mb4" : "utf8")
53
+ # thus no need to do it on configure_connection :
54
+ config[:encoding] = nil if config.key?(:encoding)
55
+ end
56
+ # properties['useUnicode'] is true by default
57
+ if collation = config[:collation]
58
+ properties['connectionCollation'] = collation
59
+ end
60
+ if ! ( reconnect = config[:reconnect] ).nil?
61
+ properties['autoReconnect'] ||= reconnect.to_s
62
+ # properties['maxReconnects'] ||= '3'
63
+ # with reconnect fail-over sets connection read-only (by default)
64
+ # properties['failOverReadOnly'] ||= 'false'
65
+ end
66
+ end
67
+ if config[:sslkey] || sslcert = config[:sslcert] # || config[:use_ssl]
68
+ properties['useSSL'] ||= true # supported by MariaDB as well
69
+ if mysql_driver
70
+ properties['requireSSL'] ||= true
71
+ properties['clientCertificateKeyStoreUrl'] ||= java.io.File.new(sslcert).to_url.to_s if sslcert
72
+ if sslca = config[:sslca]
73
+ properties['trustCertificateKeyStoreUrl'] ||= java.io.File.new(sslca).to_url.to_s
74
+ else
75
+ properties['verifyServerCertificate'] ||= false
76
+ end
77
+ end
78
+ properties['verifyServerCertificate'] ||= false if mariadb_driver
79
+ else
80
+ # According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection
81
+ # must be established by default if explicit option isn't set :
82
+ properties[mariadb_driver ? 'useSsl' : 'useSSL'] ||= false
83
+ end
84
+ if socket = config[:socket]
85
+ properties['localSocket'] ||= socket if mariadb_driver
86
+ end
87
+
88
+ # for the Connector/J 5.1 line this is true by default - but it requires some really nasty
89
+ # quirks to get casted Time values extracted properly according for AR's default_timezone
90
+ # - thus we're turning it off (should be off in newer driver versions >= 6 anyway)
91
+ # + also MariaDB driver is compilant and we would need to branch out based on driver
92
+ properties['useLegacyDatetimeCode'] = false
93
+
94
+ jdbc_connection(config)
95
+ end
96
+ alias_method :jdbcmysql_connection, :mysql_connection
97
+ alias_method :mysql2_connection, :mysql_connection
98
+
99
+ def mariadb_connection(config)
100
+ config[:adapter_spec] ||= ::ArJdbc::MySQL
101
+ config[:adapter_class] = ActiveRecord::ConnectionAdapters::Mysql2Adapter unless config.key?(:adapter_class)
102
+
103
+ return jndi_connection(config) if jndi_config?(config)
104
+
105
+ begin
106
+ require 'jdbc/mariadb'
107
+ ::Jdbc::MariaDB.load_driver(:require) if defined?(::Jdbc::MariaDB.load_driver)
108
+ rescue LoadError # assuming driver.jar is on the class-path
109
+ end
110
+
111
+ config[:driver] ||= 'org.mariadb.jdbc.Driver'
112
+
113
+ mysql_connection(config)
114
+ end
115
+ alias_method :jdbcmariadb_connection, :mariadb_connection
116
+
117
+ private
118
+
119
+ @@mysql_encodings = nil
120
+
121
+ # @see https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-charsets.html
122
+ def convert_mysql_encoding(encoding) # to charset-name (characterEncoding=...)
123
+ ( @@mysql_encodings ||= {
124
+ "big5" => "Big5",
125
+ "dec8" => nil,
126
+ #"cp850" => "Cp850",
127
+ "hp8" => nil,
128
+ #"koi8r" => "KOI8-R",
129
+ "latin1" => "Cp1252",
130
+ "latin2" => "ISO8859_2",
131
+ "swe7" => nil,
132
+ "ascii" => "US-ASCII",
133
+ "ujis" => "EUC_JP",
134
+ "sjis" => "SJIS",
135
+ "hebrew" => "ISO8859_8",
136
+ "tis620" => "TIS620",
137
+ "euckr" => "EUC_KR",
138
+ #"koi8u" => "KOI8-R",
139
+ "gb2312" => "EUC_CN",
140
+ "greek" => "ISO8859_7",
141
+ "cp1250" => "Cp1250",
142
+ "gbk" => "GBK",
143
+ #"latin5" => "ISO-8859-9",
144
+ "armscii8" => nil,
145
+ "ucs2" => "UnicodeBig",
146
+ "cp866" => "Cp866",
147
+ "keybcs2" => nil,
148
+ "macce" => "MacCentralEurope",
149
+ "macroman" => "MacRoman",
150
+ #"cp852" => "CP852",
151
+ #"latin7" => "ISO-8859-13",
152
+ "cp1251" => "Cp1251",
153
+ "cp1256" => "Cp1256",
154
+ "cp1257" => "Cp1257",
155
+ "binary" => false,
156
+ "geostd8" => nil,
157
+ "cp932" => "Cp932",
158
+ #"eucjpms" => "eucJP-ms"
159
+ "utf8" => "UTF-8",
160
+ "utf8mb4" => false,
161
+ "utf16" => false,
162
+ "utf32" => false,
163
+ } )[ encoding ]
164
+ end
165
+
166
+ end
@@ -0,0 +1,863 @@
1
+ # NOTE: file contains code adapted from **oracle-enhanced** adapter, license follows
2
+ =begin
3
+ Copyright (c) 2008-2011 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ =end
24
+
25
+ ArJdbc.load_java_part :Oracle
26
+
27
+ module ArJdbc
28
+ module Oracle
29
+
30
+ require 'arjdbc/oracle/column'
31
+
32
+ # @private
33
+ def self.extended(adapter); initialize!; end
34
+
35
+ # @private
36
+ @@_initialized = nil
37
+
38
+ # @private
39
+ def self.initialize!
40
+ return if @@_initialized; @@_initialized = true
41
+
42
+ require 'arjdbc/util/serialized_attributes'
43
+ Util::SerializedAttributes.setup /LOB\(|LOB$/i, 'after_save_with_oracle_lob'
44
+
45
+ unless ActiveRecord::ConnectionAdapters::AbstractAdapter.
46
+ instance_methods(false).detect { |m| m.to_s == "prefetch_primary_key?" }
47
+ require 'arjdbc/jdbc/quoted_primary_key'
48
+ ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
49
+ end
50
+ end
51
+
52
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
53
+ def self.jdbc_connection_class
54
+ ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
55
+ end
56
+
57
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
58
+ def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::OracleColumn end
59
+
60
+ # @private
61
+ @@update_lob_values = true
62
+
63
+ # Updating records with LOB values (binary/text columns) in a separate
64
+ # statement can be disabled using :
65
+ #
66
+ # ArJdbc::Oracle.update_lob_values = false
67
+ #
68
+ # @note This only applies when prepared statements are not used.
69
+ def self.update_lob_values?; @@update_lob_values; end
70
+ # @see #update_lob_values?
71
+ def self.update_lob_values=(update); @@update_lob_values = update; end
72
+
73
+ # @see #update_lob_values?
74
+ # @see ArJdbc::Util::SerializedAttributes#update_lob_columns
75
+ def update_lob_value?(value, column = nil)
76
+ Oracle.update_lob_values? && ! prepared_statements? && ! ( value.nil? || value == '' )
77
+ end
78
+
79
+ # @private
80
+ @@emulate_booleans = true
81
+
82
+ # Boolean emulation can be disabled using :
83
+ #
84
+ # ArJdbc::Oracle.emulate_booleans = false
85
+ #
86
+ # @see ActiveRecord::ConnectionAdapters::OracleAdapter#emulate_booleans
87
+ def self.emulate_booleans?; @@emulate_booleans; end
88
+ # @deprecated Use {#emulate_booleans?} instead.
89
+ def self.emulate_booleans; @@emulate_booleans; end
90
+ # @see #emulate_booleans?
91
+ def self.emulate_booleans=(emulate); @@emulate_booleans = emulate; end
92
+
93
+ class TableDefinition < ::ActiveRecord::ConnectionAdapters::TableDefinition
94
+ def raw(*args)
95
+ options = args.extract_options!
96
+ column(args[0], 'raw', options)
97
+ end
98
+
99
+ def xml(*args)
100
+ options = args.extract_options!
101
+ column(args[0], 'xml', options)
102
+ end
103
+ end
104
+
105
+ def table_definition(*args)
106
+ new_table_definition(TableDefinition, *args)
107
+ end
108
+
109
+ def self.arel_visitor_type(config = nil)
110
+ ::Arel::Visitors::Oracle
111
+ end
112
+
113
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#bind_substitution
114
+ # @private
115
+ class BindSubstitution < ::Arel::Visitors::Oracle
116
+ include ::Arel::Visitors::BindVisitor
117
+ end if defined? ::Arel::Visitors::BindVisitor
118
+
119
+ ADAPTER_NAME = 'Oracle'.freeze
120
+
121
+ def adapter_name
122
+ ADAPTER_NAME
123
+ end
124
+
125
+ NATIVE_DATABASE_TYPES = {
126
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
127
+ :string => { :name => "VARCHAR2", :limit => 255 },
128
+ :text => { :name => "CLOB" },
129
+ :integer => { :name => "NUMBER", :limit => 38 },
130
+ :float => { :name => "NUMBER" },
131
+ :decimal => { :name => "DECIMAL" },
132
+ :datetime => { :name => "DATE" },
133
+ :timestamp => { :name => "TIMESTAMP" },
134
+ :time => { :name => "DATE" },
135
+ :date => { :name => "DATE" },
136
+ :binary => { :name => "BLOB" },
137
+ :boolean => { :name => "NUMBER", :limit => 1 },
138
+ :raw => { :name => "RAW", :limit => 2000 },
139
+ :xml => { :name => 'XMLTYPE' }
140
+ }
141
+
142
+ def native_database_types
143
+ super.merge(NATIVE_DATABASE_TYPES)
144
+ end
145
+
146
+ def modify_types(types)
147
+ super(types)
148
+ NATIVE_DATABASE_TYPES.each do |key, value|
149
+ types[key] = value.dup
150
+ end
151
+ types
152
+ end
153
+
154
+ # Prevent ORA-01795 for in clauses with more than 1000
155
+ def in_clause_length
156
+ 1000
157
+ end
158
+ alias_method :ids_in_list_limit, :in_clause_length
159
+
160
+ IDENTIFIER_LENGTH = 30
161
+
162
+ # maximum length of Oracle identifiers is 30
163
+ def table_alias_length; IDENTIFIER_LENGTH; end
164
+ def table_name_length; IDENTIFIER_LENGTH; end
165
+ def index_name_length; IDENTIFIER_LENGTH; end
166
+ def column_name_length; IDENTIFIER_LENGTH; end
167
+ def sequence_name_length; IDENTIFIER_LENGTH end
168
+
169
+ # @private
170
+ # Will take all or first 26 characters of table name and append _seq suffix
171
+ def default_sequence_name(table_name, primary_key = nil)
172
+ len = IDENTIFIER_LENGTH - 4
173
+ table_name.to_s.gsub (/(^|\.)([\w$-]{1,#{len}})([\w$-]*)$/), '\1\2_seq'
174
+ end
175
+
176
+ # @private
177
+ def default_trigger_name(table_name)
178
+ "#{table_name.to_s[0, IDENTIFIER_LENGTH - 4]}_pkt"
179
+ end
180
+
181
+ # @override
182
+ def create_table(name, options = {})
183
+ super(name, options)
184
+ unless options[:id] == false
185
+ seq_name = options[:sequence_name] || default_sequence_name(name)
186
+ start_value = options[:sequence_start_value] || 10000
187
+ raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
188
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{start_value}"
189
+ end
190
+ end
191
+
192
+ # @override
193
+ def rename_table(name, new_name)
194
+ if new_name.to_s.length > table_name_length
195
+ raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
196
+ end
197
+ if "#{new_name}_seq".to_s.length > sequence_name_length
198
+ raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters"
199
+ end
200
+ execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
201
+ execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
202
+ end
203
+
204
+ # @override
205
+ def drop_table(name, options = {})
206
+ outcome = super(name)
207
+ return outcome if name == 'schema_migrations'
208
+ seq_name = options.key?(:sequence_name) ? # pass nil/false - no sequence
209
+ options[:sequence_name] : default_sequence_name(name)
210
+ return outcome unless seq_name
211
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
212
+ end
213
+
214
+ # @override
215
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
216
+ case type.to_sym
217
+ when :binary
218
+ # { BLOB | BINARY LARGE OBJECT } [ ( length [{K |M |G }] ) ]
219
+ # although Oracle does not like limit (length) with BLOB (or CLOB) :
220
+ #
221
+ # CREATE TABLE binaries (data BLOB, short_data BLOB(1024));
222
+ # ORA-00907: missing right parenthesis *
223
+ #
224
+ # TODO do we need to worry about NORMAL vs. non IN-TABLE BLOBs ?!
225
+ # http://dba.stackexchange.com/questions/8770/improve-blob-writing-performance-in-oracle-11g
226
+ # - if the LOB is smaller than 3900 bytes it can be stored inside the
227
+ # table row; by default this is enabled,
228
+ # unless you specify DISABLE STORAGE IN ROW
229
+ # - normal LOB - stored in a separate segment, outside of table,
230
+ # you may even put it in another tablespace;
231
+ super(type, nil, nil, nil)
232
+ when :text
233
+ super(type, nil, nil, nil)
234
+ else
235
+ super
236
+ end
237
+ end
238
+
239
+ def indexes(table, name = nil)
240
+ @connection.indexes(table, name, @connection.connection.meta_data.user_name)
241
+ end
242
+
243
+ # @note Only used with (non-AREL) ActiveRecord **2.3**.
244
+ # @see Arel::Visitors::Oracle
245
+ def add_limit_offset!(sql, options)
246
+ offset = options[:offset] || 0
247
+ if limit = options[:limit]
248
+ sql.replace "SELECT * FROM " <<
249
+ "(select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset + limit})" <<
250
+ " WHERE raw_rnum_ > #{offset}"
251
+ elsif offset > 0
252
+ sql.replace "SELECT * FROM " <<
253
+ "(select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_)" <<
254
+ " WHERE raw_rnum_ > #{offset}"
255
+ end
256
+ end if ::ActiveRecord::VERSION::MAJOR < 3
257
+
258
+ def current_user
259
+ @current_user ||= execute("SELECT sys_context('userenv', 'session_user') su FROM dual").first['su']
260
+ end
261
+
262
+ def current_database
263
+ @current_database ||= execute("SELECT sys_context('userenv', 'db_name') db FROM dual").first['db']
264
+ end
265
+
266
+ def current_schema
267
+ execute("SELECT sys_context('userenv', 'current_schema') schema FROM dual").first['schema']
268
+ end
269
+
270
+ def current_schema=(schema_owner)
271
+ execute("ALTER SESSION SET current_schema=#{schema_owner}")
272
+ end
273
+
274
+ # @override
275
+ def release_savepoint(name = nil)
276
+ # no RELEASE SAVEPOINT statement in Oracle (JDBC driver throws "Unsupported feature")
277
+ end
278
+
279
+ # @override
280
+ def add_index(table_name, column_name, options = {})
281
+ index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options)
282
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}"
283
+ if index_type == 'UNIQUE'
284
+ unless quoted_column_names =~ /\(.*\)/
285
+ execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
286
+ end
287
+ end
288
+ end if AR42
289
+
290
+ # @private
291
+ def add_index_options(table_name, column_name, options = {})
292
+ column_names = Array(column_name)
293
+ index_name = index_name(table_name, column: column_names)
294
+
295
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
296
+
297
+ index_type = options[:unique] ? "UNIQUE" : ""
298
+ index_name = options[:name].to_s if options.key?(:name)
299
+ tablespace = '' # tablespace_for(:index, options[:tablespace])
300
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
301
+ index_options = '' # index_options = options[:options]
302
+
303
+ if index_name.to_s.length > max_index_length
304
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
305
+ end
306
+ if index_name_exists?(table_name, index_name, false)
307
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
308
+ end
309
+
310
+ quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
311
+ [ index_name, index_type, quoted_column_names, tablespace, index_options ]
312
+ end if AR42
313
+
314
+ # @override
315
+ def remove_index(table_name, options = {})
316
+ index_name = index_name(table_name, options)
317
+ unless index_name_exists?(table_name, index_name, true)
318
+ # sometimes options can be String or Array with column names
319
+ options = {} unless options.is_a?(Hash)
320
+ if options.has_key? :name
321
+ options_without_column = options.dup
322
+ options_without_column.delete :column
323
+ index_name_without_column = index_name(table_name, options_without_column)
324
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
325
+ end
326
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
327
+ end
328
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
329
+ execute "DROP INDEX #{quote_column_name(index_name)}"
330
+ end if AR42
331
+
332
+ # @private
333
+ def remove_index(table_name, options = {})
334
+ execute "DROP INDEX #{index_name(table_name, options)}"
335
+ end unless AR42
336
+
337
+ def change_column_default(table_name, column_name, default)
338
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
339
+ end
340
+
341
+ # @override
342
+ def add_column_options!(sql, options)
343
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
344
+ if options_include_default?(options) && (column = options[:column]) && column.type == :text
345
+ sql << " DEFAULT #{quote(options.delete(:default))}"
346
+ end
347
+ super
348
+ end
349
+
350
+ # @override
351
+ def change_column(table_name, column_name, type, options = {})
352
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} " <<
353
+ "MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}"
354
+ add_column_options!(change_column_sql, options)
355
+ execute(change_column_sql)
356
+ end
357
+
358
+ # @override
359
+ def rename_column(table_name, column_name, new_column_name)
360
+ execute "ALTER TABLE #{quote_table_name(table_name)} " <<
361
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
362
+ end
363
+
364
+ if ActiveRecord::VERSION::MAJOR >= 4
365
+
366
+ # @override
367
+ def remove_column(table_name, column_name, type = nil, options = {})
368
+ do_remove_column(table_name, column_name)
369
+ end
370
+
371
+ else
372
+
373
+ # @override
374
+ def remove_column(table_name, *column_names)
375
+ for column_name in column_names.flatten
376
+ do_remove_column(table_name, column_name)
377
+ end
378
+ end
379
+ alias remove_columns remove_column
380
+
381
+ end
382
+
383
+ def do_remove_column(table_name, column_name)
384
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
385
+ end
386
+ private :do_remove_column
387
+
388
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
389
+ #
390
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
391
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
392
+ # won't actually get a distinct list of the column you want (presuming the column
393
+ # has duplicates with multiple values for the ordered-by columns. So we use the
394
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
395
+ # making every row the same.
396
+ #
397
+ # distinct("posts.id", "posts.created_at desc")
398
+ #
399
+ # @override
400
+ def distinct(columns, order_by)
401
+ "DISTINCT #{columns_for_distinct(columns, order_by)}"
402
+ end
403
+
404
+ # @override Since AR 4.0 (on 4.1 {#distinct} is gone and won't be called).
405
+ def columns_for_distinct(columns, orders)
406
+ return columns if orders.blank?
407
+ if orders.is_a?(Array) # AR 3.x vs 4.x
408
+ orders = orders.map { |column| column.is_a?(String) ? column : column.to_sql }
409
+ else
410
+ orders = extract_order_columns(orders)
411
+ end
412
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
413
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
414
+ order_columns = orders.map do |c, i|
415
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
416
+ end
417
+ columns = [ columns ]; columns.flatten!
418
+ columns.push( *order_columns ).join(', ')
419
+ end
420
+
421
+ # ORDER BY clause for the passed order option.
422
+ #
423
+ # Uses column aliases as defined by {#distinct}.
424
+ def add_order_by_for_association_limiting!(sql, options)
425
+ return sql if options[:order].blank?
426
+
427
+ order_columns = extract_order_columns(options[:order]) do |columns|
428
+ columns.map! { |s| $1 if s =~ / (.*)/ }; columns
429
+ end
430
+ order = order_columns.map { |s, i| "alias_#{i}__ #{s}" } # @see {#distinct}
431
+
432
+ sql << "ORDER BY #{order.join(', ')}"
433
+ end
434
+
435
+ def extract_order_columns(order_by)
436
+ columns = order_by.split(',')
437
+ columns.map!(&:strip); columns.reject!(&:blank?)
438
+ columns = yield(columns) if block_given?
439
+ columns.zip( (0...columns.size).to_a )
440
+ end
441
+ private :extract_order_columns
442
+
443
+ def temporary_table?(table_name)
444
+ select_value("SELECT temporary FROM user_tables WHERE table_name = '#{table_name.upcase}'") == 'Y'
445
+ end
446
+
447
+ def tables
448
+ @connection.tables(nil, oracle_schema)
449
+ end
450
+
451
+ # NOTE: better to use current_schema instead of the configured one ?!
452
+ def columns(table_name, name = nil)
453
+ @connection.columns_internal(table_name.to_s, nil, oracle_schema)
454
+ end
455
+
456
+ def tablespace(table_name)
457
+ select_value "SELECT tablespace_name FROM user_tables WHERE table_name='#{table_name.to_s.upcase}'"
458
+ end
459
+
460
+ def charset
461
+ database_parameters['NLS_CHARACTERSET']
462
+ end
463
+
464
+ def collation
465
+ database_parameters['NLS_COMP']
466
+ end
467
+
468
+ def database_parameters
469
+ return @database_parameters unless ( @database_parameters ||= {} ).empty?
470
+ @connection.execute_query_raw("SELECT * FROM NLS_DATABASE_PARAMETERS") do
471
+ |name, value| @database_parameters[name] = value
472
+ end
473
+ @database_parameters
474
+ end
475
+
476
+ # QUOTING ==================================================
477
+
478
+ # @override
479
+ def quote_table_name(name)
480
+ name.to_s.split('.').map{ |n| n.split('@').map{ |m| quote_column_name(m) }.join('@') }.join('.')
481
+ end
482
+
483
+ # @override
484
+ def quote_column_name(name)
485
+ # if only valid lowercase column characters in name
486
+ if ( name = name.to_s ) =~ /\A[a-z][a-z_0-9\$#]*\Z/
487
+ # putting double-quotes around an identifier causes Oracle to treat the
488
+ # identifier as case sensitive (otherwise assumes case-insensitivity) !
489
+ # all upper case is an exception, where double-quotes are meaningless
490
+ "\"#{name.upcase}\"" # name.upcase
491
+ else
492
+ # remove double quotes which cannot be used inside quoted identifier
493
+ "\"#{name.gsub('"', '')}\""
494
+ end
495
+ end
496
+
497
+ def unquote_table_name(name)
498
+ name = name[1...-1] if name[0, 1] == '"'
499
+ name.upcase == name ? name.downcase : name
500
+ end
501
+
502
+ # @override
503
+ def quote(value, column = nil)
504
+ return value if sql_literal?(value)
505
+
506
+ column_type = column && column.type
507
+ if column_type == :text || column_type == :binary
508
+ return 'NULL' if value.nil? || value == ''
509
+ if update_lob_value?(value, column)
510
+ if /(.*?)\([0-9]+\)/ =~ ( sql_type = column.sql_type )
511
+ %Q{empty_#{ $1.downcase }()}
512
+ else
513
+ %Q{empty_#{ sql_type.respond_to?(:downcase) ? sql_type.downcase : 'blob' }()}
514
+ end
515
+ else
516
+ "'#{quote_string(value.to_s)}'"
517
+ end
518
+ elsif column_type == :xml
519
+ "XMLTYPE('#{quote_string(value)}')" # XMLTYPE ?
520
+ elsif column_type == :raw
521
+ quote_raw(value)
522
+ else
523
+ if column.respond_to?(:primary) && column.primary && column.klass != String
524
+ return value.to_i.to_s
525
+ end
526
+
527
+ if column_type == :datetime || column_type == :time
528
+ if value.acts_like?(:time)
529
+ %Q{TO_DATE('#{get_time(value).strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')}
530
+ else
531
+ value.blank? ? 'NULL' : %Q{DATE'#{value}'} # assume correctly formated DATE (string)
532
+ end
533
+ elsif ( like_date = value.acts_like?(:date) ) || column_type == :date
534
+ if value.acts_like?(:time) # value.respond_to?(:strftime)
535
+ %Q{DATE'#{get_time(value).strftime("%Y-%m-%d")}'}
536
+ elsif like_date
537
+ %Q{DATE'#{quoted_date(value)}'} # DATE 'YYYY-MM-DD'
538
+ else
539
+ value.blank? ? 'NULL' : %Q{DATE'#{value}'} # assume correctly formated DATE (string)
540
+ end
541
+ elsif ( like_time = value.acts_like?(:time) ) || column_type == :timestamp
542
+ if like_time
543
+ %Q{TIMESTAMP'#{quoted_date(value, true)}'} # TIMESTAMP 'YYYY-MM-DD HH24:MI:SS.FF'
544
+ else
545
+ value.blank? ? 'NULL' : %Q{TIMESTAMP'#{value}'} # assume correctly formated TIMESTAMP (string)
546
+ end
547
+ else
548
+ super
549
+ end
550
+ end
551
+ end
552
+
553
+ # Quote date/time values for use in SQL input.
554
+ # Includes milliseconds if the value is a Time responding to usec.
555
+ # @override
556
+ def quoted_date(value, time = nil)
557
+ if time || ( time.nil? && value.acts_like?(:time) )
558
+ usec = value.respond_to?(:usec) && (value.usec / 10000.0).round # .428000 -> .43
559
+ return "#{get_time(value).to_s(:db)}.#{sprintf("%02d", usec)}" if usec
560
+ # value.strftime("%Y-%m-%d %H:%M:%S")
561
+ end
562
+ value.to_s(:db)
563
+ end
564
+
565
+ def quote_raw(value)
566
+ value = value.unpack('C*') if value.is_a?(String)
567
+ "'#{value.map { |x| "%02X" % x }.join}'"
568
+ end
569
+
570
+ # @override
571
+ def supports_migrations?; true end
572
+
573
+ # @override
574
+ def supports_primary_key?; true end
575
+
576
+ # @override
577
+ def supports_savepoints?; true end
578
+
579
+ # @override
580
+ def supports_explain?; true end
581
+
582
+ # @override
583
+ def supports_views?; true end
584
+
585
+ def truncate(table_name, name = nil)
586
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
587
+ end
588
+
589
+ def explain(arel, binds = [])
590
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
591
+ return if sql =~ /FROM all_/
592
+ exec_update(sql, 'EXPLAIN', binds)
593
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
594
+ end
595
+
596
+ def select(sql, name = nil, binds = [])
597
+ result = super # AR::Result (4.0) or Array (<= 3.2)
598
+ result.columns.delete('raw_rnum_') if result.respond_to?(:columns)
599
+ result.each { |row| row.delete('raw_rnum_') } # Hash rows even for AR::Result
600
+ result
601
+ end
602
+
603
+ @@do_not_prefetch_primary_key = {}
604
+
605
+ # Returns true for Oracle adapter (since Oracle requires primary key
606
+ # values to be pre-fetched before insert).
607
+ # @see #next_sequence_value
608
+ # @override
609
+ def prefetch_primary_key?(table_name = nil)
610
+ return true if table_name.nil?
611
+ do_not_prefetch_hash = @@do_not_prefetch_primary_key
612
+ do_not_prefetch = do_not_prefetch_hash[ table_name = table_name.to_s ]
613
+ if do_not_prefetch.nil?
614
+ owner, desc_table_name, db_link = @connection.describe(table_name, default_owner)
615
+ do_not_prefetch_hash[table_name] = do_not_prefetch =
616
+ ! has_primary_key?(table_name, owner, desc_table_name, db_link) ||
617
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
618
+ end
619
+ ! do_not_prefetch
620
+ end
621
+
622
+ # used to clear prefetch primary key flag for all tables
623
+ # @private
624
+ def clear_prefetch_primary_key; @@do_not_prefetch_primary_key = {} end
625
+
626
+ # @private
627
+ def has_primary_key?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
628
+ ! pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
629
+ end
630
+
631
+ # @private check if table has primary key trigger with _pkt suffix
632
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
633
+ (owner, desc_table_name, db_link) = @connection.describe(table_name, default_owner) unless desc_table_name
634
+
635
+ trigger_name = default_trigger_name(table_name).upcase
636
+ pkt_sql = <<-SQL
637
+ SELECT trigger_name
638
+ FROM all_triggers#{db_link}
639
+ WHERE owner = '#{owner}'
640
+ AND trigger_name = '#{trigger_name}'
641
+ AND table_owner = '#{owner}'
642
+ AND table_name = '#{desc_table_name}'
643
+ AND status = 'ENABLED'
644
+ SQL
645
+ select_value(pkt_sql, 'Primary Key Trigger') ? true : false
646
+ end
647
+
648
+ # use in set_sequence_name to avoid fetching primary key value from sequence
649
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
650
+
651
+ # Returns the next sequence value from a sequence generator. Not generally
652
+ # called directly; used by ActiveRecord to get the next primary key value
653
+ # when inserting a new database record (see #prefetch_primary_key?).
654
+ def next_sequence_value(sequence_name)
655
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
656
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
657
+ sequence_name = quote_table_name(sequence_name)
658
+ sql = "SELECT #{sequence_name}.NEXTVAL id FROM dual"
659
+ log(sql, 'SQL') { @connection.next_sequence_value(sequence_name) }
660
+ end
661
+
662
+ def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil, db_link = nil)
663
+ (owner, desc_table_name, db_link) = @connection.describe(table_name, default_owner) unless desc_table_name
664
+
665
+ seqs = select_values(<<-SQL.strip.gsub(/\s+/, ' '), 'Sequence')
666
+ SELECT us.sequence_name
667
+ FROM all_sequences#{db_link} us
668
+ WHERE us.sequence_owner = '#{owner}'
669
+ AND us.sequence_name = '#{desc_table_name}_SEQ'
670
+ SQL
671
+
672
+ # changed back from user_constraints to all_constraints for consistency
673
+ pks = select_values(<<-SQL.strip.gsub(/\s+/, ' '), 'Primary Key')
674
+ SELECT cc.column_name
675
+ FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc
676
+ WHERE c.owner = '#{owner}'
677
+ AND c.table_name = '#{desc_table_name}'
678
+ AND c.constraint_type = 'P'
679
+ AND cc.owner = c.owner
680
+ AND cc.constraint_name = c.constraint_name
681
+ SQL
682
+
683
+ # only support single column keys
684
+ pks.size == 1 ? [oracle_downcase(pks.first),
685
+ oracle_downcase(seqs.first)] : nil
686
+ end
687
+ private :pk_and_sequence_for
688
+
689
+ # Returns just a table's primary key
690
+ def primary_key(table_name)
691
+ pk_and_sequence = pk_and_sequence_for(table_name)
692
+ pk_and_sequence && pk_and_sequence.first
693
+ end
694
+
695
+ # @override (for AR <= 3.0)
696
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
697
+ # if PK is already pre-fetched from sequence or if there is no PK :
698
+ if id_value || pk.nil?
699
+ execute(sql, name)
700
+ return id_value
701
+ end
702
+
703
+ if pk && use_insert_returning? # true by default on AR <= 3.0
704
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO ?"
705
+ exec_insert_returning(sql, name, nil, pk)
706
+ else
707
+ execute(sql, name)
708
+ end
709
+ end
710
+ protected :insert_sql
711
+
712
+ # @override
713
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
714
+ unless id_value || pk.nil?
715
+ if pk && use_insert_returning?
716
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO ?"
717
+ end
718
+ end
719
+ [ sql, binds ]
720
+ end
721
+
722
+ # @override
723
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
724
+ # NOTE: ActiveRecord::Relation calls our {#next_sequence_value}
725
+ # (from its `insert`) and passes the returned id_value here ...
726
+ sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
727
+ if id_value
728
+ exec_update(sql, name, binds)
729
+ return id_value
730
+ else
731
+ value = exec_insert(sql, name, binds, pk, sequence_name)
732
+ id_value || last_inserted_id(value)
733
+ end
734
+ end
735
+
736
+ # @override
737
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
738
+ if pk && use_insert_returning?
739
+ if sql.is_a?(String) && sql.index('RETURNING')
740
+ return exec_insert_returning(sql, name, binds, pk)
741
+ end
742
+ end
743
+ super(sql, name, binds) # assume no generated id for table
744
+ end
745
+
746
+ def exec_insert_returning(sql, name, binds, pk = nil)
747
+ sql = to_sql(sql, binds) if sql.respond_to?(:to_sql)
748
+ if prepared_statements?
749
+ log(sql, name, binds) { @connection.execute_insert_returning(sql, binds) }
750
+ else
751
+ log(sql, name) { @connection.execute_insert_returning(sql, nil) }
752
+ end
753
+ end
754
+ # private :exec_insert_returning
755
+
756
+ def next_id_value(sql, sequence_name = nil)
757
+ # Assume the SQL contains a bind-variable for the ID
758
+ sequence_name ||= begin
759
+ # Extract the table from the insert SQL. Yuck.
760
+ table = extract_table_ref_from_insert_sql(sql)
761
+ default_sequence_name(table)
762
+ end
763
+ next_sequence_value(sequence_name)
764
+ end
765
+ private :next_id_value
766
+
767
+ def use_insert_returning?
768
+ if @use_insert_returning.nil?
769
+ @use_insert_returning = false
770
+ end
771
+ @use_insert_returning
772
+ end
773
+
774
+ private
775
+
776
+ def _execute(sql, name = nil)
777
+ if self.class.select?(sql)
778
+ @connection.execute_query_raw(sql)
779
+ elsif self.class.insert?(sql)
780
+ @connection.execute_insert(sql)
781
+ else
782
+ @connection.execute_update(sql)
783
+ end
784
+ end
785
+
786
+ def extract_table_ref_from_insert_sql(sql)
787
+ table = sql.split(" ", 4)[2]
788
+ if idx = table.index('(')
789
+ table = table[0...idx] # INTO table(col1, col2) ...
790
+ end
791
+ unquote_table_name(table)
792
+ end
793
+
794
+ # In Oracle, schemas are usually created under your username :
795
+ # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
796
+ #
797
+ # A schema is the set of objects (tables, views, indexes, etc) that belongs
798
+ # to an user, often used as another way to refer to an Oracle user account.
799
+ #
800
+ # But allow separate configuration as "schema:" anyway (see #53)
801
+ def oracle_schema
802
+ if @config[:schema]
803
+ @config[:schema].to_s
804
+ elsif @config[:username]
805
+ @config[:username].to_s
806
+ end
807
+ end
808
+
809
+ # default schema owner
810
+ def default_owner
811
+ unless defined? @default_owner
812
+ username = config[:username] ? config[:username].to_s : jdbc_connection.meta_data.user_name
813
+ @default_owner = username.nil? ? nil : username.upcase
814
+ end
815
+ @default_owner
816
+ end
817
+
818
+ def oracle_downcase(column_name)
819
+ return nil if column_name.nil?
820
+ column_name =~ /[a-z]/ ? column_name : column_name.downcase
821
+ end
822
+
823
+ end
824
+ end
825
+
826
+ require 'arjdbc/util/quoted_cache'
827
+
828
+ module ActiveRecord::ConnectionAdapters
829
+
830
+ remove_const(:OracleAdapter) if const_defined?(:OracleAdapter)
831
+
832
+ class OracleAdapter < JdbcAdapter
833
+ include ::ArJdbc::Oracle
834
+ include ::ArJdbc::Util::QuotedCache
835
+
836
+ # By default, the MysqlAdapter will consider all columns of type
837
+ # <tt>tinyint(1)</tt> as boolean. If you wish to disable this :
838
+ #
839
+ # ActiveRecord::ConnectionAdapters::OracleAdapter.emulate_booleans = false
840
+ #
841
+ def self.emulate_booleans?; ::ArJdbc::Oracle.emulate_booleans?; end
842
+ def self.emulate_booleans; ::ArJdbc::Oracle.emulate_booleans?; end # oracle-enhanced
843
+ def self.emulate_booleans=(emulate); ::ArJdbc::Oracle.emulate_booleans = emulate; end
844
+
845
+ def initialize(*args)
846
+ ::ArJdbc::Oracle.initialize!
847
+ super # configure_connection happens in super
848
+
849
+ @use_insert_returning = config.key?(:insert_returning) ?
850
+ self.class.type_cast_config_to_boolean(config[:insert_returning]) : nil
851
+ end
852
+
853
+ end
854
+
855
+ class OracleColumn < JdbcColumn
856
+ include ::ArJdbc::Oracle::Column
857
+
858
+ # def returning_id?; @returning_id ||= nil end
859
+ # def returning_id!; @returning_id = true end
860
+
861
+ end
862
+
863
+ end