activerecord-oracle_enhanced-adapter 5.2.8 → 7.0.3

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +390 -21
  3. data/README.md +35 -8
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +1 -1
  6. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +3 -3
  7. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +42 -37
  8. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +59 -60
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +5 -10
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +86 -81
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +9 -10
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +1 -2
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +37 -16
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +1 -1
  15. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +5 -6
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +58 -49
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +1 -1
  18. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +6 -7
  19. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +75 -51
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +13 -14
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +14 -4
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +27 -24
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +156 -155
  24. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +103 -90
  25. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +3 -2
  26. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +261 -161
  27. data/lib/active_record/type/oracle_enhanced/boolean.rb +0 -1
  28. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  29. data/lib/active_record/type/oracle_enhanced/integer.rb +0 -1
  30. data/lib/arel/visitors/oracle.rb +221 -0
  31. data/lib/arel/visitors/oracle12.rb +128 -0
  32. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +0 -2
  33. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +78 -26
  34. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +7 -15
  35. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +5 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +17 -17
  37. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +7 -10
  38. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +0 -15
  39. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +33 -36
  40. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +77 -258
  41. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +38 -39
  42. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +273 -85
  43. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +7 -8
  44. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +2 -4
  45. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +43 -0
  46. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  47. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  48. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +1 -1
  49. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +2 -2
  50. data/spec/active_record/oracle_enhanced/type/json_spec.rb +0 -1
  51. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +6 -5
  52. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +2 -4
  53. data/spec/spec_config.yaml.template +2 -2
  54. data/spec/spec_helper.rb +13 -2
  55. metadata +52 -30
  56. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +0 -28
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  # interface independent methods
6
6
  module OracleEnhanced
7
- class Connection #:nodoc:
7
+ class Connection # :nodoc:
8
8
  def self.create(config)
9
9
  case ORACLE_ENHANCED_CONNECTION
10
10
  when :oci
@@ -19,16 +19,12 @@ module ActiveRecord
19
19
  attr_reader :raw_connection
20
20
 
21
21
  private
22
-
23
22
  # Used always by JDBC connection as well by OCI connection when describing tables over database link
24
23
  def describe(name)
25
24
  name = name.to_s
26
25
  if name.include?("@")
27
- name, db_link = name.split("@")
28
- default_owner = _select_value("SELECT username FROM all_db_links WHERE db_link = '#{db_link.upcase}'")
29
- db_link = "@#{db_link}"
26
+ raise ArgumentError "db link is not supported"
30
27
  else
31
- db_link = nil
32
28
  default_owner = @owner
33
29
  end
34
30
  real_name = OracleEnhanced::Quoting.valid_table_name?(name) ? name.upcase : name
@@ -37,33 +33,33 @@ module ActiveRecord
37
33
  else
38
34
  table_owner, table_name = default_owner, real_name
39
35
  end
40
- sql = <<-SQL.strip.gsub(/\s+/, " ")
41
- SELECT owner, table_name, 'TABLE' name_type
42
- FROM all_tables#{db_link}
43
- WHERE owner = '#{table_owner}'
44
- AND table_name = '#{table_name}'
45
- UNION ALL
46
- SELECT owner, view_name table_name, 'VIEW' name_type
47
- FROM all_views#{db_link}
48
- WHERE owner = '#{table_owner}'
49
- AND view_name = '#{table_name}'
50
- UNION ALL
51
- SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
52
- FROM all_synonyms#{db_link}
53
- WHERE owner = '#{table_owner}'
54
- AND synonym_name = '#{table_name}'
55
- UNION ALL
56
- SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
57
- FROM all_synonyms#{db_link}
58
- WHERE owner = 'PUBLIC'
59
- AND synonym_name = '#{real_name}'
60
- SQL
61
- if result = _select_one(sql)
36
+ sql = <<~SQL.squish
37
+ SELECT owner, table_name, 'TABLE' name_type
38
+ FROM all_tables
39
+ WHERE owner = :table_owner
40
+ AND table_name = :table_name
41
+ UNION ALL
42
+ SELECT owner, view_name table_name, 'VIEW' name_type
43
+ FROM all_views
44
+ WHERE owner = :table_owner
45
+ AND view_name = :table_name
46
+ UNION ALL
47
+ SELECT table_owner, table_name, 'SYNONYM' name_type
48
+ FROM all_synonyms
49
+ WHERE owner = :table_owner
50
+ AND synonym_name = :table_name
51
+ UNION ALL
52
+ SELECT table_owner, table_name, 'SYNONYM' name_type
53
+ FROM all_synonyms
54
+ WHERE owner = 'PUBLIC'
55
+ AND synonym_name = :real_name
56
+ SQL
57
+ if result = _select_one(sql, "CONNECTION", [table_owner, table_name, table_owner, table_name, table_owner, table_name, real_name])
62
58
  case result["name_type"]
