activerecord-oracle_enhanced-adapter 1.5.6 → 1.6.0.beta1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/History.md +87 -0
  4. data/README.md +271 -174
  5. data/VERSION +1 -1
  6. data/activerecord-oracle_enhanced-adapter.gemspec +26 -22
  7. data/lib/active_record/connection_adapters/{oracle_enhanced_column.rb → oracle_enhanced/column.rb} +14 -63
  8. data/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb +65 -0
  9. data/lib/active_record/connection_adapters/{oracle_enhanced_connection.rb → oracle_enhanced/connection.rb} +2 -2
  10. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +347 -0
  11. data/lib/active_record/connection_adapters/{oracle_enhanced_cpk.rb → oracle_enhanced/cpk.rb} +0 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +257 -0
  13. data/lib/active_record/connection_adapters/{oracle_enhanced_database_tasks.rb → oracle_enhanced/database_tasks.rb} +0 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb +40 -0
  15. data/lib/active_record/connection_adapters/{oracle_enhanced_jdbc_connection.rb → oracle_enhanced/jdbc_connection.rb} +0 -0
  16. data/lib/active_record/connection_adapters/{oracle_enhanced_oci_connection.rb → oracle_enhanced/oci_connection.rb} +0 -0
  17. data/lib/active_record/connection_adapters/{oracle_enhanced_procedures.rb → oracle_enhanced/procedures.rb} +0 -0
  18. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb → oracle_enhanced/schema_creation.rb} +17 -16
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +92 -0
  20. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_dumper.rb → oracle_enhanced/schema_dumper.rb} +4 -32
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +543 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +65 -0
  23. data/lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb → oracle_enhanced/structure_dump.rb} +26 -4
  24. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +1 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +159 -66
  26. data/lib/active_record/oracle_enhanced/type/integer.rb +13 -0
  27. data/lib/active_record/oracle_enhanced/type/raw.rb +13 -0
  28. data/lib/active_record/oracle_enhanced/type/timestamp.rb +11 -0
  29. data/lib/activerecord-oracle_enhanced-adapter.rb +1 -1
  30. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +6 -31
  31. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +1 -1
  32. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +2 -2
  33. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +2 -2
  34. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +63 -63
  35. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +1 -1
  36. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +7 -13
  37. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +21 -175
  38. data/spec/spec_config.yaml.template +10 -0
  39. data/spec/spec_helper.rb +21 -10
  40. metadata +29 -25
  41. data/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +0 -77
  42. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -350
  43. data/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +0 -262
  44. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -45
  45. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -197
  46. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -450
  47. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  48. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
