activerecord-jdbc-alt-adapter 50.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
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