63
59
  when "SYNONYM"
64
- describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}#{db_link}")
60
+ describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}")
65
61
  else
66
- db_link ? [result["owner"], result["table_name"], db_link] : [result["owner"], result["table_name"]]
62
+ [result["owner"], result["table_name"]]
67
63
  end
68
64
  else
69
65
  raise OracleEnhanced::ConnectionException, %Q{"DESC #{name}" failed; does it exist?}
@@ -85,7 +81,7 @@ module ActiveRecord
85
81
  # To avoid it is called from anywhere else, added _ at the beginning of the method name.
86
82
  def _oracle_downcase(column_name)
87
83
  return nil if column_name.nil?
88
- column_name =~ /[a-z]/ ? column_name : column_name.downcase
84
+ /[a-z]/.match?(column_name) ? column_name : column_name.downcase
89
85
  end
90
86
 
91
87
  # _select_one and _select_value methods are expected to be called
@@ -96,14 +92,23 @@ module ActiveRecord
96
92
 
97
93
  # Returns a record hash with the column names as keys and column values
98
94
  # as values.
95
+ # binds is a array of native values in contrast to ActiveRecord::Relation::QueryAttribute
99
96
  def _select_one(arel, name = nil, binds = [])
100
- result = select(arel)
101
- result.first if result
97
+ cursor = prepare(arel)
98
+ cursor.bind_params(binds)
99
+ cursor.exec
100
+ columns = cursor.get_col_names.map do |col_name|
101
+ _oracle_downcase(col_name)
102
+ end
103
+ row = cursor.fetch
104
+ columns.each_with_index.map { |x, i| [x, row[i]] }.to_h if row
105
+ ensure
106
+ cursor.close
102
107
  end
103
108
 
104
109
  # Returns a single value from a record
105
110
  def _select_value(arel, name = nil, binds = [])
106
- if result = _select_one(arel)
111
+ if result = _select_one(arel, name, binds)
107
112
  result.values.first
108
113
  end
109
114
  end
@@ -113,14 +118,14 @@ module ActiveRecord
113
118
  def database_version
114
119
  raise NoMethodError, "Not implemented for this raw driver"
115
120
  end
116
- class ConnectionException < StandardError #:nodoc:
121
+ class ConnectionException < StandardError # :nodoc:
117
122
  end
118
123
  end
119
124
  end
120
125
  end
121
126
 
122
- # if MRI or YARV
123
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
127
+ # if MRI or YARV or TruffleRuby
128
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby"
124
129
  ORACLE_ENHANCED_CONNECTION = :oci
125
130
  require "active_record/connection_adapters/oracle_enhanced/oci_connection"
126
131
  # if JRuby
@@ -86,7 +86,7 @@ module ActiveRecord
86
86
  create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on])
87
87
  end
88
88
 
89
- sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}".dup
89
+ sql = +"CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
90
90
  sql << " (#{quoted_column_name})"
