activerecord-oracle_enhanced-adapter 1.4.3 → 5.2.0

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 (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1162 -2
  3. data/README.md +567 -155
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +3 -1
  6. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +19 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +132 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +345 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +52 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +280 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +64 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +59 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +538 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +46 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +435 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +196 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +164 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +79 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +194 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +709 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +28 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +353 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +33 -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 +385 -1083
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +20 -0
  30. data/lib/active_record/type/oracle_enhanced/integer.rb +15 -0
  31. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  32. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  34. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  35. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  36. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  37. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  38. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  39. data/lib/activerecord-oracle_enhanced-adapter.rb +5 -13
  40. data/spec/active_record/connection_adapters/{oracle_enhanced_emulate_oracle_adapter_spec.rb → emulation/oracle_adapter_spec.rb} +5 -4
  41. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +469 -0
  42. data/spec/active_record/connection_adapters/{oracle_enhanced_context_index_spec.rb → oracle_enhanced/context_index_spec.rb} +140 -128
  43. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +112 -0
  44. data/spec/active_record/connection_adapters/{oracle_enhanced_dbms_output_spec.rb → oracle_enhanced/dbms_output_spec.rb} +13 -13
  45. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +365 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +196 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1433 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +478 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +385 -550
  51. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +92 -1249
  52. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  53. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +208 -0
  54. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +139 -0
  55. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  56. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +91 -0
  57. data/spec/active_record/oracle_enhanced/type/json_spec.rb +57 -0
  58. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  59. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  60. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +122 -0
  61. data/spec/active_record/oracle_enhanced/type/text_spec.rb +229 -0
  62. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +75 -0
  63. data/spec/spec_config.yaml.template +11 -0
  64. data/spec/spec_helper.rb +100 -93
  65. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  66. data/spec/support/alter_system_user_password.sql +2 -0
  67. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  68. metadata +105 -152
  69. data/.rspec +0 -2
  70. data/Gemfile +0 -52
  71. data/RUNNING_TESTS.md +0 -45
  72. data/Rakefile +0 -59
  73. data/activerecord-oracle_enhanced-adapter.gemspec +0 -130
  74. data/lib/active_record/connection_adapters/oracle_enhanced.rake +0 -105
  75. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +0 -41
  76. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +0 -121
  77. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +0 -151
  78. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +0 -119
  79. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -359
  80. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +0 -25
  81. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +0 -21
  82. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -46
  83. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +0 -572
  84. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +0 -497
  85. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +0 -260
  86. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -227
  87. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +0 -260
  88. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -428
  89. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  90. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +0 -294
  91. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +0 -17
  92. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  93. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +0 -334
  94. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +0 -19
  95. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +0 -113
  96. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +0 -141
  97. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +0 -378
  98. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +0 -440
  99. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +0 -1400
  100. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +0 -339
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhanced
6
+ module SchemaStatementsExt
7
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
8
+ # By default trigger name will be "table_name_pkt", you can override the name with
9
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
10
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
11
+ #
12
+ # add_primary_key_trigger :users
13
+ #
14
+ # You can also create primary key trigger using +create_table+ with :primary_key_trigger
15
+ # option:
16
+ #
17
+ # create_table :users, :primary_key_trigger => true do |t|
18
+ # # ...
19
+ # end
20
+ #
21
+ def add_primary_key_trigger(table_name, options = {})
22
+ # call the same private method that is used for create_table :primary_key_trigger => true
23
+ create_primary_key_trigger(table_name, options)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,353 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord #:nodoc:
4
+ module ConnectionAdapters #:nodoc:
5
+ module OracleEnhanced #:nodoc:
6
+ module StructureDump #:nodoc:
7
+ # Statements separator used in structure dump to allow loading of structure dump also with SQL*Plus
8
+ STATEMENT_TOKEN = "\n\n/\n\n"
9
+
10
+ def structure_dump #:nodoc:
11
+ sequences = select(<<-SQL.strip.gsub(/\s+/, " "), "sequences to dump at structure dump")
12
+ SELECT sequence_name, min_value, max_value, increment_by, order_flag, cycle_flag
13
+ FROM all_sequences
14
+ where sequence_owner = SYS_CONTEXT('userenv', 'session_user') ORDER BY 1
15
+ SQL
16
+
17
+ structure = sequences.map do |result|
18
+ "CREATE SEQUENCE #{quote_table_name(result["sequence_name"])} MINVALUE #{result["min_value"]} MAXVALUE #{result["max_value"]} INCREMENT BY #{result["increment_by"]} #{result["order_flag"] == 'Y' ? "ORDER" : "NOORDER"} #{result["cycle_flag"] == 'Y' ? "CYCLE" : "NOCYCLE"}"
19
+ end
20
+ tables = select_values(<<-SQL.strip.gsub(/\s+/, " "), "tables at structure dump")
21
+ SELECT table_name FROM all_tables t
22
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
23
+ AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
24
+ WHERE mv.owner = t.owner AND mv.mview_name = t.table_name)
25
+ AND NOT EXISTS (SELECT mvl.log_table FROM all_mview_logs mvl
26
+ WHERE mvl.log_owner = t.owner AND mvl.log_table = t.table_name)
27
+ ORDER BY 1
28
+ SQL
29
+ tables.each do |table_name|
30
+ virtual_columns = virtual_columns_for(table_name) if supports_virtual_columns?
31
+ ddl = "CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n".dup
32
+ columns = select_all(<<-SQL.strip.gsub(/\s+/, " "), "columns at structure dump")
33
+ SELECT column_name, data_type, data_length, char_used, char_length,
34
+ data_precision, data_scale, data_default, nullable
35
+ FROM all_tab_columns
36
+ WHERE table_name = '#{table_name}'
37
+ AND owner = SYS_CONTEXT('userenv', 'session_user')
38
+ ORDER BY column_id
39
+ SQL
40
+ cols = columns.map do |row|
41
+ if (v = virtual_columns.find { |col| col["column_name"] == row["column_name"] })
42
+ structure_dump_virtual_column(row, v["data_default"])
43
+ else
44
+ structure_dump_column(row)
45
+ end
46
+ end
47
+ ddl << cols.map { |col| " #{col}" }.join(",\n")
48
+ ddl << structure_dump_primary_key(table_name)
49
+ ddl << "\n)"
50
+ structure << ddl
51
+ structure << structure_dump_indexes(table_name)
52
+ structure << structure_dump_unique_keys(table_name)
53
+ structure << structure_dump_table_comments(table_name)
54
+ structure << structure_dump_column_comments(table_name)
55
+ end
56
+
57
+ join_with_statement_token(structure) << structure_dump_fk_constraints
58
+ end
59
+
60
+ def structure_dump_column(column) #:nodoc:
61
+ col = "\"#{column['column_name']}\" #{column['data_type']}".dup
62
+ if (column["data_type"] == "NUMBER") && !column["data_precision"].nil?
63
+ col << "(#{column['data_precision'].to_i}"
64
+ col << ",#{column['data_scale'].to_i}" if !column["data_scale"].nil?
65
+ col << ")"
66
+ elsif column["data_type"].include?("CHAR") || column["data_type"] == "RAW"
67
+ length = column["char_used"] == "C" ? column["char_length"].to_i : column["data_length"].to_i
68
+ col << "(#{length})"
69
+ end
70
+ col << " DEFAULT #{column['data_default']}" if !column["data_default"].nil?
71
+ col << " NOT NULL" if column["nullable"] == "N"
72
+ col
73
+ end
74
+
75
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
76
+ data_default = data_default.gsub(/"/, "")
77
+ col = "\"#{column['column_name']}\" #{column['data_type']}".dup
78
+ if (column["data_type"] == "NUMBER") && !column["data_precision"].nil?
79
+ col << "(#{column['data_precision'].to_i}"
80
+ col << ",#{column['data_scale'].to_i}" if !column["data_scale"].nil?
81
+ col << ")"
82
+ elsif column["data_type"].include?("CHAR") || column["data_type"] == "RAW"
83
+ length = column["char_used"] == "C" ? column["char_length"].to_i : column["data_length"].to_i
84
+ col << "(#{length})"
85
+ end
86
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
87
+ end
88
+
89
+ def structure_dump_primary_key(table) #:nodoc:
90
+ opts = { name: "", cols: [] }
91
+ pks = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Primary Keys")
92
+ SELECT a.constraint_name, a.column_name, a.position
93
+ FROM all_cons_columns a
94
+ JOIN all_constraints c
95
+ ON a.constraint_name = c.constraint_name
96
+ WHERE c.table_name = '#{table.upcase}'
97
+ AND c.constraint_type = 'P'
98
+ AND a.owner = c.owner
99
+ AND c.owner = SYS_CONTEXT('userenv', 'current_schema')
100
+ SQL
101
+ pks.each do |row|
102
+ opts[:name] = row["constraint_name"]
103
+ opts[:cols][row["position"] - 1] = row["column_name"]
104
+ end
105
+ opts[:cols].length > 0 ? ",\n CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : ""
106
+ end
107
+
108
+ def structure_dump_unique_keys(table) #:nodoc:
109
+ keys = {}
110
+ uks = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Primary Keys")
111
+ SELECT a.constraint_name, a.column_name, a.position
112
+ FROM all_cons_columns a
113
+ JOIN all_constraints c
114
+ ON a.constraint_name = c.constraint_name
115
+ WHERE c.table_name = '#{table.upcase}'
116
+ AND c.constraint_type = 'U'
117
+ AND a.owner = c.owner
118
+ AND c.owner = SYS_CONTEXT('userenv', 'current_schema')
119
+ SQL
120
+ uks.each do |uk|
121
+ keys[uk["constraint_name"]] ||= []
122
+ keys[uk["constraint_name"]][uk["position"] - 1] = uk["column_name"]
123
+ end
124
+ keys.map do |k, v|
125
+ "ALTER TABLE #{table.upcase} ADD CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
126
+ end
127
+ end
128
+
129
+ def structure_dump_indexes(table_name) #:nodoc:
130
+ indexes(table_name).map do |options|
131
+ column_names = options.columns
132
+ options = { name: options.name, unique: options.unique }
133
+ index_name = index_name(table_name, column: column_names)
134
+ if Hash === options # legacy support, since this param was a string
135
+ index_type = options[:unique] ? "UNIQUE" : ""
136
+ index_name = options[:name] || index_name
137
+ else
138
+ index_type = options
139
+ end
140
+ quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
141
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
142
+ end
143
+ end
144
+
145
+ def structure_dump_fk_constraints #:nodoc:
146
+ foreign_keys = select_all(<<-SQL.strip.gsub(/\s+/, " "), "foreign keys at structure dump")
147
+ SELECT table_name FROM all_tables
148
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
149
+ SQL
150
+ fks = foreign_keys.map do |table|
151
+ if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
152
+ foreign_keys.map do |fk|
153
+ sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(fk.options[:name])} ".dup
154
+ sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
155
+ end
156
+ end
157
+ end.flatten.compact
158
+ join_with_statement_token(fks)
159
+ end
160
+
161
+ def structure_dump_table_comments(table_name)
162
+ comments = []
163
+ comment = table_comment(table_name)
164
+
165
+ unless comment.nil?
166
+ comments << "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{quote_string(comment)}'"
167
+ end
168
+
169
+ join_with_statement_token(comments)
170
+ end
171
+
172
+ def structure_dump_column_comments(table_name)
173
+ comments = []
174
+ columns = select_values(<<-SQL.strip.gsub(/\s+/, " "), "column comments at structure dump")
175
+ SELECT column_name FROM user_tab_columns
176
+ WHERE table_name = '#{table_name}' ORDER BY column_id
177
+ SQL
178
+
179
+ columns.each do |column|
180
+ comment = column_comment(table_name, column)
181
+ unless comment.nil?
182
+ comments << "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column)} IS '#{quote_string(comment)}'"
183
+ end
184
+ end
185
+
186
+ join_with_statement_token(comments)
187
+ end
188
+
189
+ def foreign_key_definition(to_table, options = {}) #:nodoc:
190
+ column_sql = quote_column_name(options[:column] || "#{to_table.to_s.singularize}_id")
191
+ references = options[:references] ? options[:references].first : nil
192
+ references_sql = quote_column_name(options[:primary_key] || references || "id")
193
+
194
+ sql = "FOREIGN KEY (#{column_sql}) REFERENCES #{quote_table_name(to_table)}(#{references_sql})"
195
+
196
+ case options[:dependent]
197
+ when :nullify
198
+ sql << " ON DELETE SET NULL"
199
+ when :delete
200
+ sql << " ON DELETE CASCADE"
201
+ end
202
+ sql
203
+ end
204
+
205
+ # Extract all stored procedures, packages, synonyms and views.
206
+ def structure_dump_db_stored_code #:nodoc:
207
+ structure = []
208
+ all_source = select_all(<<-SQL.strip.gsub(/\s+/, " "), "stored program at structure dump")
209
+ SELECT DISTINCT name, type
210
+ FROM all_source
211
+ WHERE type IN ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
212
+ AND name NOT LIKE 'BIN$%'
213
+ AND owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY type
214
+ SQL
215
+ all_source.each do |source|
216
+ ddl = "CREATE OR REPLACE \n".dup
217
+ texts = select_all(<<-SQL.strip.gsub(/\s+/, " "), "all source at structure dump")
218
+ SELECT text
219
+ FROM all_source
220
+ WHERE name = '#{source['name']}'
221
+ AND type = '#{source['type']}'
222
+ AND owner = SYS_CONTEXT('userenv', 'current_schema')
223
+ ORDER BY line
224
+ SQL
225
+ texts.each do |row|
226
+ ddl << row["text"]
227
+ end
228
+ ddl << ";" unless ddl.strip[-1, 1] == ";"
229
+ structure << ddl
230
+ end
231
+
232
+ # export views
233
+ views = select_all(<<-SQL.strip.gsub(/\s+/, " "), "views at structure dump")
234
+ SELECT view_name, text FROM all_views
235
+ WHERE owner = SYS_CONTEXT('userenv', 'session_user') ORDER BY view_name ASC
236
+ SQL
237
+ views.each do |view|
238
+ structure << "CREATE OR REPLACE FORCE VIEW #{view['view_name']} AS\n #{view['text']}"
239
+ end
240
+
241
+ # export synonyms
242
+ synonyms = select_all(<<-SQL.strip.gsub(/\s+/, " "), "synonyms at structure dump")
243
+ SELECT owner, synonym_name, table_name, table_owner
244
+ FROM all_synonyms
245
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
246
+ SQL
247
+ synonyms.each do |synonym|
248
+ structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}
249
+ FOR #{synonym['table_owner']}.#{synonym['table_name']}"
250
+ end
251
+
252
+ join_with_statement_token(structure)
253
+ end
254
+
255
+ def structure_drop #:nodoc:
256
+ sequences = select_values(<<-SQL.strip.gsub(/\s+/, " "), "sequences to drop at structure dump")
257
+ SELECT sequence_name FROM all_sequences where sequence_owner = SYS_CONTEXT('userenv', 'session_user') ORDER BY 1
258
+ SQL
259
+ statements = sequences.map do |seq|
260
+ "DROP SEQUENCE \"#{seq}\""
261
+ end
262
+ tables = select_values(<<-SQL.strip.gsub(/\s+/, " "), "tables to drop at structure dump")
263
+ SELECT table_name from all_tables t
264
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
265
+ AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
266
+ WHERE mv.owner = t.owner AND mv.mview_name = t.table_name)
267
+ AND NOT EXISTS (SELECT mvl.log_table FROM all_mview_logs mvl
268
+ WHERE mvl.log_owner = t.owner AND mvl.log_table = t.table_name)
269
+ ORDER BY 1
270
+ SQL
271
+ tables.each do |table|
272
+ statements << "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
273
+ end
274
+ join_with_statement_token(statements)
275
+ end
276
+
277
+ def temp_table_drop #:nodoc:
278
+ temporary_tables = select_values(<<-SQL.strip.gsub(/\s+/, " "), "temporary tables to drop at structure dump")
279
+ SELECT table_name FROM all_tables
280
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
281
+ AND secondary = 'N' AND temporary = 'Y' ORDER BY 1
282
+ SQL
283
+ statements = temporary_tables.map do |table|
284
+ "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
285
+ end
286
+ join_with_statement_token(statements)
287
+ end
288
+
289
+ def full_drop(preserve_tables = false) #:nodoc:
290
+ s = preserve_tables ? [] : [structure_drop]
291
+ s << temp_table_drop if preserve_tables
292
+ s << drop_sql_for_feature("view")
293
+ s << drop_sql_for_feature("materialized view")
294
+ s << drop_sql_for_feature("synonym")
295
+ s << drop_sql_for_feature("type")
296
+ s << drop_sql_for_object("package")
297
+ s << drop_sql_for_object("function")
298
+ s << drop_sql_for_object("procedure")
299
+ s.join
300
+ end
301
+
302
+ def execute_structure_dump(string)
303
+ string.split(STATEMENT_TOKEN).each do |ddl|
304
+ execute(ddl) unless ddl.blank?
305
+ end
306
+ end
307
+
308
+ private
309
+
310
+ # Called only if `supports_virtual_columns?` returns true
311
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
312
+ def virtual_columns_for(table)
313
+ select_all(<<-SQL.strip.gsub(/\s+/, " "), "virtual columns for")
314
+ SELECT column_name, data_default
315
+ FROM all_tab_cols
316
+ WHERE virtual_column = 'YES'
317
+ AND owner = SYS_CONTEXT('userenv', 'session_user')
318
+ AND table_name = '#{table.upcase}'
319
+ SQL
320
+ end
321
+
322
+ def drop_sql_for_feature(type)
323
+ short_type = type == "materialized view" ? "mview" : type
324
+ features = select_values(<<-SQL.strip.gsub(/\s+/, " "), "features to drop")
325
+ SELECT #{short_type}_name FROM all_#{short_type.tableize}
326
+ where owner = SYS_CONTEXT('userenv', 'session_user')
327
+ SQL
328
+ statements = features.map do |name|
329
+ "DROP #{type.upcase} \"#{name}\""
330
+ end
331
+ join_with_statement_token(statements)
332
+ end
333
+
334
+ def drop_sql_for_object(type)
335
+ objects = select_values(<<-SQL.strip.gsub(/\s+/, " "), "objects to drop")
336
+ SELECT object_name FROM all_objects
337
+ WHERE object_type = '#{type.upcase}' and owner = SYS_CONTEXT('userenv', 'session_user')
338
+ SQL
339
+ statements = objects.map do |name|
340
+ "DROP #{type.upcase} \"#{name}\""
341
+ end
342
+ join_with_statement_token(statements)
343
+ end
344
+
345
+ def join_with_statement_token(array)
346
+ string = array.join(STATEMENT_TOKEN)
347
+ string << STATEMENT_TOKEN unless string.blank?
348
+ string
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters #:nodoc:
5
+ module OracleEnhanced
6
+ class TypeMetadata < DelegateClass(ActiveRecord::ConnectionAdapters::SqlTypeMetadata) # :nodoc:
7
+ attr_reader :virtual
8
+
9
+ def initialize(type_metadata, virtual: nil)
10
+ super(type_metadata)
11
+ @type_metadata = type_metadata
12
+ @virtual = virtual
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(OracleEnhanced::TypeMetadata) &&
17
+ attributes_for_hash == other.attributes_for_hash
18
+ end
19
+ alias eql? ==
20
+
21
+ def hash
22
+ attributes_for_hash.hash
23
+ end
24
+
25
+ protected
26
+
27
+ def attributes_for_hash
28
+ [self.class, @type_metadata, virtual]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = File.read(File.expand_path("../../../../../VERSION", __FILE__)).chomp
@@ -1,5 +1,6 @@
1
- # -*- coding: utf-8 -*-
2
- # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
1
+ # frozen_string_literal: true
2
+
3
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 10g, 11g and 12c
3
4
  #
