activerecord-oracle_enhanced-adapter 8.1.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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1971 -0
  3. data/License.txt +20 -0
  4. data/README.md +947 -0
  5. data/VERSION +1 -0
  6. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +359 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +325 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +629 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +57 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +465 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +739 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +394 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +886 -0
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
  30. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  31. data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
  32. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  34. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  35. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  36. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  37. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  38. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  39. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  40. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  41. data/lib/arel/visitors/oracle.rb +216 -0
  42. data/lib/arel/visitors/oracle12.rb +121 -0
  43. data/lib/arel/visitors/oracle_common.rb +51 -0
  44. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +40 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +589 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1318 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +485 -0
  57. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  58. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  59. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  60. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  61. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  62. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  63. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  64. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  65. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  66. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +101 -0
  67. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  68. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  69. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  70. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  71. data/spec/active_record/oracle_enhanced/type/text_spec.rb +295 -0
  72. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +107 -0
  73. data/spec/spec_config.yaml.template +11 -0
  74. data/spec/spec_helper.rb +225 -0
  75. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  76. data/spec/support/alter_system_user_password.sql +2 -0
  77. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  78. metadata +181 -0
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhanced
6
+ module DatabaseStatements
7
+ # DATABASE STATEMENTS ======================================
8
+ #
9
+ # see: abstract/database_statements.rb
10
+
11
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
12
+ :close, :declare, :fetch, :move, :set, :show
13
+ ) # :nodoc:
14
+ private_constant :READ_QUERY
15
+
16
+ def write_query?(sql) # :nodoc:
17
+ !READ_QUERY.match?(sql)
18
+ rescue ArgumentError # Invalid encoding
19
+ !READ_QUERY.match?(sql.b)
20
+ end
21
+
22
+ # Executes a SQL statement
23
+ def execute(...)
24
+ super
25
+ end
26
+
27
+ # Low level execution of a SQL statement on the connection returning adapter specific result object.
28
+ def raw_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: false)
29
+ sql = preprocess_query(sql)
30
+
31
+ type_casted_binds = type_casted_binds(binds)
32
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
33
+ log(sql, name, binds, type_casted_binds, async: async) do
34
+ cursor = nil
35
+ cached = false
36
+ with_retry do
37
+ if binds.nil? || binds.empty?
38
+ cursor = conn.prepare(sql)
39
+ else
40
+ unless @statements.key? sql
41
+ @statements[sql] = conn.prepare(sql)
42
+ end
43
+
44
+ cursor = @statements[sql]
45
+ cursor.bind_params(type_casted_binds)
46
+
47
+ cached = true
48
+ end
49
+ cursor.exec
50
+ end
51
+
52
+ columns = cursor.get_col_names.map do |col_name|
53
+ oracle_downcase(col_name)
54
+ end
55
+
56
+ rows = []
57
+ if cursor.select_statement?
58
+ fetch_options = { get_lob_value: (name != "Writable Large Object") }
59
+ while row = cursor.fetch(fetch_options)
60
+ rows << row
61
+ end
62
+ end
63
+
64
+ affected_rows_count = cursor.row_count
65
+
66
+ cursor.close unless cached
67
+
68
+ { columns: columns, rows: rows, affected_rows_count: affected_rows_count }
69
+ end
70
+ end
71
+ end
72
+
73
+ def cast_result(result)
74
+ if result.nil?
75
+ ActiveRecord::Result.empty
76
+ else
77
+ ActiveRecord::Result.new(result[:columns], result[:rows])
78
+ end
79
+ end
80
+
81
+ def affected_rows(result)
82
+ result[:affected_rows_count]
83
+ end
84
+
85
+ def supports_explain?
86
+ true
87
+ end
88
+
89
+ def explain(arel, binds = [], options = [])
90
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
91
+ return if /FROM all_/.match?(sql)
92
+ if ORACLE_ENHANCED_CONNECTION == :jdbc
93
+ exec_query(sql, "EXPLAIN", binds)
94
+ else
95
+ exec_query(sql, "EXPLAIN")
96
+ end
97
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", "EXPLAIN").join("\n")
98
+ end
99
+
100
+ def build_explain_clause(options = [])
101
+ # Oracle does not have anything similar to "EXPLAIN ANALYZE"
102
+ # https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/EXPLAIN-PLAN.html#GUID-FD540872-4ED3-4936-96A2-362539931BA0
103
+ end
104
+
105
+ # New method in ActiveRecord 3.1
106
+ # Will add RETURNING clause in case of trigger generated primary keys
107
+ def sql_for_insert(sql, pk, binds, _returning)
108
+ unless pk == false || pk.nil? || pk.is_a?(Array) || pk.is_a?(String)
109
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
110
+ (binds = binds.dup) << ActiveRecord::Relation::QueryAttribute.new("returning_id", nil, Type::OracleEnhanced::Integer.new)
111
+ end
112
+ super
113
+ end
114
+
115
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
116
+ pk = nil if id_value
117
+ Array(super || id_value)
118
+ end
119
+
120
+ # New method in ActiveRecord 3.1
121
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
122
+ sql, binds = sql_for_insert(sql, pk, binds, returning)
123
+ type_casted_binds = type_casted_binds(binds)
124
+
125
+ log(sql, name, binds, type_casted_binds) do
126
+ cached = false
127
+ cursor = nil
128
+ returning_id_col = returning_id_index = nil
129
+ with_retry do
130
+ if binds.nil? || binds.empty?
131
+ cursor = _connection.prepare(sql)
132
+ else
133
+ unless @statements.key?(sql)
134
+ @statements[sql] = _connection.prepare(sql)
135
+ end
136
+
137
+ cursor = @statements[sql]
138
+
139
+ cursor.bind_params(type_casted_binds)
140
+
141
+ if /:returning_id/.match?(sql)
142
+ # it currently expects that returning_id comes last part of binds
143
+ returning_id_index = binds.size
144
+ cursor.bind_returning_param(returning_id_index, Integer)
145
+ end
146
+
147
+ cached = true
148
+ end
149
+
150
+ cursor.exec_update
151
+ end
152
+
153
+ rows = []
154
+ if returning_id_index
155
+ returning_id = cursor.get_returning_param(returning_id_index, Integer).to_i
156
+ rows << [returning_id]
157
+ end
158
+ cursor.close unless cached
159
+ build_result(columns: returning_id_col || [], rows: rows)
160
+ end
161
+ end
162
+
163
+ # New method in ActiveRecord 3.1
164
+ def exec_update(sql, name = nil, binds = [])
165
+ type_casted_binds = type_casted_binds(binds)
166
+
167
+ log(sql, name, binds, type_casted_binds) do
168
+ with_retry do
169
+ cached = false
170
+ if binds.nil? || binds.empty?
171
+ cursor = _connection.prepare(sql)
172
+ else
173
+ if @statements.key?(sql)
174
+ cursor = @statements[sql]
175
+ else
176
+ cursor = @statements[sql] = _connection.prepare(sql)
177
+ end
178
+
179
+ cursor.bind_params(type_casted_binds)
180
+
181
+ cached = true
182
+ end
183
+
184
+ res = cursor.exec_update
185
+ cursor.close unless cached
186
+ res
187
+ end
188
+ end
189
+ end
190
+
191
+ alias :exec_delete :exec_update
192
+
193
+ def returning_column_values(result)
194
+ result.rows.first
195
+ end
196
+
197
+ def begin_db_transaction # :nodoc:
198
+ _connection.autocommit = false
199
+ end
200
+
201
+ def transaction_isolation_levels
202
+ # Oracle database supports `READ COMMITTED` and `SERIALIZABLE`
203
+ # No read uncommitted nor repeatable read supppoted
204
+ # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422
205
+ {
206
+ read_committed: "READ COMMITTED",
207
+ serializable: "SERIALIZABLE"
208
+ }
209
+ end
210
+
211
+ def begin_isolated_db_transaction(isolation)
212
+ begin_db_transaction
213
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
214
+ end
215
+
216
+ def commit_db_transaction # :nodoc:
217
+ _connection.commit
218
+ ensure
219
+ _connection.autocommit = true
220
+ end
221
+
222
+ def exec_rollback_db_transaction # :nodoc:
223
+ _connection.rollback
224
+ ensure
225
+ _connection.autocommit = true
226
+ end
227
+
228
+ def create_savepoint(name = current_savepoint_name) # :nodoc:
229
+ execute("SAVEPOINT #{name}", "TRANSACTION")
230
+ end
231
+
232
+ def exec_rollback_to_savepoint(name = current_savepoint_name) # :nodoc:
233
+ execute("ROLLBACK TO #{name}", "TRANSACTION")
234
+ end
235
+
236
+ def release_savepoint(name = current_savepoint_name) # :nodoc:
237
+ # there is no RELEASE SAVEPOINT statement in Oracle
238
+ end
239
+
240
+ # Returns default sequence name for table.
241
+ # Will take all or first 26 characters of table name and append _seq suffix
242
+ def default_sequence_name(table_name, primary_key = nil)
243
+ table_name.to_s.gsub((/(^|\.)([\w$-]{1,#{sequence_name_length - 4}})([\w$-]*)$/), '\1\2_seq')
244
+ end
245
+
246
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
247
+ def insert_fixture(fixture, table_name) # :nodoc:
248
+ super
249
+
250
+ if ActiveRecord::Base.pluralize_table_names
251
+ klass = table_name.to_s.singularize.camelize
252
+ else
253
+ klass = table_name.to_s.camelize
254
+ end
255
+
256
+ klass = klass.constantize rescue nil
257
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
258
+ write_lobs(table_name, klass, fixture, klass.lob_columns)
259
+ end
260
+ end
261
+
262
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
263
+ disable_referential_integrity do
264
+ transaction(requires_new: true) do
265
+ tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
266
+
267
+ fixture_set.each do |table_name, rows|
268
+ rows.each { |row| insert_fixture(row, table_name) }
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ # Oracle Database does not support this feature
275
+ # Refer https://community.oracle.com/ideas/13845 and consider to vote
276
+ # if you need this feature.
277
+ def empty_insert_statement_value
278
+ raise NotImplementedError
279
+ end
280
+
281
+ # Writes LOB values from attributes for specified columns
282
+ def write_lobs(table_name, klass, attributes, columns) # :nodoc:
283
+ id = quote(attributes[klass.primary_key])
284
+ columns.each do |col|
285
+ value = attributes[col.name]
286
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
287
+ next unless value
288
+ value = klass.attribute_types[col.name].serialize(value)
289
+ # value can be nil after serialization because ActiveRecord serializes [] and {} as nil
290
+ next unless value
291
+ uncached do
292
+ unless lob_record = select_one(sql = <<~SQL.squish, "Writable Large Object")
293
+ SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)}
294
+ WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE
295
+ SQL
296
+ raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
297
+ end
298
+ lob = lob_record[col.name]
299
+ _connection.write_lob(lob, value.to_s, col.type == :binary)
300
+ end
301
+ end
302
+ end
303
+
304
+ private
305
+ def with_retry
306
+ _connection.with_retry do
307
+ yield
308
+ rescue
309
+ @statements.clear
310
+ raise
311
+ end
312
+ end
313
+
314
+ def handle_warnings(sql)
315
+ @notice_receiver_sql_warnings.each do |warning|
316
+ next if warning_ignored?(warning)
317
+
318
+ warning.sql = sql
319
+ ActiveRecord.db_warnings_action.call(warning)
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/base"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ class OracleEnhancedAdapter
8
+ class DatabaseTasks
9
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def create
16
+ system_password = ENV.fetch("ORACLE_SYSTEM_PASSWORD") {
17
+ print "Please provide the SYSTEM password for your Oracle installation (set ORACLE_SYSTEM_PASSWORD to avoid this prompt)\n>"
18
+ $stdin.gets.strip
19
+ }
20
+ establish_connection(@config.merge(username: "SYSTEM", password: system_password))
21
+ begin
22
+ connection.execute "CREATE USER #{@config[:username]} IDENTIFIED BY #{@config[:password]}"
23
+ rescue => e
24
+ if /ORA-01920/.match?(e.message) # user name conflicts with another user or role name
25
+ connection.execute "ALTER USER #{@config[:username]} IDENTIFIED BY #{@config[:password]}"
26
+ else
27
+ raise e
28
+ end
29
+ end
30
+
31
+ OracleEnhancedAdapter.permissions.each do |permission|
32
+ connection.execute "GRANT #{permission} TO #{@config[:username]}"
33
+ end
34
+ end
35
+
36
+ def drop
37
+ establish_connection(@config)
38
+ connection.execute_structure_dump(connection.full_drop)
39
+ end
40
+
41
+ def purge
42
+ drop
43
+ connection.execute("PURGE RECYCLEBIN") rescue nil
44
+ end
45
+
46
+ def structure_dump(filename, extra_flags)
47
+ establish_connection(@config)
48
+ File.open(filename, "w:utf-8") { |f| f << connection.structure_dump }
49
+ if @config[:structure_dump] == "db_stored_code"
50
+ File.open(filename, "a") { |f| f << connection.structure_dump_db_stored_code }
51
+ end
52
+ end
53
+
54
+ def structure_load(filename, extra_flags)
55
+ establish_connection(@config)
56
+ connection.execute_structure_dump(File.read(filename))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/(oci|oracle)/, ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::DatabaseTasks)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhanced
6
+ module DbmsOutput
7
+ # DBMS_OUTPUT =============================================
8
+ #
9
+ # PL/SQL in Oracle uses dbms_output for logging print statements
10
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
11
+ # code can can be debugged together in a single application
12
+
13
+ # Maximum DBMS_OUTPUT buffer size
14
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
15
+
16
+ # Turn DBMS_Output logging on
17
+ def enable_dbms_output
18
+ set_dbms_output_plsql_connection
19
+ @enable_dbms_output = true
20
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
21
+ end
22
+ # Turn DBMS_Output logging off
23
+ def disable_dbms_output
24
+ set_dbms_output_plsql_connection
25
+ @enable_dbms_output = false
26
+ plsql(:dbms_output).sys.dbms_output.disable
27
+ end
28
+ # Is DBMS_Output logging enabled?
29
+ def dbms_output_enabled?
30
+ @enable_dbms_output
31
+ end
32
+
33
+ private
34
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false, &block)
35
+ instrumenter.instrument(
36
+ "sql.active_record",
37
+ sql: sql,
38
+ name: name,
39
+ binds: binds,
40
+ type_casted_binds: type_casted_binds,
41
+ statement_name: statement_name,
42
+ async: async,
43
+ connection: self,
44
+ &block
45
+ )
46
+ rescue => e
47
+ # FIXME: raise ex.set_query(sql, binds)
48
+ raise translate_exception_class(e, sql, binds)
49
+ ensure
50
+ log_dbms_output if dbms_output_enabled?
51
+ end
52
+
53
+ def set_dbms_output_plsql_connection
54
+ raise OracleEnhanced::ConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
55
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
56
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
57
+ plsql(:dbms_output).connection = raw_connection
58
+ end
59
+ end
60
+
61
+ def log_dbms_output
62
+ while true do
63
+ result = plsql(:dbms_output).sys.dbms_output.get_line(line: "", status: 0)
64
+ break unless result[:status] == 0
65
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}" if @logger
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end