91
91
  sql << " INDEXTYPE IS CTXSYS.CONTEXT"
92
92
  parameters = []
@@ -142,63 +142,62 @@ module ActiveRecord
142
142
  end
143
143
 
144
144
  private
145
-
146
145
  def create_datastore_procedure(table_name, procedure_name, column_names, options)
147
146
  quoted_table_name = quote_table_name(table_name)
148
147
  select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i }
149
148
  select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, " ") }
150
149
  keys, selected_columns = parse_select_queries(select_queries)
151
150
  quoted_column_names = (column_names + keys).map { |col| quote_column_name(col) }
152
- execute <<-SQL
153
- CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)}
154
- (p_rowid IN ROWID,
155
- p_clob IN OUT NOCOPY CLOB) IS
156
- -- add_context_index_parameters #{(column_names + select_queries).inspect}#{!options.empty? ? ', '.dup << options.inspect[1..-2] : ''}
151
+ execute <<~SQL
152
+ CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)}
153
+ (p_rowid IN ROWID,
154
+ p_clob IN OUT NOCOPY CLOB) IS
155
+ -- add_context_index_parameters #{(column_names + select_queries).inspect}#{!options.empty? ? +', ' << options.inspect[1..-2] : ''}
157
156
  #{
158
- selected_columns.map do |cols|
159
- cols.map do |col|
160
- raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28
161
- "l_#{col} VARCHAR2(32767);\n"
157
+ selected_columns.map do |cols|
158
+ cols.map do |col|
159
+ raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28
160
+ "l_#{col} VARCHAR2(32767);\n"
161
+ end.join
162
162
  end.join
163
- end.join
164
163
  } BEGIN
165
- FOR r1 IN (
166
- SELECT #{quoted_column_names.join(', ')}
167
- FROM #{quoted_table_name}
168
- WHERE #{quoted_table_name}.ROWID = p_rowid
169
- ) LOOP
170
- #{
171
- (column_names.map do |col|
172
- col = col.to_s
173
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 2}, '<#{col}>');\n".dup <<
174
- "IF LENGTH(r1.#{col}) > 0 THEN\n" <<
175
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" <<
176
- "END IF;\n" <<
177
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 3}, '</#{col}>');\n"
178
- end.join) <<
179
- (selected_columns.zip(select_queries).map do |cols, query|
180
- (cols.map do |col|
181
- "l_#{col} := '';\n"
182
- end.join) <<
183
- "FOR r2 IN (\n" <<
184
- query.gsub(/:(\w+)/, "r1.\\1") << "\n) LOOP\n" <<
185
- (cols.map do |col|
186
- "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n"
187
- end.join) <<
188
- "END LOOP;\n" <<
189
- (cols.map do |col|
164
+ FOR r1 IN (
165
+ SELECT #{quoted_column_names.join(', ')}
166
+ FROM #{quoted_table_name}
167
+ WHERE #{quoted_table_name}.ROWID = p_rowid
168
+ ) LOOP
169
+ #{
170
+ (column_names.map do |col|
190
171
  col = col.to_s
191
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 2}, '<#{col}>');\n".dup <<
192
- "IF LENGTH(l_#{col}) > 0 THEN\n" <<
193
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" <<
172
+ +"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 2}, '<#{col}>');\n" <<
173
+ "IF LENGTH(r1.#{col}) > 0 THEN\n" <<
174
+ "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" <<
194
175
  "END IF;\n" <<
195
176
  "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 3}, '</#{col}>');\n"
177
+ end.join) <<
178
+ (selected_columns.zip(select_queries).map do |cols, query|
179
+ (cols.map do |col|
180
+ "l_#{col} := '';\n"
181
+ end.join) <<
182
+ "FOR r2 IN (\n" <<
183
+ query.gsub(/:(\w+)/, "r1.\\1") << "\n) LOOP\n" <<
184
+ (cols.map do |col|
185
+ "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n"
186
+ end.join) <<
187
+ "END LOOP;\n" <<
188
+ (cols.map do |col|
189
+ col = col.to_s
190
+ +"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 2}, '<#{col}>');\n" <<
191
+ "IF LENGTH(l_#{col}) > 0 THEN\n" <<
192
+ "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" <<
193
+ "END IF;\n" <<
194
+ "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length + 3}, '</#{col}>');\n"
195
+ end.join)
196
196
  end.join)