4
5
  # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
5
6
  #
@@ -29,27 +30,58 @@
29
30
  # contribution.
30
31
  # portions Copyright 2005 Graham Jenkins
31
32
 
32
- # ActiveRecord 2.2 does not load version file automatically
33
- require 'active_record/version' unless defined?(ActiveRecord::VERSION)
34
-
35
- require 'active_record/connection_adapters/abstract_adapter'
36
- require 'active_record/connection_adapters/oracle_enhanced_connection'
37
-
38
- require 'active_record/connection_adapters/oracle_enhanced_base_ext'
39
- require 'active_record/connection_adapters/oracle_enhanced_column'
40
-
41
- require 'digest/sha1'
33
+ require "active_record/connection_adapters/abstract_adapter"
34
+ require "active_record/connection_adapters/statement_pool"
35
+ require "active_record/connection_adapters/oracle_enhanced/connection"
36
+ require "active_record/connection_adapters/oracle_enhanced/database_statements"
37
+ require "active_record/connection_adapters/oracle_enhanced/schema_creation"
38
+ require "active_record/connection_adapters/oracle_enhanced/schema_definitions"
39
+ require "active_record/connection_adapters/oracle_enhanced/schema_dumper"
40
+ require "active_record/connection_adapters/oracle_enhanced/schema_statements"
41
+ require "active_record/connection_adapters/oracle_enhanced/schema_statements_ext"
42
+ require "active_record/connection_adapters/oracle_enhanced/context_index"
43
+ require "active_record/connection_adapters/oracle_enhanced/column"
44
+ require "active_record/connection_adapters/oracle_enhanced/quoting"
45
+ require "active_record/connection_adapters/oracle_enhanced/database_limits"
46
+ require "active_record/connection_adapters/oracle_enhanced/dbms_output"
47
+ require "active_record/connection_adapters/oracle_enhanced/type_metadata"
48
+ require "active_record/connection_adapters/oracle_enhanced/structure_dump"
49
+ require "active_record/connection_adapters/oracle_enhanced/lob"
50
+
51
+ require "active_record/type/oracle_enhanced/raw"
52
+ require "active_record/type/oracle_enhanced/integer"
53
+ require "active_record/type/oracle_enhanced/string"
54
+ require "active_record/type/oracle_enhanced/national_character_string"
55
+ require "active_record/type/oracle_enhanced/text"
56
+ require "active_record/type/oracle_enhanced/national_character_text"
57
+ require "active_record/type/oracle_enhanced/boolean"
58
+ require "active_record/type/oracle_enhanced/json"
59
+ require "active_record/type/oracle_enhanced/timestamptz"
60
+ require "active_record/type/oracle_enhanced/timestampltz"
42
61
 
