activerecord-oracle_enhanced-adapter 7.2.1-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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1977 -0
  3. data/License.txt +20 -0
  4. data/README.md +863 -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 +344 -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 +295 -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 +561 -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 +45 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +457 -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 +94 -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 +738 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +366 -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 +865 -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 +36 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +574 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1289 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +474 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  57. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  58. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  59. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  60. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  61. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  62. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  63. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  64. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +99 -0
  65. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  66. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +56 -0
  67. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  68. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  69. data/spec/active_record/oracle_enhanced/type/text_spec.rb +244 -0
  70. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +77 -0
  71. data/spec/spec_config.yaml.template +11 -0
  72. data/spec/spec_helper.rb +214 -0
  73. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  74. data/spec/support/alter_system_user_password.sql +2 -0
  75. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  76. metadata +177 -0
@@ -0,0 +1,295 @@
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
+ # Executes a SQL statement
12
+ def execute(sql, name = nil, async: false, allow_retry: false)
13
+ sql = transform_query(sql)
14
+
15
+ log(sql, name, async: async) { _connection.exec(sql, allow_retry: allow_retry) }
16
+ end
17
+
18
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false)
19
+ sql = transform_query(sql)
20
+
21
+ type_casted_binds = type_casted_binds(binds)
22
+
23
+ log(sql, name, binds, type_casted_binds, async: async) do
24
+ cursor = nil
25
+ cached = false
26
+ with_retry do
27
+ if without_prepared_statement?(binds)
28
+ cursor = _connection.prepare(sql)
29
+ else
30
+ unless @statements.key? sql
31
+ @statements[sql] = _connection.prepare(sql)
32
+ end
33
+
34
+ cursor = @statements[sql]
35
+
36
+ cursor.bind_params(type_casted_binds)
37
+
38
+ cached = true
39
+ end
40
+
41
+ cursor.exec
42
+ end
43
+
44
+ if (name == "EXPLAIN") && sql.start_with?("EXPLAIN")
45
+ res = true
46
+ else
47
+ columns = cursor.get_col_names.map do |col_name|
48
+ oracle_downcase(col_name)
49
+ end
50
+ rows = []
51
+ fetch_options = { get_lob_value: (name != "Writable Large Object") }
52
+ while row = cursor.fetch(fetch_options)
53
+ rows << row
54
+ end
55
+ res = build_result(columns: columns, rows: rows)
56
+ end
57
+
58
+ cursor.close unless cached
59
+ res
60
+ end
61
+ end
62
+ alias_method :internal_exec_query, :exec_query
63
+
64
+ def supports_explain?
65
+ true
66
+ end
67
+
68
+ def explain(arel, binds = [], options = [])
69
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
70
+ return if /FROM all_/.match?(sql)
71
+ if ORACLE_ENHANCED_CONNECTION == :jdbc
72
+ exec_query(sql, "EXPLAIN", binds)
73
+ else
74
+ exec_query(sql, "EXPLAIN")
75
+ end
76
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", "EXPLAIN").join("\n")
77
+ end
78
+
79
+ def build_explain_clause(options = [])
80
+ # Oracle does not have anything similar to "EXPLAIN ANALYZE"
81
+ # https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/EXPLAIN-PLAN.html#GUID-FD540872-4ED3-4936-96A2-362539931BA0
82
+ end
83
+
84
+ # New method in ActiveRecord 3.1
85
+ # Will add RETURNING clause in case of trigger generated primary keys
86
+ def sql_for_insert(sql, pk, binds, _returning)
87
+ unless pk == false || pk.nil? || pk.is_a?(Array) || pk.is_a?(String)
88
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
89
+ (binds = binds.dup) << ActiveRecord::Relation::QueryAttribute.new("returning_id", nil, Type::OracleEnhanced::Integer.new)
90
+ end
91
+ super
92
+ end
93
+
94
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
95
+ pk = nil if id_value
96
+ Array(super || id_value)
97
+ end
98
+
99
+ # New method in ActiveRecord 3.1
100
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
101
+ sql, binds = sql_for_insert(sql, pk, binds, returning)
102
+ type_casted_binds = type_casted_binds(binds)
103
+
104
+ log(sql, name, binds, type_casted_binds) do
105
+ cached = false
106
+ cursor = nil
107
+ returning_id_col = returning_id_index = nil
108
+ with_retry do
109
+ if without_prepared_statement?(binds)
110
+ cursor = _connection.prepare(sql)
111
+ else
112
+ unless @statements.key?(sql)
113
+ @statements[sql] = _connection.prepare(sql)
114
+ end
115
+
116
+ cursor = @statements[sql]
117
+
118
+ cursor.bind_params(type_casted_binds)
119
+
120
+ if /:returning_id/.match?(sql)
121
+ # it currently expects that returning_id comes last part of binds
122
+ returning_id_index = binds.size
123
+ cursor.bind_returning_param(returning_id_index, Integer)
124
+ end
125
+
126
+ cached = true
127
+ end
128
+
129
+ cursor.exec_update
130
+ end
131
+
132
+ rows = []
133
+ if returning_id_index
134
+ returning_id = cursor.get_returning_param(returning_id_index, Integer).to_i
135
+ rows << [returning_id]
136
+ end
137
+ cursor.close unless cached
138
+ build_result(columns: returning_id_col || [], rows: rows)
139
+ end
140
+ end
141
+
142
+ # New method in ActiveRecord 3.1
143
+ def exec_update(sql, name = nil, binds = [])
144
+ type_casted_binds = type_casted_binds(binds)
145
+
146
+ log(sql, name, binds, type_casted_binds) do
147
+ with_retry do
148
+ cached = false
149
+ if without_prepared_statement?(binds)
150
+ cursor = _connection.prepare(sql)
151
+ else
152
+ if @statements.key?(sql)
153
+ cursor = @statements[sql]
154
+ else
155
+ cursor = @statements[sql] = _connection.prepare(sql)
156
+ end
157
+
158
+ cursor.bind_params(type_casted_binds)
159
+
160
+ cached = true
161
+ end
162
+
163
+ res = cursor.exec_update
164
+ cursor.close unless cached
165
+ res
166
+ end
167
+ end
168
+ end
169
+
170
+ alias :exec_delete :exec_update
171
+
172
+ def returning_column_values(result)
173
+ result.rows.first
174
+ end
175
+
176
+ def begin_db_transaction # :nodoc:
177
+ _connection.autocommit = false
178
+ end
179
+
180
+ def transaction_isolation_levels
181
+ # Oracle database supports `READ COMMITTED` and `SERIALIZABLE`
182
+ # No read uncommitted nor repeatable read supppoted
183
+ # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422
184
+ {
185
+ read_committed: "READ COMMITTED",
186
+ serializable: "SERIALIZABLE"
187
+ }
188
+ end
189
+
190
+ def begin_isolated_db_transaction(isolation)
191
+ begin_db_transaction
192
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
193
+ end
194
+
195
+ def commit_db_transaction # :nodoc:
196
+ _connection.commit
197
+ ensure
198
+ _connection.autocommit = true
199
+ end
200
+
201
+ def exec_rollback_db_transaction # :nodoc:
202
+ _connection.rollback
203
+ ensure
204
+ _connection.autocommit = true
205
+ end
206
+
207
+ def create_savepoint(name = current_savepoint_name) # :nodoc:
208
+ execute("SAVEPOINT #{name}", "TRANSACTION")
209
+ end
210
+
211
+ def exec_rollback_to_savepoint(name = current_savepoint_name) # :nodoc:
212
+ execute("ROLLBACK TO #{name}", "TRANSACTION")
213
+ end
214
+
215
+ def release_savepoint(name = current_savepoint_name) # :nodoc:
216
+ # there is no RELEASE SAVEPOINT statement in Oracle
217
+ end
218
+
219
+ # Returns default sequence name for table.
220
+ # Will take all or first 26 characters of table name and append _seq suffix
221
+ def default_sequence_name(table_name, primary_key = nil)
222
+ table_name.to_s.gsub((/(^|\.)([\w$-]{1,#{sequence_name_length - 4}})([\w$-]*)$/), '\1\2_seq')
223
+ end
224
+
225
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
226
+ def insert_fixture(fixture, table_name) # :nodoc:
227
+ super
228
+
229
+ if ActiveRecord::Base.pluralize_table_names
230
+ klass = table_name.to_s.singularize.camelize
231
+ else
232
+ klass = table_name.to_s.camelize
233
+ end
234
+
235
+ klass = klass.constantize rescue nil
236
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
237
+ write_lobs(table_name, klass, fixture, klass.lob_columns)
238
+ end
239
+ end
240
+
241
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
242
+ disable_referential_integrity do
243
+ transaction(requires_new: true) do
244
+ tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
245
+
246
+ fixture_set.each do |table_name, rows|
247
+ rows.each { |row| insert_fixture(row, table_name) }
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ # Oracle Database does not support this feature
254
+ # Refer https://community.oracle.com/ideas/13845 and consider to vote
255
+ # if you need this feature.
256
+ def empty_insert_statement_value
257
+ raise NotImplementedError
258
+ end
259
+
260
+ # Writes LOB values from attributes for specified columns
261
+ def write_lobs(table_name, klass, attributes, columns) # :nodoc:
262
+ id = quote(attributes[klass.primary_key])
263
+ columns.each do |col|
264
+ value = attributes[col.name]
265
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
266
+ next unless value
267
+ value = klass.attribute_types[col.name].serialize(value)
268
+ # value can be nil after serialization because ActiveRecord serializes [] and {} as nil
269
+ next unless value
270
+ uncached do
271
+ unless lob_record = select_one(sql = <<~SQL.squish, "Writable Large Object")
272
+ SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)}
273
+ WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE
274
+ SQL
275
+ raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
276
+ end
277
+ lob = lob_record[col.name]
278
+ _connection.write_lob(lob, value.to_s, col.type == :binary)
279
+ end
280
+ end
281
+ end
282
+
283
+ private
284
+ def with_retry
285
+ _connection.with_retry do
286
+ yield
287
+ rescue
288
+ @statements.clear
289
+ raise
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+ 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