197
- end.join)
198
197
  }
199
- END LOOP;
200
- END;
201
- SQL
198
+ END LOOP;
199
+ END;
200
+ SQL
202
201
  end
203
202
 
204
203
  def parse_select_queries(select_queries)
@@ -215,17 +214,17 @@ module ActiveRecord
215
214
 
216
215
  def create_datastore_preference(datastore_name, procedure_name)
217
216
  drop_ctx_preference(datastore_name)
218
- execute <<-SQL
219
- BEGIN
220
- CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE');
221
- CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}');
222
- END;
223
- SQL
217
+ execute <<~SQL
218
+ BEGIN
219
+ CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE');
220
+ CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}');
221
+ END;
222
+ SQL
224
223
  end
225
224
 
226
225
  def create_storage_preference(storage_name, tablespace)
227
226
  drop_ctx_preference(storage_name)
228
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n".dup
227
+ sql = +"BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n"
229
228
  ["I_TABLE_CLAUSE", "K_TABLE_CLAUSE", "R_TABLE_CLAUSE",
230
229
  "N_TABLE_CLAUSE", "I_INDEX_CLAUSE", "P_TABLE_CLAUSE"].each do |clause|
231
230
  default_clause = case clause
@@ -241,7 +240,7 @@ module ActiveRecord
241
240
 
242
241
  def create_lexer_preference(lexer_name, lexer_type, options)
243
242
  drop_ctx_preference(lexer_name)
244
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n".dup
243
+ sql = +"BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n"
245
244
  options.each do |key, value|
246
245
  plsql_value = case value
247
246
  when String; "'#{value}'"
@@ -258,7 +257,7 @@ module ActiveRecord
258
257
 
259
258
  def create_wordlist_preference(wordlist_name, wordlist_type, options)
260
259
  drop_ctx_preference(wordlist_name)
261
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n".dup
260
+ sql = +"BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n"
262
261
  options.each do |key, value|
263
262
  plsql_value = case value
264
263
  when String; "'#{value}'"
@@ -281,13 +280,13 @@ module ActiveRecord
281
280
  trigger_name = default_index_column_trigger_name(index_name)
282
281
  columns = Array(index_column_source)
283
282
  quoted_column_names = columns.map { |col| quote_column_name(col) }.join(", ")
284
- execute <<-SQL
285
- CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
286
- BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW
287
- BEGIN
288
- :new.#{quote_column_name(index_column)} := '1';
289
- END;
290
- SQL
283
+ execute <<~SQL
284
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
285
+ BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW
286
+ BEGIN
287
+ :new.#{quote_column_name(index_column)} := '1';
288
+ END;
289
+ SQL
291
290
  end
292
291
 
293
292
  def drop_index_column_trigger(index_name)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/deprecation"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module OracleEnhanced
@@ -7,7 +9,7 @@ module ActiveRecord
7
9
  # maximum length of Oracle identifiers
8
10
  IDENTIFIER_MAX_LENGTH = 30
9
11
 
10
- def table_alias_length #:nodoc:
12
+ def table_alias_length # :nodoc:
11
13
  IDENTIFIER_MAX_LENGTH
12
14
  end
13
15
 
@@ -15,20 +17,13 @@ module ActiveRecord
15
17
  def table_name_length
16
18
  IDENTIFIER_MAX_LENGTH
17
19
  end
20
+ deprecate :table_name_length
18
21
 