43
62
  module ActiveRecord
44
- module ConnectionAdapters #:nodoc:
63
+ module ConnectionHandling #:nodoc:
64
+ # Establishes a connection to the database that's used by all Active Record objects.
65
+ def oracle_enhanced_connection(config) #:nodoc:
66
+ if config[:emulate_oracle_adapter] == true
67
+ # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
68
+ # conditionals in the rails activerecord test suite
69
+ require "active_record/connection_adapters/emulation/oracle_adapter"
70
+ ConnectionAdapters::OracleAdapter.new(
71
+ ConnectionAdapters::OracleEnhanced::Connection.create(config), logger, config)
72
+ else
73
+ ConnectionAdapters::OracleEnhancedAdapter.new(
74
+ ConnectionAdapters::OracleEnhanced::Connection.create(config), logger, config)
75
+ end
76
+ end
77
+ end
45
78
 
79
+ module ConnectionAdapters #:nodoc:
46
80
  # Oracle enhanced adapter will work with both
47
- # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
81
+ # CRuby ruby-oci8 gem (which provides interface to Oracle OCI client)
48
82
  # or with JRuby and Oracle JDBC driver.
49
83
  #
50
- # It should work with Oracle 9i, 10g and 11g databases.
51
- # Limited set of functionality should work on Oracle 8i as well but several features
52
- # rely on newer functionality in Oracle database.
84
+ # It should work with Oracle 10g, 11g and 12c databases.
53
85
  #
54
86
  # Usage notes:
55
87
  # * Key generation assumes a "${table_name}_seq" sequence is available
@@ -79,9 +111,10 @@ module ActiveRecord
79
111
  # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
80
112
  # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
81
113
  # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
82
- # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
114
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, no default value
83
115
  # * <tt>:time_zone</tt> - database session time zone
84
116
  # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
117
+ # * <tt>:schema</tt> - database schema which holds schema objects.
85
118
  #
86
119
  # Optionals NLS parameters:
87
120
  #
@@ -89,6 +122,7 @@ module ActiveRecord
89
122
  # * <tt>:nls_comp</tt>
90
123
  # * <tt>:nls_currency</tt>
91
124
  # * <tt>:nls_date_format</tt> - format for :date columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS</tt>
125
+ # (changing `nls_date_format` parameter value is not supported. Use the default value.)
92
126
  # * <tt>:nls_date_language</tt>
93
127
  # * <tt>:nls_dual_currency</tt>
94
128
  # * <tt>:nls_iso_currency</tt>
@@ -100,11 +134,20 @@ module ActiveRecord
100
134
  # * <tt>:nls_sort</tt>
101
135
  # * <tt>:nls_territory</tt>
102
136
  # * <tt>:nls_timestamp_format</tt> - format for :timestamp columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS:FF6</tt>
137
+ # (changing `nls_timestamp_format` parameter value is not supported. Use the default value.)
103
138
  # * <tt>:nls_timestamp_tz_format</tt>
104
139
  # * <tt>:nls_time_format</tt>
105
140
  # * <tt>:nls_time_tz_format</tt>
106
141
  #
107
142
  class OracleEnhancedAdapter < AbstractAdapter
143
+ include OracleEnhanced::DatabaseStatements
144
+ include OracleEnhanced::SchemaStatements
145
+ include OracleEnhanced::SchemaStatementsExt
146
+ include OracleEnhanced::ContextIndex
147
+ include OracleEnhanced::Quoting
148
+ include OracleEnhanced::DatabaseLimits
149
+ include OracleEnhanced::DbmsOutput
150
+ include OracleEnhanced::StructureDump
108
151
 
109
152
  ##
110
153
  # :singleton-method:
@@ -118,419 +161,197 @@ module ActiveRecord
118
161
 
119
162
  ##
120
163
  # :singleton-method:
121
- # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
122
- # to Time or DateTime (if value is out of Time value range) value.
123
- # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
124
- # to Date then you can add the following line to your initializer file:
164
+ # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of
165
+ # objects to go into specific tablespaces, specify them like this in an initializer:
125
166
  #
126
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
167
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces =
168
+ # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'}
127
169
  #
128
- # As this option can have side effects when unnecessary typecasting is done it is recommended
129
- # that Date columns are explicily defined with +set_date_columns+ method.
130
- cattr_accessor :emulate_dates
131
- self.emulate_dates = false
132
-
133
- ##
134
- # :singleton-method:
135
- # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of
136
- # objects to go into specific tablespaces, specify them like this in an initializer:
137
- #
138
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces =
139
- # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'}
140
- #
141
- # Using the :tablespace option where available (e.g create_table) will take precedence
142
- # over these settings.
170
+ # Using the :tablespace option where available (e.g create_table) will take precedence
171
+ # over these settings.
143
172
  cattr_accessor :default_tablespaces
144
- self.default_tablespaces={}
173
+ self.default_tablespaces = {}
145
174
 
146
175
  ##
147
176
  # :singleton-method:
148
- # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
149
- # to Time or DateTime (if value is out of Time value range) value.
150
- # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
151
- # to Date then you can add the following line to your initializer file:
152
- #
153
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
154
- #
155
- # As this option can have side effects when unnecessary typecasting is done it is recommended
156
- # that Date columns are explicily defined with +set_date_columns+ method.
157
- cattr_accessor :emulate_dates_by_column_name
158
- self.emulate_dates_by_column_name = false
159
-
160
- # Check column name to identify if it is Date (and not Time) column.
161
- # Is used if +emulate_dates_by_column_name+ option is set to +true+.
162
- # Override this method definition in initializer file if different Date column recognition is needed.
163
- def self.is_date_column?(name, table_name = nil)
164
- name =~ /(^|_)date(_|$)/i
165
- end
166
-
167
- # instance method uses at first check if column type defined at class level
168
- def is_date_column?(name, table_name = nil) #:nodoc:
169
- case get_type_for_column(table_name, name)
170
- when nil
171
- self.class.is_date_column?(name, table_name)
172
- when :date
173
- true
174
- else
175
- false
176
- end
177
- end
178
-
179
- ##
180
- # :singleton-method:
181
- # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
182
- # (without precision or scale) to Float or BigDecimal value.
183
- # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
184
- # to Integer then you can add the following line to your initializer file:
185
- #
186
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
187
- cattr_accessor :emulate_integers_by_column_name
188
- self.emulate_integers_by_column_name = false
189
-
190
- # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
191
- # Is used if +emulate_integers_by_column_name+ option is set to +true+.
192
- # Override this method definition in initializer file if different Integer column recognition is needed.
193
- def self.is_integer_column?(name, table_name = nil)
194
- name =~ /(^|_)id$/i
195
- end
196
-
197
- ##
198
- # :singleton-method:
199
- # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
200
- # are typecasted to booleans then you can add the following line to your initializer file:
177
+ # If you wish that CHAR(1), VARCHAR2(1) columns are typecasted to booleans
178
+ # then you can add the following line to your initializer file:
201
179
  #
202
180
  # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
203
181
  cattr_accessor :emulate_booleans_from_strings
204
182
  self.emulate_booleans_from_strings = false
205
183
 
206
- # Check column name to identify if it is boolean (and not String) column.
207
- # Is used if +emulate_booleans_from_strings+ option is set to +true+.
208
- # Override this method definition in initializer file if different boolean column recognition is needed.
209
- def self.is_boolean_column?(name, field_type, table_name = nil)
210
- return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
211
- field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
212
- end
213
-
214
- # How boolean value should be quoted to String.
215
- # Used if +emulate_booleans_from_strings+ option is set to +true+.
216
- def self.boolean_to_string(bool)
217
- bool ? "Y" : "N"
218
- end
219
-
220
184
  ##
221
185
  # :singleton-method:
222
- # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
186
+ # By default, OracleEnhanced adapter will use Oracle12 visitor
187
+ # if database version is Oracle 12.1.
188
+ # If you wish to use Oracle visitor which is intended to work with Oracle 11.2 or lower
189
+ # for Oracle 12.1 database you can add the following line to your initializer file:
223
190
  #
224
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
225
- cattr_accessor :string_to_date_format
226
- self.string_to_date_format = nil
191
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_old_oracle_visitor = true
192
+ cattr_accessor :use_old_oracle_visitor
193
+ self.use_old_oracle_visitor = false
227
194
 
228
195
  ##
229
196
  # :singleton-method:
230
- # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
197
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
231
198
  #
232
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
233
- cattr_accessor :string_to_time_format
234
- self.string_to_time_format = nil
235
-
236
- class StatementPool
237
- include Enumerable
238
-
239
- def initialize(connection, max = 300)
240
- @connection = connection
241
- @max = max
242
- @cache = {}
243
- end
244
-
245
- def each(&block); @cache.each(&block); end
246
- def key?(key); @cache.key?(key); end
247
- def [](key); @cache[key]; end
248
- def length; @cache.length; end
249
- def delete(key); @cache.delete(key); end
199
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
200
+ cattr_accessor :default_sequence_start_value
201
+ self.default_sequence_start_value = 10000
250
202
 
251
- def []=(sql, key)
252
- while @max <= @cache.size
253
- @cache.shift.last.close
254
- end
255
- @cache[sql] = key
256
- end
203
+ class StatementPool < ConnectionAdapters::StatementPool
204
+ private
257
205
 
258
- def clear
259
- @cache.values.each do |cursor|
260
- cursor.close
206
+ def dealloc(stmt)
207
+ stmt.close
261
208
  end
262
- @cache.clear
263
- end
264
209
  end
265
210
 