@@ -1,350 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module OracleEnhancedContextIndex
4
-
5
- # Define full text index with Oracle specific CONTEXT index type
6
- #
7
- # Oracle CONTEXT index by default supports full text indexing of one column.
8
- # This method allows full text index creation also on several columns
9
- # as well as indexing related table columns by generating stored procedure
10
- # that concatenates all columns for indexing as well as generating trigger
11
- # that will update main index column to trigger reindexing of record.
12
- #
13
- # Use +contains+ ActiveRecord model instance method to add CONTAINS where condition
14
- # and order by score of matched results.
15
- #
16
- # Options:
17
- #
18
- # * <tt>:name</tt>
19
- # * <tt>:index_column</tt>
20
- # * <tt>:index_column_trigger_on</tt>
21
- # * <tt>:tablespace</tt>
22
- # * <tt>:sync</tt> - 'MANUAL', 'EVERY "interval-string"' or 'ON COMMIT' (defaults to 'MANUAL').
23
- # * <tt>:lexer</tt> - Lexer options (e.g. <tt>:type => 'BASIC_LEXER', :base_letter => true</tt>).
24
- # * <tt>:wordlist</tt> - Wordlist options (e.g. <tt>:type => 'BASIC_WORDLIST', :prefix_index => true</tt>).
25
- # * <tt>:transactional</tt> - When +true+, the CONTAINS operator will process inserted and updated rows.
26
- #
27
- # ===== Examples
28
- #
29
- # ====== Creating single column index
30
- # add_context_index :posts, :title
31
- # search with
32
- # Post.contains(:title, 'word')
33
- #
34
- # ====== Creating index on several columns
35
- # add_context_index :posts, [:title, :body]
36
- # search with (use first column as argument for contains method but it will search in all index columns)
37
- # Post.contains(:title, 'word')
38
- #
39
- # ====== Creating index on several columns with dummy index column and commit option
40
- # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT'
41
- # search with
42
- # Post.contains(:all_text, 'word')
43
- #
44
- # ====== Creating index with trigger option (will reindex when specified columns are updated)
45
- # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT',
46
- # :index_column_trigger_on => [:created_at, :updated_at]
47
- # search with
48
- # Post.contains(:all_text, 'word')
49
- #
50
- # ====== Creating index on multiple tables
51
- # add_context_index :posts,
52
- # [:title, :body,
53
- # # specify aliases always with AS keyword
54
- # "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
55
- # ],
56
- # :name => 'post_and_comments_index',
57
- # :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
58
- # :sync => 'ON COMMIT'
59
- # search in any table columns
60
- # Post.contains(:all_text, 'word')
61
- # search in specified column
62
- # Post.contains(:all_text, "aaa within title")
63
- # Post.contains(:all_text, "bbb within comment_author")
64
- #
65
- # ====== Creating index using lexer
66
- # add_context_index :posts, :title, :lexer => { :type => 'BASIC_LEXER', :base_letter => true, ... }
67
- #
68
- # ====== Creating index using wordlist
69
- # add_context_index :posts, :title, :wordlist => { :type => 'BASIC_WORDLIST', :prefix_index => true, ... }
70
- #
71
- # ====== Creating transactional index (will reindex changed rows when querying)
72
- # add_context_index :posts, :title, :transactional => true
73
- #
74
- def add_context_index(table_name, column_name, options = {})
75
- self.all_schema_indexes = nil
76
- column_names = Array(column_name)
77
- index_name = options[:name] || index_name(table_name, :column => options[:index_column] || column_names,
78
- # CONEXT index name max length is 25
79
- :identifier_max_length => 25)
80
-
81
- quoted_column_name = quote_column_name(options[:index_column] || column_names.first)
82
- if options[:index_column_trigger_on]
83
- raise ArgumentError, "Option :index_column should be specified together with :index_column_trigger_on option" \
84
- unless options[:index_column]
85
- create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on])
86
- end
87
-
88
- sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
89
- sql << " (#{quoted_column_name})"
90
- sql << " INDEXTYPE IS CTXSYS.CONTEXT"
91
- parameters = []
92
- if column_names.size > 1
93
- procedure_name = default_datastore_procedure(index_name)
94
- datastore_name = default_datastore_name(index_name)
95
- create_datastore_procedure(table_name, procedure_name, column_names, options)
96
- create_datastore_preference(datastore_name, procedure_name)
97
- parameters << "DATASTORE #{datastore_name} SECTION GROUP CTXSYS.AUTO_SECTION_GROUP"
98
- end
99
- if options[:tablespace]
100
- storage_name = default_storage_name(index_name)
101
- create_storage_preference(storage_name, options[:tablespace])
102
- parameters << "STORAGE #{storage_name}"
103
- end
104
- if options[:sync]
105
- parameters << "SYNC(#{options[:sync]})"
106
- end
107
- if options[:lexer] && (lexer_type = options[:lexer][:type])
108
- lexer_name = default_lexer_name(index_name)
109
- (lexer_options = options[:lexer].dup).delete(:type)
110
- create_lexer_preference(lexer_name, lexer_type, lexer_options)
111
- parameters << "LEXER #{lexer_name}"
112
- end
113
- if options[:wordlist] && (wordlist_type = options[:wordlist][:type])
114
- wordlist_name = default_wordlist_name(index_name)
115
- (wordlist_options = options[:wordlist].dup).delete(:type)
116
- create_wordlist_preference(wordlist_name, wordlist_type, wordlist_options)
117
- parameters << "WORDLIST #{wordlist_name}"
118
- end
119
- if options[:transactional]
120
- parameters << "TRANSACTIONAL"
121
- end
122
- unless parameters.empty?
123
- sql << " PARAMETERS ('#{parameters.join(' ')}')"
124
- end
125
- execute sql
126
- end
127
-
128
- # Drop full text index with Oracle specific CONTEXT index type
129
- def remove_context_index(table_name, options = {})
130
- self.all_schema_indexes = nil
131
- unless Hash === options # if column names passed as argument
132
- options = {:column => Array(options)}
133
- end
134
- index_name = options[:name] || index_name(table_name,
135
- :column => options[:index_column] || options[:column], :identifier_max_length => 25)
136
- execute "DROP INDEX #{index_name}"
137
- drop_ctx_preference(default_datastore_name(index_name))
138
- drop_ctx_preference(default_storage_name(index_name))
139
- procedure_name = default_datastore_procedure(index_name)
140
- execute "DROP PROCEDURE #{quote_table_name(procedure_name)}" rescue nil
141
- drop_index_column_trigger(index_name)
142
- end
143
-
144
- private
145
-
146
- def create_datastore_procedure(table_name, procedure_name, column_names, options)
147
- quoted_table_name = quote_table_name(table_name)
148
- select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i }
149
- select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, ' ') }
150
- keys, selected_columns = parse_select_queries(select_queries)
151
- quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)}
152
- execute compress_lines(<<-SQL)
153
- CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)}
154
- (p_rowid IN ROWID,
155
- p_clob IN OUT NOCOPY CLOB) IS
156
- -- add_context_index_parameters #{(column_names+select_queries).inspect}#{!options.empty? ? ', ' << options.inspect[1..-2] : ''}
157
- #{
158
- selected_columns.map do |cols|
159
- cols.map do |col|
160
- raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28
161
- "l_#{col} VARCHAR2(32767);\n"
162
- end.join
163
- end.join
164
- } BEGIN
165
- FOR r1 IN (
166
- SELECT #{quoted_column_names.join(', ')}
167
- FROM #{quoted_table_name}
168
- WHERE #{quoted_table_name}.ROWID = p_rowid
169
- ) LOOP
170
- #{
171
- (column_names.map do |col|
172
- col = col.to_s
173
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
174
- "IF LENGTH(r1.#{col}) > 0 THEN\n" <<
175
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" <<
176
- "END IF;\n" <<
177
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
178
- end.join) <<
179
- (selected_columns.zip(select_queries).map do |cols, query|
180
- (cols.map do |col|
181
- "l_#{col} := '';\n"
182
- end.join) <<
183
- "FOR r2 IN (\n" <<
184
- query.gsub(/:(\w+)/,"r1.\\1") << "\n) LOOP\n" <<
185
- (cols.map do |col|
186
- "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n"
187
- end.join) <<
188
- "END LOOP;\n" <<
189
- (cols.map do |col|
190
- col = col.to_s
191
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
192
- "IF LENGTH(l_#{col}) > 0 THEN\n" <<
193
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" <<
194
- "END IF;\n" <<
195
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
196
- end.join)
197
- end.join)
198
- }
199
- END LOOP;
200
- END;
201
- SQL
202
- end
203
-
204
- def parse_select_queries(select_queries)
205
- keys = []
206
- selected_columns = []
207
- select_queries.each do |query|
208
- # get primary or foreign keys like :id or :something_id
209
- keys << (query.scan(/:\w+/).map{|k| k[1..-1].downcase.to_sym})
210
- select_part = query.scan(/^select\s.*\sfrom/i).first
211
- selected_columns << select_part.scan(/\sas\s+(\w+)/i).map{|c| c.first}
212
- end
213
- [keys.flatten.uniq, selected_columns]
214
- end
215
-
216
- def create_datastore_preference(datastore_name, procedure_name)
217
- drop_ctx_preference(datastore_name)
218
- execute <<-SQL
219
- BEGIN
220
- CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE');
221
- CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}');
222
- END;
223
- SQL
224
- end
225
-
226
- def create_storage_preference(storage_name, tablespace)
227
- drop_ctx_preference(storage_name)
228
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n"
229
- ['I_TABLE_CLAUSE', 'K_TABLE_CLAUSE', 'R_TABLE_CLAUSE',
230
- 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause|
231
- default_clause = case clause
232
- when 'R_TABLE_CLAUSE'; 'LOB(DATA) STORE AS (CACHE) '
233
- when 'I_INDEX_CLAUSE'; 'COMPRESS 2 '
234
- else ''
235
- end
236
- sql << "CTX_DDL.SET_ATTRIBUTE('#{storage_name}', '#{clause}', '#{default_clause}TABLESPACE #{tablespace}');\n"
237
- end
238
- sql << "END;\n"
239
- execute sql
240
- end
241
-
242
- def create_lexer_preference(lexer_name, lexer_type, options)
243
- drop_ctx_preference(lexer_name)
244
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n"
245
- options.each do |key, value|
246
- plsql_value = case value
247
- when String; "'#{value}'"
248
- when true; "'YES'"
249
- when false; "'NO'"
250
- when nil; 'NULL'
251
- else value
252
- end
253
- sql << "CTX_DDL.SET_ATTRIBUTE('#{lexer_name}', '#{key}', #{plsql_value});\n"
254
- end
255
- sql << "END;\n"
256
- execute sql
257
- end
258
-
259
- def create_wordlist_preference(wordlist_name, wordlist_type, options)
260
- drop_ctx_preference(wordlist_name)
261
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n"
262
- options.each do |key, value|
263
- plsql_value = case value
264
- when String; "'#{value}'"
265
- when true; "'YES'"
266
- when false; "'NO'"
267
- when nil; 'NULL'
268
- else value
269
- end
270
- sql << "CTX_DDL.SET_ATTRIBUTE('#{wordlist_name}', '#{key}', #{plsql_value});\n"
271
- end
272
- sql << "END;\n"
273
- execute sql
274
- end
275
-
276
- def drop_ctx_preference(preference_name)
277
- execute "BEGIN CTX_DDL.DROP_PREFERENCE('#{preference_name}'); END;" rescue nil
278
- end
279
-
280
- def create_index_column_trigger(table_name, index_name, index_column, index_column_source)
281
- trigger_name = default_index_column_trigger_name(index_name)
282
- columns = Array(index_column_source)
283
- quoted_column_names = columns.map{|col| quote_column_name(col)}.join(', ')
284
- execute compress_lines(<<-SQL)
285
- CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
286
- BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW
287
- BEGIN
288
- :new.#{quote_column_name(index_column)} := '1';
289
- END;
290
- SQL
291
- end
292
-
293
- def drop_index_column_trigger(index_name)
294
- trigger_name = default_index_column_trigger_name(index_name)
295
- execute "DROP TRIGGER #{quote_table_name(trigger_name)}" rescue nil
296
- end
297
-
298
- def default_datastore_procedure(index_name)
299
- "#{index_name}_prc"
300
- end
301
-
302
- def default_datastore_name(index_name)
303
- "#{index_name}_dst"
304
- end
305
-
306
- def default_storage_name(index_name)
307
- "#{index_name}_sto"
308
- end
309
-
310
- def default_index_column_trigger_name(index_name)
311
- "#{index_name}_trg"
312
- end
313
-
314
- def default_lexer_name(index_name)
315
- "#{index_name}_lex"
316
- end
317
-
318
- def default_wordlist_name(index_name)
319
- "#{index_name}_wl"
320
- end
321
-
322
- module BaseClassMethods
323
- # Declare that model table has context index defined.
324
- # As a result <tt>contains</tt> class scope method is defined.
325
- def has_context_index
326
- extend ContextIndexClassMethods
327
- end
328
- end
329
-
330
- module ContextIndexClassMethods
331
- # Add context index condition.
332
- def contains(column, query, options ={})
333
- score_label = options[:label].to_i || 1
334
- where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query).
335
- order("SCORE(#{score_label}) DESC")
336
- end
337
- end
338
-
339
- end
340
-
341
- end
342
- end
343
-
344
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
345
- include ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex
346
- end
347
-
348
- ActiveRecord::Base.class_eval do
349
- extend ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex::BaseClassMethods
350
- end
@@ -1,262 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module OracleEnhancedDatabaseStatements
4
- # DATABASE STATEMENTS ======================================
5
- #
6
- # see: abstract/database_statements.rb
7
-
8
- # Executes a SQL statement
9
- def execute(sql, name = nil)
10
- log(sql, name) { @connection.exec(sql) }
11
- end
12
-
13
- def substitute_at(column, index)
14
- Arel::Nodes::BindParam.new (":a#{index + 1}")
15
- end
16
-
17
- def clear_cache!
18
- @statements.clear
19
- end
20
-
21
- def exec_query(sql, name = 'SQL', binds = [])
22
- type_casted_binds = binds.map { |col, val|
23
- [col, type_cast(val, col)]
24
- }
25
- log(sql, name, type_casted_binds) do
26
- cursor = nil
27
- cached = false
28
- if without_prepared_statement?(binds)
29
- cursor = @connection.prepare(sql)
30
- else
31
- unless @statements.key? sql
32
- @statements[sql] = @connection.prepare(sql)
33
- end
34
-
35
- cursor = @statements[sql]
36
-
37
- binds.each_with_index do |bind, i|
38
- col, val = bind
39
- cursor.bind_param(i + 1, type_cast(val, col), col)
40
- end
41
-
42
- cached = true
43
- end
44
-
45
- cursor.exec
46
-
47
- if name == 'EXPLAIN' and sql =~ /^EXPLAIN/
48
- res = true
49
- else
50
- columns = cursor.get_col_names.map do |col_name|
51
- @connection.oracle_downcase(col_name)
52
- end
53
- rows = []
54
- fetch_options = {:get_lob_value => (name != 'Writable Large Object')}
55
- while row = cursor.fetch(fetch_options)
56
- rows << row
57
- end
58
- res = ActiveRecord::Result.new(columns, rows)
59
- end
60
-
61
- cursor.close unless cached
62
- res
63
- end
64
- end
65
-
66
- def supports_statement_cache?
67
- true
68
- end
69
-
70
- def supports_explain?
71
- true
72
- end
73
-
74
- def explain(arel, binds = [])
75
- sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}"
76
- return if sql =~ /FROM all_/
77
- if ORACLE_ENHANCED_CONNECTION == :jdbc
78
- exec_query(sql, 'EXPLAIN', binds)
79
- else
80
- exec_query(sql, 'EXPLAIN')
81
- end
82
- select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
83
- end
84
-
85
- # Returns an array of arrays containing the field values.
86
- # Order is the same as that returned by #columns.
87
- def select_rows(sql, name = nil, binds = [])
88
- exec_query(sql, name, binds).rows
89
- end
90
-
91
- # Executes an INSERT statement and returns the new record's ID
92
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
93
- # if primary key value is already prefetched from sequence
94
- # or if there is no primary key
95
- if id_value || pk.nil?
96
- execute(sql, name)
97
- return id_value
98
- end
99
-
100
- sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
101
- log(sql, name) do
102
- @connection.exec_with_returning(sql_with_returning)
103
- end
104
- end
105
- protected :insert_sql
106
-
107
- # New method in ActiveRecord 3.1
108
- # Will add RETURNING clause in case of trigger generated primary keys
109
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
110
- unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
111
- sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
112
- returning_id_col = OracleEnhancedColumn.new("returning_id", nil, "number", true, "dual", :integer, true, true)
113
- (binds = binds.dup) << [returning_id_col, nil]
114
- end
115
- [sql, binds]
116
- end
117
-
118
- # New method in ActiveRecord 3.1
119
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
120
- type_casted_binds = binds.map { |col, val|
121
- [col, type_cast(val, col)]
122
- }
123
- log(sql, name, type_casted_binds) do
124
- returning_id_col = returning_id_index = nil
125
- if without_prepared_statement?(binds)
126
- cursor = @connection.prepare(sql)
127
- else
128
- unless @statements.key? (sql)
129
- @statements[sql] = @connection.prepare(sql)
130
- end
131
-
132
- cursor = @statements[sql]
133
-
134
- binds.each_with_index do |bind, i|
135
- col, val = bind
136
- if col.returning_id?
137
- returning_id_col = [col]
138
- returning_id_index = i + 1
139
- cursor.bind_returning_param(returning_id_index, Integer)
140
- else
141
- cursor.bind_param(i + 1, type_cast(val, col), col)
142
- end
143
- end
144
- end
145
-
146
- cursor.exec_update
147
-
148
- rows = []
149
- if returning_id_index
150
- returning_id = cursor.get_returning_param(returning_id_index, Integer)
151
- rows << [returning_id]
152
- end
153
- ActiveRecord::Result.new(returning_id_col || [], rows)
154
- end
155
- end
156
-
157
- # New method in ActiveRecord 3.1
158
- def exec_update(sql, name, binds)
159
- log(sql, name, binds) do
160
- cached = false
161
- if without_prepared_statement?(binds)
162
- cursor = @connection.prepare(sql)
163
- else
164
- cursor = if @statements.key?(sql)
165
- @statements[sql]
166
- else
167
- @statements[sql] = @connection.prepare(sql)
168
- end
169
-
170
- binds.each_with_index do |bind, i|
171
- col, val = bind
172
- cursor.bind_param(i + 1, type_cast(val, col), col)
173
- end
174
- cached = true
175
- end
176
-
177
- res = cursor.exec_update
178
- cursor.close unless cached
179
- res
180
- end
181
- end
182
-
183
- alias :exec_delete :exec_update
184
-
185
- def begin_db_transaction #:nodoc:
186
- @connection.autocommit = false
187
- end
188
-
189
- def transaction_isolation_levels
190
- # Oracle database supports `READ COMMITTED` and `SERIALIZABLE`
191
- # No read uncommitted nor repeatable read supppoted
192
- # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422
193
- {
194
- read_committed: "READ COMMITTED",
195
- serializable: "SERIALIZABLE"
196
- }
197
- end
198
-
199
- def begin_isolated_db_transaction(isolation)
200
- begin_db_transaction
201
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
202
- end
203
-
204
- def commit_db_transaction #:nodoc:
205
- @connection.commit
206
- ensure
207
- @connection.autocommit = true
208
- end
209
-
210
- def rollback_db_transaction #:nodoc:
211
- @connection.rollback
212
- ensure
213
- @connection.autocommit = true
214
- end
215
-
216
- def create_savepoint(name = current_savepoint_name) #:nodoc:
217
- execute("SAVEPOINT #{current_savepoint_name}")
218
- end
219
-
220
- def rollback_to_savepoint(name = current_savepoint_name) #:nodoc:
221
- execute("ROLLBACK TO #{current_savepoint_name}")
222
- end
223
-
224
- def release_savepoint(name = current_savepoint_name) #:nodoc:
225
- # there is no RELEASE SAVEPOINT statement in Oracle
226
- end
227
-
228
- # Returns default sequence name for table.
229
- # Will take all or first 26 characters of table name and append _seq suffix
230
- def default_sequence_name(table_name, primary_key = nil)
231
- table_name.to_s.gsub /(^|\.)([\w$-]{1,#{sequence_name_length-4}})([\w$-]*)$/, '\1\2_seq'
232
- end
233
-
234
- # Inserts the given fixture into the table. Overridden to properly handle lobs.
235
- def insert_fixture(fixture, table_name) #:nodoc:
236
- super
237
-
238
- if ActiveRecord::Base.pluralize_table_names
239
- klass = table_name.to_s.singularize.camelize
240
- else
241
- klass = table_name.to_s.camelize
242
- end
243
-
244
- klass = klass.constantize rescue nil
245
- if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
246
- write_lobs(table_name, klass, fixture, klass.lob_columns)
247
- end
248
- end
249
-
250
- private
251
-
252
- def select(sql, name = nil, binds = [])
253
- exec_query(sql, name, binds)
254
- end
255
-
256
- end
257
- end
258
- end
259
-
260
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
261
- include ActiveRecord::ConnectionAdapters::OracleEnhancedDatabaseStatements
262
- end