19
22
  # the maximum length of a column name
20
23
  def column_name_length
21
24
  IDENTIFIER_MAX_LENGTH
22
25
  end
23
-
24
- # Returns the maximum allowed length for an index name. This
25
- # limit is enforced by rails and Is less than or equal to
26
- # <tt>index_name_length</tt>. The gap between
27
- # <tt>index_name_length</tt> is to allow internal rails
28
- # opreations to use prefixes in temporary opreations.
29
- def allowed_index_name_length
30
- index_name_length
31
- end
26
+ deprecate :column_name_length
32
27
 
33
28
  # the maximum length of an index name
34
29
  # supported by this database
@@ -9,38 +9,39 @@ module ActiveRecord
9
9
  # see: abstract/database_statements.rb
10
10
 
11
11
  # Executes a SQL statement
12
- def execute(sql, name = nil)
13
- log(sql, name) { @connection.exec(sql) }
14
- end
12
+ def execute(sql, name = nil, async: false)
13
+ sql = transform_query(sql)
15
14
 
16
- def clear_cache!
17
- @statements.clear
18
- reload_type_map
15
+ log(sql, name, async: async) { @connection.exec(sql) }
19
16
  end
20
17
 
21
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
18
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
19
+ sql = transform_query(sql)
20
+
22
21
  type_casted_binds = type_casted_binds(binds)
23
22
 
24
- log(sql, name, binds, type_casted_binds) do
23
+ log(sql, name, binds, type_casted_binds, async: async) do
25
24
  cursor = nil
26
25
  cached = false
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
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
33
 
34
- cursor = @statements[sql]
34
+ cursor = @statements[sql]
35
35
 
36
- cursor.bind_params(type_casted_binds)
36
+ cursor.bind_params(type_casted_binds)
37
37
 
38
- cached = true
39
- end
38
+ cached = true
39
+ end
40
40
 
41
- cursor.exec
41
+ cursor.exec
42
+ end
42
43
 
43
- if (name == "EXPLAIN") && sql =~ /^EXPLAIN/
44
+ if (name == "EXPLAIN") && sql.start_with?("EXPLAIN")
44
45
  res = true
45
46
  else
46
47
  columns = cursor.get_col_names.map do |col_name|
@@ -51,7 +52,7 @@ module ActiveRecord
51
52
  while row = cursor.fetch(fetch_options)
52
53
  rows << row
53
54
  end
54
- res = ActiveRecord::Result.new(columns, rows)
55
+ res = build_result(columns: columns, rows: rows)
55
56
  end
56
57
 
57
58
  cursor.close unless cached
@@ -65,7 +66,7 @@ module ActiveRecord
65
66
 
66
67
  def explain(arel, binds = [])
67
68
  sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
68
- return if sql =~ /FROM all_/
69
+ return if /FROM all_/.match?(sql)
69
70
  if ORACLE_ENHANCED_CONNECTION == :jdbc
70
71
  exec_query(sql, "EXPLAIN", binds)
71
72
  else
@@ -76,8 +77,8 @@ module ActiveRecord
76
77
 
77
78
  # New method in ActiveRecord 3.1
78
79
  # Will add RETURNING clause in case of trigger generated primary keys
79
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
80
- unless id_value || pk == false || pk.nil? || pk.is_a?(Array)
80
+ def sql_for_insert(sql, pk, binds)
81
+ unless pk == false || pk.nil? || pk.is_a?(Array) || pk.is_a?(String)
81
82
  sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
82
83
  (binds = binds.dup) << ActiveRecord::Relation::QueryAttribute.new("returning_id", nil, Type::OracleEnhanced::Integer.new)
83
84
  end
@@ -91,41 +92,44 @@ module ActiveRecord
91
92
 
92
93
  # New method in ActiveRecord 3.1
93
94
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
94
- sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds)
95
+ sql, binds = sql_for_insert(sql, pk, binds)
95
96
  type_casted_binds = type_casted_binds(binds)
