22
33ActiveSupport . on_load ( :active_record ) do
44 if System ::Database . oracle?
5+ ActiveRecord ::ConnectionAdapters ::OracleEnhancedAdapter . class_eval do
6+ # https://github.com/rsim/oracle-enhanced/pull/2573
7+ # Perhaps will end-up into adapter version 8.1.4+ but double check
8+ def supports_fetch_first_n_rows_and_offset?
9+ true
10+ end
11+ end
12+
513 require 'arel/visitors/oracle12_hack' || next # once done, we can skip setup
614
715 # in 6.0.6 automatic detection of max identifier length was introduced
@@ -41,7 +49,15 @@ def close_and_clear_statements
4149 end
4250 end
4351
52+ # https://github.com/rsim/oracle-enhanced/pull/2485 enables callbacks
53+ # which are basically redundant for use case we don't have.
54+ # Version 7.1 has only the `update` callbacks so the `create` ones
55+ # need to be uncommented by probably 7.2 upgrade.
56+ # If you uncomment them and things don't fail, then we are good.
4457 ActiveRecord ::Base . skip_callback ( :update , :after , :enhanced_write_lobs )
58+ # ActiveRecord::Base.skip_callback(:create, :after, :enhanced_write_lobs)
59+ ActiveRecord ::Base . skip_callback ( :update , :before , :record_changed_lobs )
60+ # ActiveRecord::Base.skip_callback(:create, :before, :record_lobs_for_create)
4561
4662 # For more information see https://github.com/rsim/oracle-enhanced/pull/2483
4763 module OracleEnhancedSmartQuoting
@@ -159,6 +175,9 @@ def add_column(table_name, column_name, type, **options)
159175 # ar_object.with_lock doesn't work OOB on oracle, see https://github.com/rsim/oracle-enhanced/issues/2237
160176 # A workaround is to avoid using FETCH FIRST when reloading an object by primary key.
161177 # https://github.com/rails/rails/blob/v7.1.5.1/activerecord/lib/active_record/relation/finder_methods.rb#L506
178+ # Might be fixed in 8.1.4+ with
179+ # https://github.com/rsim/oracle-enhanced/pull/2573 and
180+ # https://github.com/rsim/oracle-enhanced/pull/2693
162181 def find_one ( id )
163182 if ActiveRecord ::Base === id
164183 raise ArgumentError , <<-MSG . squish
@@ -184,153 +203,6 @@ def find_one(id)
184203 end
185204 end )
186205
187- BabySqueel ::Nodes ::Attribute . prepend ( Module . new do
188- # those relations are used in subqueries and oracle does not support ORDER in subqueries
189- private def sanitize_relation ( rel )
190- super rel . unscope ( :order )
191- end
192- end )
193-
194- ThinkingSphinx ::ActiveRecord ::SQLSource . prepend ( Module . new do
195- # If the Adapter is Oracle then we forcibly use ODBC client
196- def type
197- @type ||= case adapter
198- when ThinkingSphinx ::ActiveRecord ::DatabaseAdapters ::OracleAdapter
199- 'odbc'
200- else
201- super
202- end
203- end
204- end )
205-
206- ThinkingSphinx ::ActiveRecord ::DatabaseAdapters . module_eval do
207- class << self
208- prepend ( Module . new do
209- # https://github.com/pat/thinking-sphinx/blob/v3.2.0/lib/thinking_sphinx/active_record/database_adapters.rb#L35-L45
210- # Adding a new DatabaseAdapters for ThinkingSphinx
211- # This adds support for Oracle. OracleAdapter is responsible of query generation for Sphinx
212- def adapter_for ( model )
213- return default . new ( model ) if default
214-
215- adapter = adapter_type_for ( model )
216- klass = case adapter
217- when :oracle
218- ThinkingSphinx ::ActiveRecord ::DatabaseAdapters ::OracleAdapter
219- else
220- super
221- end
222- klass . new model
223- end
224-
225- # https://github.com/pat/thinking-sphinx/blob/v3.2.0/lib/thinking_sphinx/active_record/database_adapters.rb#L21-L33
226- # This method is only needed for `adapter_for`
227- # Part of freedom patch for ThinkingSphinx handling with Oracle
228- def adapter_type_for ( model )
229- class_name = model . connection . class . name
230- case class_name . split ( '::' ) . last
231- when /oracle/i
232- :oracle
233- else
234- super
235- end
236- end
237- end
238- )
239- end
240- end
241-
242- ThinkingSphinx ::Deltas ::DatetimeDelta . prepend ( Module . new do
243- # https://github.com/pat/ts-datetime-delta/blob/v2.0.2/lib/thinking_sphinx/deltas/datetime_delta.rb#L167
244- # A SQL condition (as part of the WHERE clause) that limits the result set to
245- # just the delta data, or all data, depending on whether the toggled argument
246- # is true or not. For datetime deltas, the former value is a check on the
247- # delta column being within the threshold. In the latter's case, no condition
248- # is needed, so nil is returned.
249- def clause ( *args )
250- model = ( args . length >= 2 ? args [ 0 ] : nil )
251- is_delta = ( args . length >= 2 ? args [ 1 ] : args [ 0 ] ) || false
252-
253- table_name = ( model . nil? ? adapter . quoted_table_name : model . quoted_table_name )
254- column_name = ( model . nil? ? adapter . quote ( @column . to_s ) : model . connection . quote_column_name ( @column . to_s ) )
255-
256- if is_delta
257- if adapter . class . name . downcase [ /oracle/ ]
258- "EXTRACT( day from ((#{ table_name } .#{ column_name } - SYSDATE) * 60 * 60 * 24)) + #{ @threshold } > 0"
259- else
260- super
261- end
262- else
263- nil
264- end
265- end
266- end )
267-
268- ThinkingSphinx ::ActiveRecord ::SQLSource . prepend ( Module . new do
269- # This autogenerate the odbc_dsn string based on database.yml
270- # For now it is only particular to our project, later we could extract it and make a PR to the upstream project
271- def set_database_settings ( settings )
272- super
273- conf = System ::Database . database_config . configuration_hash
274- if type == 'odbc'
275- @odbc_dsn ||= "DSN=oracle;Driver={Oracle-Driver};Dbq=#{ conf [ :host ] } :#{ conf [ :port ] || 1521 } /#{ conf [ :database ] } ;Uid=#{ conf [ :username ] } ;Pwd=#{ conf [ :password ] } "
276- end
277- end
278- end )
279-
280- ActiveRecord ::ConnectionAdapters ::OracleEnhanced ::SchemaStatements . module_eval do
281- def add_index ( table_name , column_name , **options ) #:nodoc:
282- # All this code is exactly the same as the original except the line of the ALTER TABLE, which adds an additional USING INDEX #{quote_column_name(index_name)}
283- # The reason of this is otherwise it picks the first index that finds that contains that column name, even if it is shared with other columns and it is not unique.
284- # upstreamed: https://github.com/rsim/oracle-enhanced/pull/2293
285- index_name , index_type , quoted_column_names , tablespace , index_options = add_index_options ( table_name , column_name , **options )
286- quoted_table_name = quote_table_name ( table_name )
287- quoted_column_name = quote_column_name ( index_name )
288- execute "CREATE #{ index_type } INDEX #{ quoted_column_name } ON #{ quoted_table_name } (#{ quoted_column_names } )#{ tablespace } #{ index_options } "
289- if index_type == 'UNIQUE' && quoted_column_names !~ /\( .*\) /
290- execute "ALTER TABLE #{ quoted_table_name } ADD CONSTRAINT #{ quoted_column_name } #{ index_type } (#{ quoted_column_names } ) USING INDEX #{ quoted_column_name } "
291- end
292- end
293-
294- def distinct_relation_for_primary_key ( relation ) # :nodoc:
295- primary_key_columns = Array ( relation . primary_key ) . map do |column |
296- visitor . compile ( relation . table [ column ] )
297- end
298-
299- values = columns_for_distinct (
300- primary_key_columns ,
301- relation . order_values
302- )
303-
304- limited = relation . reselect ( values ) . distinct!
305-
306- # The original code in https://github.com/rails/rails/blob/v7.1.5.1/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L1404-L1406
307- # ----
308- # limited_ids = select_rows(limited.arel, "SQL").map do |results|
309- # results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL
310- # end
311- # ----
312- # The change is needed because in Oracle, because otherwise the resulting `limited_ids` array would be wrong. For example,
313- # for a ServiceContract model that has the primary key "id" and a query with ordering and filtering you may get:
314- # 1. `limited` variable can be something like: #<ActiveRecord::Relation [#<ServiceContract alias_0__: "live", id: 4, raw_rnum_: 1>]>
315- # 2. `select_rows` would then return [["live",4,1]]
316- # 3. `limited_ids` calculation will result in [[1]], which is an invalid IDs list (the expected is [[4]])
317- # The updated code doesn't make assumptions about how many columns are selected or their order, but fetches the values according
318- # to the primary key column names
319- limited_ids = select_all ( limited . arel , "SQL" ) . to_ary . map do |result |
320- result . values_at ( *Array ( relation . primary_key ) )
321- end
322-
323- if limited_ids . empty?
324- relation . none!
325- else
326- relation . where! ( **Array ( relation . primary_key ) . zip ( limited_ids . transpose ) . to_h )
327- end
328-
329- relation . limit_value = relation . offset_value = nil
330- relation
331- end
332- end
333-
334206 # see https://github.com/rsim/oracle-enhanced/issues/2276
335207 module OracleEnhancedAdapterSchemaIssue2276
336208 def column_definitions ( table_name )
0 commit comments