266
- def initialize(connection, logger, config) #:nodoc:
267
- super(connection, logger)
268
- @quoted_column_names, @quoted_table_names = {}, {}
269
- @config = config
270
- @statements = StatementPool.new(connection, config.fetch(:statement_limit) { 250 })
211
+ def initialize(connection, logger = nil, config = {}) # :nodoc:
212
+ super(connection, logger, config)
213
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
271
214
  @enable_dbms_output = false
272
- @visitor = Arel::Visitors::Oracle.new self if defined?(Arel::Visitors::Oracle)
273
215
  end
274
216
 
275
- def self.visitor_for(pool) # :nodoc:
276
- Arel::Visitors::Oracle.new(pool)
277
- end
278
-
279
- ADAPTER_NAME = 'OracleEnhanced'.freeze
217
+ ADAPTER_NAME = "OracleEnhanced".freeze
280
218
 
281
219
  def adapter_name #:nodoc:
282
220
  ADAPTER_NAME
283
221
  end
284
222
 
285
- def supports_migrations? #:nodoc:
286
- true
287
- end
288
-
289
- def supports_primary_key? #:nodoc:
290
- true
223
+ def arel_visitor # :nodoc:
224
+ if supports_fetch_first_n_rows_and_offset?
225
+ Arel::Visitors::Oracle12.new(self)
226
+ else
227
+ Arel::Visitors::Oracle.new(self)
228
+ end
291
229
  end
292
230
 
293
231
  def supports_savepoints? #:nodoc:
294
232
  true
295
233
  end
296
234
 
297
- #:stopdoc:
298
- DEFAULT_NLS_PARAMETERS = {
299
- :nls_calendar => nil,
300
- :nls_comp => nil,
301
- :nls_currency => nil,
302
- :nls_date_format => 'YYYY-MM-DD HH24:MI:SS',
303
- :nls_date_language => nil,
304
- :nls_dual_currency => nil,
305
- :nls_iso_currency => nil,
306
- :nls_language => nil,
307
- :nls_length_semantics => 'CHAR',
308
- :nls_nchar_conv_excp => nil,
309
- :nls_numeric_characters => nil,
310
- :nls_sort => nil,
311
- :nls_territory => nil,
312
- :nls_timestamp_format => 'YYYY-MM-DD HH24:MI:SS:FF6',
313
- :nls_timestamp_tz_format => nil,
314
- :nls_time_format => nil,
315
- :nls_time_tz_format => nil
316
- }
317
-
318
- #:stopdoc:
319
- NATIVE_DATABASE_TYPES = {
320
- :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
321
- :string => { :name => "VARCHAR2", :limit => 255 },
322
- :text => { :name => "CLOB" },
323
- :integer => { :name => "NUMBER", :limit => 38 },
324
- :float => { :name => "NUMBER" },
325
- :decimal => { :name => "DECIMAL" },
326
- :datetime => { :name => "DATE" },
327
- # changed to native TIMESTAMP type
328
- # :timestamp => { :name => "DATE" },
329
- :timestamp => { :name => "TIMESTAMP" },
330
- :time => { :name => "DATE" },
331
- :date => { :name => "DATE" },
332
- :binary => { :name => "BLOB" },
333
- :boolean => { :name => "NUMBER", :limit => 1 },
334
- :raw => { :name => "RAW", :limit => 2000 }
335
- }
336
- # if emulate_booleans_from_strings then store booleans in VARCHAR2
337
- NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
338
- :boolean => { :name => "VARCHAR2", :limit => 1 }
339
- )
340
- #:startdoc:
341
-
342
- def native_database_types #:nodoc:
343
- emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
344
- end
345
-
346
- # maximum length of Oracle identifiers
347
- IDENTIFIER_MAX_LENGTH = 30
348
-
349
- def table_alias_length #:nodoc:
350
- IDENTIFIER_MAX_LENGTH
351
- end
352
-
353
- # the maximum length of a table name
354
- def table_name_length
355
- IDENTIFIER_MAX_LENGTH
356
- end
357
-
358
- # the maximum length of a column name
359
- def column_name_length
360
- IDENTIFIER_MAX_LENGTH
361
- end
362
-
363
- # the maximum length of an index name
364
- def index_name_length
365
- IDENTIFIER_MAX_LENGTH
235
+ def supports_transaction_isolation? #:nodoc:
236
+ true
366
237
  end
367
238
 
368
- # the maximum length of a sequence name
369
- def sequence_name_length
370
- IDENTIFIER_MAX_LENGTH
239
+ def supports_foreign_keys?
240
+ true
371
241
  end
372
242
 
373
- # To avoid ORA-01795: maximum number of expressions in a list is 1000
374
- # tell ActiveRecord to limit us to 1000 ids at a time
375
- def in_clause_length
376
- 1000
243
+ def supports_foreign_keys_in_create?
244
+ supports_foreign_keys?
377
245
  end
378
- alias ids_in_list_limit in_clause_length
379
246
 