96
97
 
97
98
  log(sql, name, binds, type_casted_binds) do
98
99
  cached = false
100
+ cursor = nil
99
101
  returning_id_col = returning_id_index = nil
100
- if without_prepared_statement?(binds)
101
- cursor = @connection.prepare(sql)
102
- else
103
- unless @statements.key?(sql)
104
- @statements[sql] = @connection.prepare(sql)
105
- end
102
+ with_retry do
103
+ if without_prepared_statement?(binds)
104
+ cursor = @connection.prepare(sql)
105
+ else
106
+ unless @statements.key?(sql)
107
+ @statements[sql] = @connection.prepare(sql)
108
+ end
109
+
110
+ cursor = @statements[sql]
106
111
 
107
- cursor = @statements[sql]
112
+ cursor.bind_params(type_casted_binds)
108
113
 
109
- cursor.bind_params(type_casted_binds)
114
+ if /:returning_id/.match?(sql)
115
+ # it currently expects that returning_id comes last part of binds
116
+ returning_id_index = binds.size
117
+ cursor.bind_returning_param(returning_id_index, Integer)
118
+ end
110
119
 
111
- if sql =~ /:returning_id/
112
- # it currently expects that returning_id comes last part of binds
113
- returning_id_index = binds.size
114
- cursor.bind_returning_param(returning_id_index, Integer)
120
+ cached = true
115
121
  end
116
122
 
117
- cached = true
123
+ cursor.exec_update
118
124
  end
119
125
 
120
- cursor.exec_update
121
-
122
126
  rows = []
123
127
  if returning_id_index
124
128
  returning_id = cursor.get_returning_param(returning_id_index, Integer).to_i
125
129
  rows << [returning_id]
126
130
  end
127
131
  cursor.close unless cached
128
- ActiveRecord::Result.new(returning_id_col || [], rows)
132
+ build_result(columns: returning_id_col || [], rows: rows)
129
133
  end
130
134
  end
131
135
 
@@ -134,30 +138,32 @@ module ActiveRecord
134
138
  type_casted_binds = type_casted_binds(binds)
135
139
 
136
140
  log(sql, name, binds, type_casted_binds) do
137
- cached = false
138
- if without_prepared_statement?(binds)
139
- cursor = @connection.prepare(sql)
140
- else
141
- if @statements.key?(sql)
142
- cursor = @statements[sql]
141
+ with_retry do
142
+ cached = false
143
+ if without_prepared_statement?(binds)
144
+ cursor = @connection.prepare(sql)
143
145
  else
144
- cursor = @statements[sql] = @connection.prepare(sql)
145
- end
146
+ if @statements.key?(sql)
147
+ cursor = @statements[sql]
148
+ else
149
+ cursor = @statements[sql] = @connection.prepare(sql)
150
+ end
146
151
 
147
- cursor.bind_params(type_casted_binds)
152
+ cursor.bind_params(type_casted_binds)
148
153
 
149
- cached = true
150
- end
154
+ cached = true
155
+ end
151
156
 
152
- res = cursor.exec_update
153
- cursor.close unless cached
154
- res
157
+ res = cursor.exec_update
158
+ cursor.close unless cached
159
+ res
160
+ end
155
161
  end
156
162
  end
157
163
 
158
164
  alias :exec_delete :exec_update
159
165
 
160
- def begin_db_transaction #:nodoc:
166
+ def begin_db_transaction # :nodoc:
161
167
  @connection.autocommit = false
162
168
  end
163
169
 
@@ -176,27 +182,27 @@ module ActiveRecord
176
182
  execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
177
183
  end
178
184
 
179
- def commit_db_transaction #:nodoc:
185
+ def commit_db_transaction # :nodoc:
180
186
  @connection.commit
181
187
  ensure
182
188
  @connection.autocommit = true
183
189
  end
