activerecord-oracle_enhanced-adapter 5.2.8 → 7.0.3

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