380
- # QUOTING ==================================================
381
- #
382
- # see: abstract/quoting.rb
383
-
384
- def quote_column_name(name) #:nodoc:
385
- name = name.to_s
386
- @quoted_column_names[name] ||= begin
387
- # if only valid lowercase column characters in name
388
- if name =~ /\A[a-z][a-z_0-9\$#]*\Z/
389
- "\"#{name.upcase}\""
390
- else
391
- # remove double quotes which cannot be used inside quoted identifier
392
- "\"#{name.gsub('"', '')}\""
393
- end
394
- end
247
+ def supports_views?
248
+ true
395
249
  end
396
250
 
397
- # This method is used in add_index to identify either column name (which is quoted)
398
- # or function based index (in which case function expression is not quoted)
399
- def quote_column_name_or_expression(name) #:nodoc:
400
- name = name.to_s
401
- case name
402
- # if only valid lowercase column characters in name
403
- when /^[a-z][a-z_0-9\$#]*$/
404
- "\"#{name.upcase}\""
405
- when /^[a-z][a-z_0-9\$#\-]*$/i
406
- "\"#{name}\""
407
- # if other characters present then assume that it is expression
408
- # which should not be quoted
251
+ def supports_fetch_first_n_rows_and_offset?
252
+ if !use_old_oracle_visitor && @connection.database_version.first >= 12
253
+ true
409
254
  else
410
- name
255
+ false
411
256
  end
412
257
  end
413
258
 
414
- # Names must be from 1 to 30 bytes long with these exceptions:
415
- # * Names of databases are limited to 8 bytes.
416
- # * Names of database links can be as long as 128 bytes.
417
- #
418
- # Nonquoted identifiers cannot be Oracle Database reserved words
419
- #
420
- # Nonquoted identifiers must begin with an alphabetic character from
421
- # your database character set
422
- #
423
- # Nonquoted identifiers can contain only alphanumeric characters from
424
- # your database character set and the underscore (_), dollar sign ($),
425
- # and pound sign (#). Database links can also contain periods (.) and
426
- # "at" signs (@). Oracle strongly discourages you from using $ and # in
427
- # nonquoted identifiers.
428
- NONQUOTED_OBJECT_NAME = /[A-Za-z][A-z0-9$#]{0,29}/
429
- NONQUOTED_DATABASE_LINK = /[A-Za-z][A-z0-9$#\.@]{0,127}/
430
- VALID_TABLE_NAME = /\A(?:#{NONQUOTED_OBJECT_NAME}\.)?#{NONQUOTED_OBJECT_NAME}(?:@#{NONQUOTED_DATABASE_LINK})?\Z/
431
-
432
- # unescaped table name should start with letter and
433
- # contain letters, digits, _, $ or #
434
- # can be prefixed with schema name
435
- # CamelCase table names should be quoted
436
- def self.valid_table_name?(name) #:nodoc:
437
- name = name.to_s
438
- name =~ VALID_TABLE_NAME && !(name =~ /[A-Z]/ && name =~ /[a-z]/) ? true : false
439
- end
440
-
441
- def quote_table_name(name) #:nodoc:
442
- name = name.to_s
443
- @quoted_table_names[name] ||= name.split('.').map{|n| n.split('@').map{|m| quote_column_name(m)}.join('@')}.join('.')
444
- end
445
-
446
- def quote_string(s) #:nodoc:
447
- s.gsub(/'/, "''")
448
- end
449
-
450
- def quote(value, column = nil) #:nodoc:
451
- if value && column
452
- case column.type
453
- when :text, :binary
454
- %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
455
- # NLS_DATE_FORMAT independent TIMESTAMP support
456
- when :timestamp
457
- quote_timestamp_with_to_timestamp(value)
458
- # NLS_DATE_FORMAT independent DATE support
459
- when :date, :time, :datetime
460
- quote_date_with_to_date(value)
461
- when :raw
462
- quote_raw(value)
463
- when :string
464
- # NCHAR and NVARCHAR2 literals should be quoted with N'...'.
465
- # Read directly instance variable as otherwise migrations with table column default values are failing
466
- # as migrations pass ColumnDefinition object to this method.
467
- # Check if instance variable is defined to avoid warnings about accessing undefined instance variable.
468
- column.instance_variable_defined?('@nchar') && column.instance_variable_get('@nchar') ? 'N' << super : super
469
- else
470
- super
471
- end
472
- elsif value.acts_like?(:date)
473
- quote_date_with_to_date(value)
474
- elsif value.acts_like?(:time)
475
- value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
476
- else
477
- super
478
- end
259
+ def supports_datetime_with_precision?
260
+ true
479
261
  end
480
262
 
481
- def quoted_true #:nodoc:
482
- return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
483
- "1"
263
+ def supports_comments?
264
+ true
484
265
  end
485
266
 
486
- def quoted_false #:nodoc:
487
- return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
488
- "0"
267
+ def supports_multi_insert?
268
+ @connection.database_version.to_s >= [11, 2].to_s
489
269
  end
490
270
 
491
- def quote_date_with_to_date(value) #:nodoc:
492
- # should support that composite_primary_keys gem will pass date as string
493
- value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
494
- "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
271
+ def supports_virtual_columns?
272
+ @connection.database_version.first >= 11
495
273
  end
496
274
 
497
- # Encode a string or byte array as string of hex codes
498
- def self.encode_raw(value)
499
- # When given a string, convert to a byte array.
500
- value = value.unpack('C*') if value.is_a?(String)
501
- value.map { |x| "%02X" % x }.join
275
+ def supports_json?
276
+ # Oracle Database 12.1 or higher version supports JSON.
277
+ # However, Oracle enhanced adapter has limited support for JSON data type.
278
+ # which does not pass many of ActiveRecord JSON tests.
279
+ #
280
+ # No migration supported for :json type due to there is no `JSON` data type
281
+ # in Oracle Database itself.
282
+ #
283
+ # If you want to use JSON data type, here are steps
284
+ # 1.Define :string or :text in migration
285
+ #
286
+ # create_table :test_posts, force: true do |t|
287
+ # t.string :title
288
+ # t.text :article
289
+ # end
290
+ #
291
+ # 2. Set :json attributes
292
+ #
293
+ # class TestPost < ActiveRecord::Base
294
+ # attribute :title, :json
295
+ # attribute :article, :json
296
+ # end
297
+ #
298
+ # 3. Add `is json` database constraints by running sql statements
299
+ #
300
+ # alter table test_posts add constraint test_posts_title_is_json check (title is json)
301
+ # alter table test_posts add constraint test_posts_article_is_json check (article is json)
302
+ #
303
+ false
502
304
  end
503
305
 
504
- # quote encoded raw value
505
- def quote_raw(value) #:nodoc:
506
- "'#{self.class.encode_raw(value)}'"
507
- end
306
+ #:stopdoc:
307
+ DEFAULT_NLS_PARAMETERS = {
308
+ nls_calendar: nil,
309
+ nls_comp: nil,
310
+ nls_currency: nil,
311
+ nls_date_format: "YYYY-MM-DD HH24:MI:SS",
312
+ nls_date_language: nil,
313
+ nls_dual_currency: nil,
314
+ nls_iso_currency: nil,
315
+ nls_language: nil,
316
+ nls_length_semantics: "CHAR",
317
+ nls_nchar_conv_excp: nil,
318
+ nls_numeric_characters: nil,
319
+ nls_sort: nil,
320
+ nls_territory: nil,
321
+ nls_timestamp_format: "YYYY-MM-DD HH24:MI:SS:FF6",
322
+ nls_timestamp_tz_format: nil,
323
+ nls_time_format: nil,
324
+ nls_time_tz_format: nil
325
+ }
508
326
 
509
- def quote_timestamp_with_to_timestamp(value) #:nodoc:
510
- # add up to 9 digits of fractional seconds to inserted time
511
- value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
512
- "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
513
- end
327
+ #:stopdoc:
328
+ NATIVE_DATABASE_TYPES = {
329
+ primary_key: "NUMBER(38) NOT NULL PRIMARY KEY",
330
+ string: { name: "VARCHAR2", limit: 255 },
331
+ text: { name: "CLOB" },
332
+ ntext: { name: "NCLOB" },
333
+ integer: { name: "NUMBER", limit: 38 },
334
+ float: { name: "BINARY_FLOAT" },
335
+ decimal: { name: "DECIMAL" },
336
+ datetime: { name: "TIMESTAMP" },
337
+ timestamp: { name: "TIMESTAMP" },
338
+ timestamptz: { name: "TIMESTAMP WITH TIME ZONE" },
339
+ timestampltz: { name: "TIMESTAMP WITH LOCAL TIME ZONE" },
340
+ time: { name: "TIMESTAMP" },
341
+ date: { name: "DATE" },
342
+ binary: { name: "BLOB" },
343
+ boolean: { name: "NUMBER", limit: 1 },
344
+ raw: { name: "RAW", limit: 2000 },
345
+ bigint: { name: "NUMBER", limit: 19 }
346
+ }
347
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
348
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
349
+ boolean: { name: "VARCHAR2", limit: 1 }
350
+ )
351
+ #:startdoc:
514
352
 
515
- # Cast a +value+ to a type that the database understands.
516
- def type_cast(value, column)
517
- case value
518
- when true, false
519
- if emulate_booleans_from_strings || column && column.type == :string
520
- self.class.boolean_to_string(value)
521
- else
522
- value ? 1 : 0
523
- end
524
- when Date, Time
525
- if value.acts_like?(:time)
526
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
527
- value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
528
- else
529
- value
530
- end
531
- else
532
- super
533
- end
353
+ def native_database_types #:nodoc:
354
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
534
355
  end
535
356
 
536
357
  # CONNECTION MANAGEMENT ====================================
@@ -559,15 +380,15 @@ module ActiveRecord
559
380
  # last known state, which isn't good enough if the connection has
560
381
  # gone stale since the last use.
561
382
  @connection.ping
562
- rescue OracleEnhancedConnectionException
383
+ rescue OracleEnhanced::ConnectionException
563
384
  false
564
385
  end
565
386
 
566
387
  # Reconnects to the database.
567
388
  def reconnect! #:nodoc:
568
- clear_cache!
389
+ super
569
390
  @connection.reset!
570
- rescue OracleEnhancedConnectionException => e
391
+ rescue OracleEnhanced::ConnectionException => e
571
392
  @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
572
393
  end
573
394
 
@@ -578,188 +399,16 @@ module ActiveRecord
578
399
 
579
400
  # Disconnects from the database.
580
401
  def disconnect! #:nodoc:
581
- clear_cache!
402
+ super
582
403
  @connection.logoff rescue nil
583
404
  end
584
405
 
585
- # DATABASE STATEMENTS ======================================
586
- #
587
- # see: abstract/database_statements.rb
588
-
589
- # Executes a SQL statement
590
- def execute(sql, name = nil)
591
- log(sql, name) { @connection.exec(sql) }
592
- end
593
-
594
- def substitute_at(column, index)
595
- Arel.sql(":a#{index + 1}")
596
- end
597
-
598
- def clear_cache!
599
- @statements.clear
600
- end
601
-
602
- def exec_query(sql, name = 'SQL', binds = [])
603
- log(sql, name, binds) do
604
- cursor = nil
605
- cached = false
606
- if binds.empty?
607
- cursor = @connection.prepare(sql)
608
- else
609
- unless @statements.key? sql
610
- @statements[sql] = @connection.prepare(sql)
611
- end
612
-
613
- cursor = @statements[sql]
614
-
615
- binds.each_with_index do |bind, i|
616
- col, val = bind
617
- cursor.bind_param(i + 1, type_cast(val, col), col)
618
- end
619
-
620
- cached = true
621
- end
622
-
623
- cursor.exec
624
-
625
- if name == 'EXPLAIN'
626
- res = true
627
- else
628
- columns = cursor.get_col_names.map do |col_name|
629
- @connection.oracle_downcase(col_name)
630
- end
631
- rows = []
632
- fetch_options = {:get_lob_value => (name != 'Writable Large Object')}
633
- while row = cursor.fetch(fetch_options)
634
- rows << row
635
- end
636
- res = ActiveRecord::Result.new(columns, rows)
637
- end
638
-
639
- cursor.close unless cached
640
- res
641
- end
642
- end
643
-
644
- def supports_statement_cache?
645
- true
646
- end
647
-
648
- def supports_explain?
649
- true
650
- end
651
-
652
- def explain(arel, binds = [])
653
- sql = "EXPLAIN PLAN FOR #{to_sql(arel)}"
654
- return if sql =~ /FROM all_/
655
- if ORACLE_ENHANCED_CONNECTION == :jdbc
656
- exec_query(sql, 'EXPLAIN', binds)
657
- else
658
- exec_query(sql, 'EXPLAIN')
659
- end
660
- select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
661
- end
662
-
663
- # Returns an array of arrays containing the field values.
664
- # Order is the same as that returned by #columns.
665
- def select_rows(sql, name = nil)
666
- # last parameter indicates to return also column list
667
- result = columns = nil
668
- log(sql, name) do
669
- result, columns = @connection.select(sql, name, true)
670
- end
671
- result.map{ |v| columns.map{|c| v[c]} }
672
- end
673
-
674
- # Executes an INSERT statement and returns the new record's ID
675
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
676
- # if primary key value is already prefetched from sequence
677
- # or if there is no primary key
678
- if id_value || pk.nil?
679
- execute(sql, name)
680
- return id_value
681
- end
682
-
683
- sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
684
- log(sql, name) do
685
- @connection.exec_with_returning(sql_with_returning)
686
- end
687
- end
688
- protected :insert_sql
689
-
690
- # New method in ActiveRecord 3.1
691
- # Will add RETURNING clause in case of trigger generated primary keys
692
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
693
- unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
694
- sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
695
- returning_id_col = OracleEnhancedColumn.new("returning_id", nil, "number", true, "dual", :integer, true, true)
696
- (binds = binds.dup) << [returning_id_col, nil]
697
- end
698
- [sql, binds]
699
- end
700
-
701
- # New method in ActiveRecord 3.1
702
- def exec_insert(sql, name, binds)
703
- log(sql, name, binds) do
704
- returning_id_col = returning_id_index = nil
705
- cursor = if @statements.key?(sql)
706
- @statements[sql]
707
- else
708
- @statements[sql] = @connection.prepare(sql)
709
- end
710
-
711
- binds.each_with_index do |bind, i|
712
- col, val = bind
713
- if col.returning_id?
714
- returning_id_col = [col]
715
- returning_id_index = i + 1
716
- cursor.bind_returning_param(returning_id_index, Integer)
717
- else
718
- cursor.bind_param(i + 1, type_cast(val, col), col)
719
- end
720
- end
721
-
722
- cursor.exec_update
723
-
724
- rows = []
725
- if returning_id_index
726
- returning_id = cursor.get_returning_param(returning_id_index, Integer)
727
- rows << [returning_id]
728
- end
729
- ActiveRecord::Result.new(returning_id_col || [], rows)
730
- end
731
- end
732
-
733
- # New method in ActiveRecord 3.1
734
- def exec_update(sql, name, binds)
735
- log(sql, name, binds) do
736
- cached = false
737
- if binds.empty?
738
- cursor = @connection.prepare(sql)
739
- else
740
- cursor = if @statements.key?(sql)
741
- @statements[sql]
742
- else
743
- @statements[sql] = @connection.prepare(sql)
744
- end
745
-
746
- binds.each_with_index do |bind, i|
747
- col, val = bind
748
- cursor.bind_param(i + 1, type_cast(val, col), col)
749
- end
750
- cached = true
751
- end
752
-
753
- res = cursor.exec_update
754
- cursor.close unless cached
755
- res
756
- end
406
+ def discard!
407
+ @connection = nil
757
408
  end
758
409
 
759
- alias :exec_delete :exec_update
760
-
761
410
  # use in set_sequence_name to avoid fetching primary key value from sequence
762
- AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
411
+ AUTOGENERATED_SEQUENCE_NAME = "autogenerated".freeze
763
412
 
764
413
  # Returns the next sequence value from a sequence generator. Not generally
765
414
  # called directly; used by ActiveRecord to get the next primary key value
@@ -768,266 +417,81 @@ module ActiveRecord
768
417
  # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
769
418
  return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
770
419
  # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
771
- @connection.select_value("SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual")
772
- end
773
-
774
- def begin_db_transaction #:nodoc:
775
- @connection.autocommit = false
776
- end
777
-
778
- def commit_db_transaction #:nodoc:
779
- @connection.commit
780
- ensure
781
- @connection.autocommit = true
782
- end
783
-
784
- def rollback_db_transaction #:nodoc:
785
- @connection.rollback
786
- ensure
787
- @connection.autocommit = true
788
- end
789
-
790
- def create_savepoint #:nodoc:
791
- execute("SAVEPOINT #{current_savepoint_name}")
792
- end
793
-
794
- def rollback_to_savepoint #:nodoc:
795
- execute("ROLLBACK TO #{current_savepoint_name}")
796
- end
797
-
798
- def release_savepoint #:nodoc:
799
- # there is no RELEASE SAVEPOINT statement in Oracle
800
- end
801
-
802
- def add_limit_offset!(sql, options) #:nodoc:
803
- # added to_i for limit and offset to protect from SQL injection
804
- offset = (options[:offset] || 0).to_i
805
- limit = options[:limit]
806
- limit = limit.is_a?(String) && limit.blank? ? nil : limit && limit.to_i
807
- if limit && offset > 0
808
- sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_ WHERE ROWNUM <= #{offset+limit}) WHERE raw_rnum_ > #{offset}"
809
- elsif limit
810
- sql.replace "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
811
- elsif offset > 0
812
- sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_) WHERE raw_rnum_ > #{offset}"
813
- end
420
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "next sequence value")
421
+ SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual
422
+ SQL
814
423
  end
815
424
 
816
- @@do_not_prefetch_primary_key = {}
817
-
818
425
  # Returns true for Oracle adapter (since Oracle requires primary key
819
426
  # values to be pre-fetched before insert). See also #next_sequence_value.
820
427
  def prefetch_primary_key?(table_name = nil)
821
428
  return true if table_name.nil?
822
429
  table_name = table_name.to_s
823
- do_not_prefetch = @@do_not_prefetch_primary_key[table_name]
824
- if do_not_prefetch.nil?
825
- owner, desc_table_name, db_link = @connection.describe(table_name)
826
- @@do_not_prefetch_primary_key[table_name] = do_not_prefetch =
827
- !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
828
- has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
829
- end
430
+ owner, desc_table_name, db_link = @connection.describe(table_name)
431
+ do_not_prefetch = !has_primary_key?(table_name, owner, desc_table_name, db_link) || has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
830
432
  !do_not_prefetch
831
433
  end
832
434
 
833
- # used just in tests to clear prefetch primary key flag for all tables
834
- def clear_prefetch_primary_key #:nodoc:
835
- @@do_not_prefetch_primary_key = {}
836
- end
837
-
838
- # Returns default sequence name for table.
839
- # Will take all or first 26 characters of table name and append _seq suffix
840
- def default_sequence_name(table_name, primary_key = nil)
841
- # TODO: remove schema prefix if present before truncating
842
- # truncate table name if necessary to fit in max length of identifier
843
- "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
844
- end
845
-
846
- # Inserts the given fixture into the table. Overridden to properly handle lobs.
847
- def insert_fixture(fixture, table_name) #:nodoc:
848
- super
849
-
850
- if ActiveRecord::Base.pluralize_table_names
851
- klass = table_name.singularize.camelize
852
- else
853
- klass = table_name.camelize
435
+ def reset_pk_sequence!(table_name, primary_key = nil, sequence_name = nil) #:nodoc:
436
+ return nil unless data_source_exists?(table_name)
437
+ unless primary_key && sequence_name
438
+ # *Note*: Only primary key is implemented - sequence will be nil.
439
+ primary_key, sequence_name = pk_and_sequence_for(table_name)
440
+ # TODO This sequence_name implemantation is just enough
441
+ # to satisty fixures. To get correct sequence_name always
442
+ # pk_and_sequence_for method needs some work.
443
+ begin
444
+ sequence_name = table_name.classify.constantize.sequence_name
445
+ rescue
446
+ sequence_name = default_sequence_name(table_name)
447
+ end
854
448
  end
855
449
 
856
- klass = klass.constantize rescue nil
857
- if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
858
- write_lobs(table_name, klass, fixture, klass.lob_columns)
450
+ if @logger && primary_key && !sequence_name
451
+ @logger.warn "#{table_name} has primary key #{primary_key} with no default sequence"
859
452
  end
860
- end
861
453
 
862
- # Writes LOB values from attributes for specified columns
863
- def write_lobs(table_name, klass, attributes, columns) #:nodoc:
864
- # is class with composite primary key>
865
- is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
866
- if is_with_cpk
867
- id = klass.primary_key.map {|pk| attributes[pk.to_s] }
868
- else
869
- id = quote(attributes[klass.primary_key])
870
- end
871
- columns.each do |col|
872
- value = attributes[col.name]
873
- # changed sequence of next two lines - should check if value is nil before converting to yaml
874
- next if value.nil? || (value == '')
875
- value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
876
- uncached do
877
- sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" :
878
- "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE"
879
- unless lob_record = select_one(sql, 'Writable Large Object')
880
- raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
881
- end
882
- lob = lob_record[col.name]
883
- @connection.write_lob(lob, value.to_s, col.type == :binary)
884
- end
454
+ if primary_key && sequence_name
455
+ new_start_value = select_value(<<-SQL.strip.gsub(/\s+/, " "), "new start value")
456
+ select NVL(max(#{quote_column_name(primary_key)}),0) + 1 from #{quote_table_name(table_name)}
457
+ SQL
458
+
459
+ execute "DROP SEQUENCE #{quote_table_name(sequence_name)}"
460
+ execute "CREATE SEQUENCE #{quote_table_name(sequence_name)} START WITH #{new_start_value}"
885
461
  end
886
462
  end
887
463
 
888
464
  # Current database name
889
465
  def current_database
890
- select_value("SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual")
466
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "current database on CDB ")
467
+ SELECT SYS_CONTEXT('userenv', 'con_name') FROM dual
468
+ SQL
469
+ rescue ActiveRecord::StatementInvalid
470
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "current database")
471
+ SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual
472
+ SQL
891
473
  end
892
474
 
893
475
  # Current database session user
894
476
  def current_user
895
- select_value("SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual")
896
- end
897
-
898
- # Default tablespace name of current user
899
- def default_tablespace
900
- select_value("SELECT LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'session_user')")
901
- end
902
-
903
- def tables(name = nil) #:nodoc:
904
- select_values(
905
- "SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name) FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'session_user') AND secondary = 'N'",
906
- name)
907
- end
908
-
909
- # Will return true if database object exists (to be able to use also views and synonyms for ActiveRecord models)
910
- def table_exists?(table_name)
911
- (owner, table_name, db_link) = @connection.describe(table_name)
912
- true
913
- rescue
914
- false
915
- end
916
-
917
- def materialized_views #:nodoc:
918
- select_values("SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'session_user')")
919
- end
920
-
921
- cattr_accessor :all_schema_indexes #:nodoc:
922
-
923
- # This method selects all indexes at once, and caches them in a class variable.
924
- # Subsequent index calls get them from the variable, without going to the DB.
925
- def indexes(table_name, name = nil) #:nodoc:
926
- (owner, table_name, db_link) = @connection.describe(table_name)
927
- unless all_schema_indexes
928
- default_tablespace_name = default_tablespace
929
- result = select_all(<<-SQL.strip.gsub(/\s+/, ' '))
930
- SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
931
- i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
932
- LOWER(i.tablespace_name) AS tablespace_name,
933
- LOWER(c.column_name) AS column_name, e.column_expression,
934
- atc.virtual_column
935
- FROM all_indexes#{db_link} i
936
- JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
937
- LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
938
- e.index_owner = i.owner AND e.column_position = c.column_position
939
- LEFT OUTER JOIN all_tab_cols#{db_link} atc ON i.table_name = atc.table_name AND
940
- c.column_name = atc.column_name AND i.owner = atc.owner AND atc.hidden_column = 'NO'
941
- WHERE i.owner = '#{owner}'
942
- AND i.table_owner = '#{owner}'
943
- AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
944
- WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
945
- ORDER BY i.index_name, c.column_position
946
- SQL
947
-
948
- current_index = nil
949
- self.all_schema_indexes = []
950
-
951
- result.each do |row|
952
- # have to keep track of indexes because above query returns dups
953
- # there is probably a better query we could figure out
954
- if current_index != row['index_name']
955
- statement_parameters = nil
956
- if row['index_type'] == 'DOMAIN' && row['ityp_owner'] == 'CTXSYS' && row['ityp_name'] == 'CONTEXT'
957
- procedure_name = default_datastore_procedure(row['index_name'])
958
- source = select_values(<<-SQL).join
959
- SELECT text
960
- FROM all_source#{db_link}
961
- WHERE owner = '#{owner}'
962
- AND name = '#{procedure_name.upcase}'
963
- ORDER BY line
964
- SQL
965
- if source =~ /-- add_context_index_parameters (.+)\n/
966
- statement_parameters = $1
967
- end
968
- end
969
- all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'],
970
- row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
971
- row['parameters'], statement_parameters,
972
- row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
973
- current_index = row['index_name']
974
- end
975
-
976
- # Functional index columns and virtual columns both get stored as column expressions,
977
- # but re-creating a virtual column index as an expression (instead of using the virtual column's name)
978
- # results in a ORA-54018 error. Thus, we only want the column expression value returned
979
- # when the column is not virtual.
980
- if row['column_expression'] && row['virtual_column'] != 'YES'
981
- all_schema_indexes.last.columns << row['column_expression']
982
- else
983
- all_schema_indexes.last.columns << row['column_name'].downcase
984
- end
985
- end
986
- end
987
-
988
- # Return the indexes just for the requested table, since AR is structured that way
989
- table_name = table_name.downcase
990
- all_schema_indexes.select{|i| i.table == table_name}
991
- end
992
-
993
- @@ignore_table_columns = nil #:nodoc:
994
-
995
- # set ignored columns for table
996
- def ignore_table_columns(table_name, *args) #:nodoc:
997
- @@ignore_table_columns ||= {}
998
- @@ignore_table_columns[table_name] ||= []
999
- @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
1000
- @@ignore_table_columns[table_name].uniq!
1001
- end
1002
-
1003
- def ignored_table_columns(table_name) #:nodoc:
1004
- @@ignore_table_columns ||= {}
1005
- @@ignore_table_columns[table_name]
1006
- end
1007
-
1008
- # used just in tests to clear ignored table columns
1009
- def clear_ignored_table_columns #:nodoc:
1010
- @@ignore_table_columns = nil
1011
- end
1012
-
1013
- @@table_column_type = nil #:nodoc:
1014
-
1015
- # set explicit type for specified table columns
1016
- def set_type_for_columns(table_name, column_type, *args) #:nodoc:
1017
- @@table_column_type ||= {}
1018
- @@table_column_type[table_name] ||= {}
1019
- args.each do |col|
1020
- @@table_column_type[table_name][col.to_s.downcase] = column_type
1021
- end
477
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "current user")
478
+ SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual
479
+ SQL
1022
480
  end
1023
481
 
1024
- def get_type_for_column(table_name, column_name) #:nodoc:
1025
- @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
482
+ # Current database session schema
483
+ def current_schema
484
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "current schema")
485
+ SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual
486
+ SQL
1026
487
  end
1027
488
 
1028
- # used just in tests to clear column data type definitions
1029
- def clear_types_for_columns #:nodoc:
1030
- @@table_column_type = nil
489
+ # Default tablespace name of current user
490
+ def default_tablespace
491
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "default tablespace")
492
+ SELECT LOWER(default_tablespace) FROM user_users
493
+ WHERE username = SYS_CONTEXT('userenv', 'current_schema')
494
+ SQL
1031
495
  end
1032
496
 
1033
497
  # check if table has primary key trigger with _pkt suffix
@@ -1035,152 +499,76 @@ module ActiveRecord
1035
499
  (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1036
500
 
1037
501
  trigger_name = default_trigger_name(table_name).upcase
1038
- pkt_sql = <<-SQL
502
+
503
+ !!select_value(<<-SQL.strip.gsub(/\s+/, " "), "Primary Key Trigger", [bind_string("owner", owner), bind_string("trigger_name", trigger_name), bind_string("owner", owner), bind_string("table_name", desc_table_name)])
1039
504
  SELECT trigger_name
1040
505
  FROM all_triggers#{db_link}
1041
- WHERE owner = '#{owner}'
1042
- AND trigger_name = '#{trigger_name}'
1043
- AND table_owner = '#{owner}'
1044
- AND table_name = '#{desc_table_name}'
506
+ WHERE owner = :owner
507
+ AND trigger_name = :trigger_name
508
+ AND table_owner = :owner
509
+ AND table_name = :table_name
1045
510
  AND status = 'ENABLED'
1046
511
  SQL
1047
- select_value(pkt_sql, 'Primary Key Trigger') ? true : false
1048
- end
1049
-
1050
- ##
1051
- # :singleton-method:
1052
- # Cache column description between requests.
1053
- # Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
1054
- # This can speed up request processing in development mode if development database is not on local computer.
1055
- #
1056
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
1057
- cattr_accessor :cache_columns
1058
- self.cache_columns = false
1059
-
1060
- def columns(table_name, name = nil) #:nodoc:
1061
- if @@cache_columns
1062
- @@columns_cache ||= {}
1063
- @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
1064
- else
1065
- columns_without_cache(table_name, name)
1066
- end
1067
512
  end
1068
513
 
1069
- def columns_without_cache(table_name, name = nil) #:nodoc:
1070
- table_name = table_name.to_s
1071
- # get ignored_columns by original table name
1072
- ignored_columns = ignored_table_columns(table_name)
1073
-
514
+ def column_definitions(table_name)
1074
515
  (owner, desc_table_name, db_link) = @connection.describe(table_name)
1075
516
 
1076
- # reset do_not_prefetch_primary_key cache for this table
1077
- @@do_not_prefetch_primary_key[table_name] = nil
1078
-
1079
- table_cols = <<-SQL.strip.gsub(/\s+/, ' ')
1080
- SELECT column_name AS name, data_type AS sql_type, data_default, nullable, virtual_column, hidden_column, data_type_owner AS sql_type_owner,
1081
- DECODE(data_type, 'NUMBER', data_precision,
517
+ select_all(<<-SQL.strip.gsub(/\s+/, " "), "Column definitions", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
518
+ SELECT cols.column_name AS name, cols.data_type AS sql_type,
519
+ cols.data_default, cols.nullable, cols.virtual_column, cols.hidden_column,
520
+ cols.data_type_owner AS sql_type_owner,
521
+ DECODE(cols.data_type, 'NUMBER', data_precision,
1082
522
  'FLOAT', data_precision,
1083
523
  'VARCHAR2', DECODE(char_used, 'C', char_length, data_length),
1084
524
  'RAW', DECODE(char_used, 'C', char_length, data_length),
1085
525
  'CHAR', DECODE(char_used, 'C', char_length, data_length),
1086
526
  NULL) AS limit,
1087
- DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale
1088
- FROM all_tab_cols#{db_link}
1089
- WHERE owner = '#{owner}'
1090
- AND table_name = '#{desc_table_name}'
1091
- AND hidden_column = 'NO'
1092
- ORDER BY column_id
527
+ DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale,
528
+ comments.comments as column_comment
529
+ FROM all_tab_cols#{db_link} cols, all_col_comments#{db_link} comments
530
+ WHERE cols.owner = :owner
531
+ AND cols.table_name = :table_name
532
+ AND cols.hidden_column = 'NO'
533
+ AND cols.owner = comments.owner
534
+ AND cols.table_name = comments.table_name
535
+ AND cols.column_name = comments.column_name
536
+ ORDER BY cols.column_id
1093
537
  SQL
1094
-
1095
- # added deletion of ignored columns
1096
- select_all(table_cols, name).delete_if do |row|
1097
- ignored_columns && ignored_columns.include?(row['name'].downcase)
1098
- end.map do |row|
1099
- limit, scale = row['limit'], row['scale']
1100
- if limit || scale
1101
- row['sql_type'] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
1102
- end
1103
-
1104
- if row['sql_type_owner']
1105
- row['sql_type'] = row['sql_type_owner'] + '.' + row['sql_type']
1106
- end
1107
-
1108
- is_virtual = row['virtual_column']=='YES'
1109
-
1110
- # clean up odd default spacing from Oracle
1111
- if row['data_default'] && !is_virtual
1112
- row['data_default'].sub!(/^(.*?)\s*$/, '\1')
1113
-
1114
- # If a default contains a newline these cleanup regexes need to
1115
- # match newlines.
1116
- row['data_default'].sub!(/^'(.*)'$/m, '\1')
1117
- row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
1118
- end
1119
-
1120
- OracleEnhancedColumn.new(oracle_downcase(row['name']),
1121
- row['data_default'],
1122
- row['sql_type'],
1123
- row['nullable'] == 'Y',
1124
- # pass table name for table specific column definitions
1125
- table_name,
1126
- # pass column type if specified in class definition
1127
- get_type_for_column(table_name, oracle_downcase(row['name'])), is_virtual)
1128
- end
1129
- end
1130
-
1131
- # used just in tests to clear column cache
1132
- def clear_columns_cache #:nodoc:
1133
- @@columns_cache = nil
1134
- @@pk_and_sequence_for_cache = nil
1135
538
  end
1136
539
 
1137
- # used in migrations to clear column cache for specified table
1138
- def clear_table_columns_cache(table_name)
1139
- if @@cache_columns
1140
- @@columns_cache ||= {}
1141
- @@columns_cache[table_name.to_s] = nil
1142
- end
1143
- end
1144
-
1145
- ##
1146
- # :singleton-method:
1147
- # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
1148
- #
1149
- # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
1150
- cattr_accessor :default_sequence_start_value
1151
- self.default_sequence_start_value = 10000
1152
-
1153
540
  # Find a table's primary key and sequence.
1154
541
  # *Note*: Only primary key is implemented - sequence will be nil.
1155
- def pk_and_sequence_for(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1156
- if @@cache_columns
1157
- @@pk_and_sequence_for_cache ||= {}
1158
- if @@pk_and_sequence_for_cache.key?(table_name)
1159
- @@pk_and_sequence_for_cache[table_name]
1160
- else
1161
- @@pk_and_sequence_for_cache[table_name] = pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1162
- end
1163
- else
1164
- pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1165
- end
1166
- end
1167
-
1168
- def pk_and_sequence_for_without_cache(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
542
+ def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil, db_link = nil) #:nodoc:
1169
543
  (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1170
544
 
545
+ seqs = select_values(<<-SQL.strip.gsub(/\s+/, " "), "Sequence", [bind_string("owner", owner), bind_string("sequence_name", default_sequence_name(desc_table_name).upcase)])
546
+ select us.sequence_name
547
+ from all_sequences#{db_link} us
548
+ where us.sequence_owner = :owner
549
+ and us.sequence_name = :sequence_name
550
+ SQL
551
+
1171
552
  # changed back from user_constraints to all_constraints for consistency
1172
- pks = select_values(<<-SQL.strip.gsub(/\s+/, ' '), 'Primary Key')
553
+ pks = select_values(<<-SQL.strip.gsub(/\s+/, " "), "Primary Key", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
1173
554
  SELECT cc.column_name
1174
555
  FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc
1175
- WHERE c.owner = '#{owner}'
1176
- AND c.table_name = '#{desc_table_name}'
556
+ WHERE c.owner = :owner
557
+ AND c.table_name = :table_name
1177
558
  AND c.constraint_type = 'P'
1178
559
  AND cc.owner = c.owner
1179
560
  AND cc.constraint_name = c.constraint_name
1180
561
  SQL
1181
562
 
563
+ warn <<-WARNING.strip_heredoc if pks.count > 1
564
+ WARNING: Active Record does not support composite primary key.
565
+
566
+ #{table_name} has composite primary key. Composite primary key is ignored.
567
+ WARNING
568
+
1182
569
  # only support single column keys
1183
- pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
570
+ pks.size == 1 ? [oracle_downcase(pks.first),
571
+ oracle_downcase(seqs.first)] : nil
1184
572
  end
1185
573
 
1186
574
  # Returns just a table's primary key
@@ -1189,215 +577,129 @@ module ActiveRecord
1189
577
  pk_and_sequence && pk_and_sequence.first
1190
578
  end
1191
579
 
1192
- def has_primary_key?(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
580
+ def has_primary_key?(table_name, owner = nil, desc_table_name = nil, db_link = nil) #:nodoc:
1193
581
  !pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
1194
582
  end
1195
583
 
1196
- # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1197
- #
1198
- # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1199
- # queries. However, with those columns included in the SELECT DISTINCT list, you
1200
- # won't actually get a distinct list of the column you want (presuming the column
1201
- # has duplicates with multiple values for the ordered-by columns. So we use the
1202
- # FIRST_VALUE function to get a single (first) value for each column, effectively
1203
- # making every row the same.
1204
- #
1205
- # distinct("posts.id", "posts.created_at desc")
1206
- def distinct(columns, order_by) #:nodoc:
1207
- return "DISTINCT #{columns}" if order_by.blank?
1208
-
1209
- # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1210
- # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1211
- order_columns = if order_by.is_a?(String)
1212
- order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1213
- else # in latest ActiveRecord versions order_by is already Array
1214
- order_by
1215
- end
1216
- order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1217
- # remove any ASC/DESC modifiers
1218
- value = c =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : c
1219
- "FIRST_VALUE(#{value}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1220
- end
1221
- sql = "DISTINCT #{columns}, "
1222
- sql << order_columns * ", "
1223
- end
584
+ def primary_keys(table_name) # :nodoc:
585
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1224
586
 
1225
- def temporary_table?(table_name) #:nodoc:
1226
- select_value("SELECT temporary FROM user_tables WHERE table_name = '#{table_name.upcase}'") == 'Y'
587
+ pks = select_values(<<-SQL.strip.gsub(/\s+/, " "), "Primary Keys", [bind_string("owner", owner), bind_string("table_name", desc_table_name)])
588
+ SELECT cc.column_name
589
+ FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc
590
+ WHERE c.owner = :owner
591
+ AND c.table_name = :table_name
592
+ AND c.constraint_type = 'P'
593
+ AND cc.owner = c.owner
594
+ AND cc.constraint_name = c.constraint_name
595
+ order by cc.position
596
+ SQL
597
+ pks.map { |pk| oracle_downcase(pk) }
1227
598
  end
1228
599
 
1229
- # ORDER BY clause for the passed order option.
1230
- #
1231
- # Uses column aliases as defined by #distinct.
1232
- #
1233
- # In Rails 3.x this method is moved to Arel
1234
- def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1235
- return sql if options[:order].blank?
1236
-
1237
- order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1238
- order.map! {|s| $1 if s =~ / (.*)/}
1239
- order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1240
-
1241
- sql << " ORDER BY #{order}"
600
+ def columns_for_distinct(columns, orders) #:nodoc:
601
+ # construct a valid columns name for DISTINCT clause,
602
+ # ie. one that includes the ORDER BY columns, using FIRST_VALUE such that
603
+ # the inclusion of these columns doesn't invalidate the DISTINCT
604
+ #
605
+ # It does not construct DISTINCT clause. Just return column names for distinct.
606
+ order_columns = orders.reject(&:blank?).map { |s|
607
+ s = s.to_sql unless s.is_a?(String)
608
+ # remove any ASC/DESC modifiers
609
+ s.gsub(/\s+(ASC|DESC)\s*?/i, "")
610
+ }.reject(&:blank?).map.with_index { |column, i|
611
+ "FIRST_VALUE(#{column}) OVER (PARTITION BY #{columns} ORDER BY #{column}) AS alias_#{i}__"
612
+ }
613
+ [super, *order_columns].join(", ")
1242
614
  end
1243
615
 
1244
- # construct additional wrapper subquery if select.offset is used to avoid generation of invalid subquery
1245
- # ... IN ( SELECT * FROM ( SELECT raw_sql_.*, rownum raw_rnum_ FROM ( ... ) raw_sql_ ) WHERE raw_rnum_ > ... )
1246
- def join_to_update(update, select) #:nodoc:
1247
- if select.offset
1248
- subsubselect = select.clone
1249
- subsubselect.projections = [update.key]
1250
-
1251
- subselect = Arel::SelectManager.new(select.engine)
1252
- subselect.project Arel.sql(quote_column_name update.key.name)
1253
- subselect.from subsubselect.as('alias_join_to_update')
1254
-
1255
- update.where update.key.in(subselect)
1256
- else
1257
- super
1258
- end
616
+ def temporary_table?(table_name) #:nodoc:
617
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "temporary table", [bind_string("table_name", table_name.upcase)]) == "Y"
618
+ SELECT temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'session_user')
619
+ SQL
1259
620
  end
1260
621
 
1261
- protected
622
+ private
1262
623
 
1263
- def translate_exception(exception, message) #:nodoc:
1264
- case @connection.error_code(exception)
1265
- when 1
1266
- RecordNotUnique.new(message, exception)
1267
- when 2291
1268
- InvalidForeignKey.new(message, exception)
1269
- else
624
+ def initialize_type_map(m = type_map)
1270
625
  super
1271
- end
1272
- end
1273
-
1274
- private
626
+ # oracle
627
+ register_class_with_precision m, %r(WITH TIME ZONE)i, Type::OracleEnhanced::TimestampTz
628
+ register_class_with_precision m, %r(WITH LOCAL TIME ZONE)i, Type::OracleEnhanced::TimestampLtz
629
+ register_class_with_limit m, %r(raw)i, Type::OracleEnhanced::Raw
630
+ register_class_with_limit m, %r(char)i, Type::OracleEnhanced::String
631
+ register_class_with_limit m, %r(clob)i, Type::OracleEnhanced::Text
632
+ register_class_with_limit m, %r(nclob)i, Type::OracleEnhanced::NationalCharacterText
633
+
634
+ m.register_type "NCHAR", Type::OracleEnhanced::NationalCharacterString.new
635
+ m.alias_type %r(NVARCHAR2)i, "NCHAR"
636
+
637
+ m.register_type(%r(NUMBER)i) do |sql_type|
638
+ scale = extract_scale(sql_type)
639
+ precision = extract_precision(sql_type)
640
+ limit = extract_limit(sql_type)
641
+ if scale == 0
642
+ Type::OracleEnhanced::Integer.new(precision: precision, limit: limit)
643
+ else
644
+ Type::Decimal.new(precision: precision, scale: scale)
645
+ end
646
+ end
1275
647
 
1276
- def select(sql, name = nil, binds = [])
1277
- if ActiveRecord.const_defined?(:Result)
1278
- exec_query(sql, name, binds).to_a
1279
- else
1280
- log(sql, name) do
1281
- @connection.select(sql, name, false)
648
+ if OracleEnhancedAdapter.emulate_booleans
649
+ m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new
1282
650
  end
1283
651
  end
1284
- end
1285
-
1286
- def oracle_downcase(column_name)
1287
- @connection.oracle_downcase(column_name)
1288
- end
1289
652
 
1290
- def compress_lines(string, join_with = "\n")
1291
- string.split($/).map { |line| line.strip }.join(join_with)
1292
- end
1293
-
1294
- public
1295
- # DBMS_OUTPUT =============================================
1296
- #
1297
- # PL/SQL in Oracle uses dbms_output for logging print statements
1298
- # These methods stick that output into the Rails log so Ruby and PL/SQL
1299
- # code can can be debugged together in a single application
1300
-
1301
- # Maximum DBMS_OUTPUT buffer size
1302
- DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1303
-
1304
- # Turn DBMS_Output logging on
1305
- def enable_dbms_output
1306
- set_dbms_output_plsql_connection
1307
- @enable_dbms_output = true
1308
- plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1309
- end
1310
- # Turn DBMS_Output logging off
1311
- def disable_dbms_output
1312
- set_dbms_output_plsql_connection
1313
- @enable_dbms_output = false
1314
- plsql(:dbms_output).sys.dbms_output.disable
1315
- end
1316
- # Is DBMS_Output logging enabled?
1317
- def dbms_output_enabled?
1318
- @enable_dbms_output
1319
- end
1320
-
1321
- protected
1322
- def log(sql, name, binds = nil) #:nodoc:
1323
- if binds
1324
- super sql, name, binds
1325
- else
1326
- super sql, name
653
+ def extract_value_from_default(default)
654
+ case default
655
+ when String
656
+ default.gsub("''", "'")
657
+ else
658
+ default
659
+ end
1327
660
  end
1328
- ensure
1329
- log_dbms_output if dbms_output_enabled?
1330
- end
1331
661
 
1332
- private
1333
-
1334
- def set_dbms_output_plsql_connection
1335
- raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1336
- # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1337
- unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1338
- plsql(:dbms_output).connection = raw_connection
662
+ def extract_limit(sql_type) #:nodoc:
663
+ case sql_type
664
+ when /^bigint/i
665
+ 19
666
+ when /\((.*)\)/
667
+ $1.to_i
668
+ end
1339
669
  end
1340
- end
1341
670
 
1342
- def log_dbms_output
1343
- while true do
1344
- result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1345
- break unless result[:status] == 0
1346
- @logger.debug "DBMS_OUTPUT: #{result[:line]}" if @logger
671
+ def translate_exception(exception, message) #:nodoc:
672
+ case @connection.error_code(exception)
673
+ when 1
674
+ RecordNotUnique.new(message)
675
+ when 60
676
+ Deadlocked.new(message)
677
+ when 900, 904, 942, 955, 1418, 2289, 2449, 17008
678
+ ActiveRecord::StatementInvalid.new(message)
679
+ when 1400
680
+ ActiveRecord::NotNullViolation.new(message)
681
+ when 2291
682
+ InvalidForeignKey.new(message)
683
+ when 12899
684
+ ValueTooLong.new(message)
685
+ else
686
+ super
687
+ end
1347
688
  end
1348
- end
1349
689
 
1350
- end
1351
- end
1352
- end
690
+ # create bind object for type String
691
+ def bind_string(name, value)
692
+ ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
693
+ end
1353
694
 
1354
- # Added LOB writing callback for sessions stored in database
1355
- # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1356
- if defined?(CGI::Session::ActiveRecordStore::Session)
1357
- if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1358
- CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1359
- #:stopdoc:
1360
- class CGI::Session::ActiveRecordStore::Session
1361
- after_save :enhanced_write_lobs
695
+ ActiveRecord::Type.register(:boolean, Type::OracleEnhanced::Boolean, adapter: :oracleenhanced)
696
+ ActiveRecord::Type.register(:json, Type::OracleEnhanced::Json, adapter: :oracleenhanced)
1362
697
  end
1363
- #:startdoc:
1364
698
  end
1365
699
  end
1366
700
 
1367
- # Implementation of standard schema definition statements and extensions for schema definition
1368
- require 'active_record/connection_adapters/oracle_enhanced_schema_statements'
1369
- require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
701
+ require "active_record/connection_adapters/oracle_enhanced/version"
1370
702
 
1371
- # Extensions for schema definition
1372
- require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1373
-
1374
- # Extensions for context index definition
1375
- require 'active_record/connection_adapters/oracle_enhanced_context_index'
1376
-
1377
- # Load custom create, update, delete methods functionality
1378
- require 'active_record/connection_adapters/oracle_enhanced_procedures'
1379
-
1380
- # Load additional methods for composite_primary_keys support
1381
- require 'active_record/connection_adapters/oracle_enhanced_cpk'
1382
-
1383
- # Load patch for dirty tracking methods
1384
- require 'active_record/connection_adapters/oracle_enhanced_dirty'
1385
-
1386
- # Load rake tasks definitions
1387
- begin
1388
- require 'active_record/connection_adapters/oracle_enhanced_tasks'
1389
- rescue LoadError
1390
- end if defined?(Rails) || defined?(RAILS_ROOT)
1391
-
1392
- # Patches and enhancements for schema dumper
1393
- require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1394
-
1395
- # Implementation of structure dump
1396
- require 'active_record/connection_adapters/oracle_enhanced_structure_dump'
1397
-
1398
- # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1399
- require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1400
-
1401
- require 'active_record/connection_adapters/oracle_enhanced_activerecord_patches'
1402
-
1403
- require 'active_record/connection_adapters/oracle_enhanced_version'
703
+ module ActiveRecord
704
+ autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures"
705
+ end