184
190
 
185
- def exec_rollback_db_transaction #:nodoc:
191
+ def exec_rollback_db_transaction # :nodoc:
186
192
  @connection.rollback
187
193
  ensure
188
194
  @connection.autocommit = true
189
195
  end
190
196
 
191
- def create_savepoint(name = current_savepoint_name) #:nodoc:
192
- execute("SAVEPOINT #{name}")
197
+ def create_savepoint(name = current_savepoint_name) # :nodoc:
198
+ execute("SAVEPOINT #{name}", "TRANSACTION")
193
199
  end
194
200
 
195
- def exec_rollback_to_savepoint(name = current_savepoint_name) #:nodoc:
196
- execute("ROLLBACK TO #{name}")
201
+ def exec_rollback_to_savepoint(name = current_savepoint_name) # :nodoc:
202
+ execute("ROLLBACK TO #{name}", "TRANSACTION")
197
203
  end
198
204
 
199
- def release_savepoint(name = current_savepoint_name) #:nodoc:
205
+ def release_savepoint(name = current_savepoint_name) # :nodoc:
200
206
  # there is no RELEASE SAVEPOINT statement in Oracle
201
207
  end
202
208
 
@@ -207,7 +213,7 @@ module ActiveRecord
207
213
  end
208
214
 
209
215
  # Inserts the given fixture into the table. Overridden to properly handle lobs.
210
- def insert_fixture(fixture, table_name) #:nodoc:
216
+ def insert_fixture(fixture, table_name) # :nodoc:
211
217
  super
212
218
 
213
219
  if ActiveRecord::Base.pluralize_table_names
@@ -222,17 +228,6 @@ module ActiveRecord
222
228
  end
223
229
  end
224
230
 
225
- # fallback to non bulk fixture insert
226
- def insert_fixtures(fixtures, table_name)
227
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
228
- `insert_fixtures` is deprecated and will be removed in the next version of Rails.
229
- Consider using `insert_fixtures_set` for performance improvement.
230
- MSG
231
- fixtures.each do |fixture|
232
- insert_fixture(fixture, table_name)
233
- end
234
- end
235
-
236
231
  def insert_fixtures_set(fixture_set, tables_to_delete = [])
237
232
  disable_referential_integrity do
238
233
  transaction(requires_new: true) do
@@ -253,17 +248,17 @@ module ActiveRecord
253
248
  end
254
249
 
255
250
  # Writes LOB values from attributes for specified columns
256
- def write_lobs(table_name, klass, attributes, columns) #:nodoc:
251
+ def write_lobs(table_name, klass, attributes, columns) # :nodoc:
257
252
  id = quote(attributes[klass.primary_key])
258
253
  columns.each do |col|
259
254
  value = attributes[col.name]
260
255
  # changed sequence of next two lines - should check if value is nil before converting to yaml
261
- next if value.blank?
262
- if klass.attribute_types[col.name].is_a? Type::Serialized
263
- value = klass.attribute_types[col.name].serialize(value)
264
- end
256
+ next unless value
257
+ value = klass.attribute_types[col.name].serialize(value)
258
+ # value can be nil after serialization because ActiveRecord serializes [] and {} as nil
259
+ next unless value
265
260
  uncached do
266
- unless lob_record = select_one(sql = <<-SQL.strip.gsub(/\s+/, " "), "Writable Large Object")
261
+ unless lob_record = select_one(sql = <<~SQL.squish, "Writable Large Object")
267
262
  SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)}
268
263
  WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE
269
264
  SQL
@@ -274,6 +269,16 @@ module ActiveRecord
274
269
  end
275
270
  end
276
271
  end
272
+
273
+ private
274
+ def with_retry
275
+ @connection.with_retry do
276
+ yield
277
+ rescue
278
+ @statements.clear
279
+ raise
280
+ end
281
+ end
277
282
  end
278
283
  end
279
284
  end