Module: ArJdbc::Oracle

Defined in:
lib/arjdbc/oracle/adapter.rb

Defined Under Namespace

Modules: Column

Constant Summary collapse

@@_lob_callback_added =
nil

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.arel2_visitors(config) ⇒ Object



148
149
150
# File 'lib/arjdbc/oracle/adapter.rb', line 148

def self.arel2_visitors(config)
  { 'oracle' => Arel::Visitors::Oracle }
end

.column_selectorObject



42
43
44
# File 'lib/arjdbc/oracle/adapter.rb', line 42

def self.column_selector
  [ /oracle/i, lambda { |cfg, column| column.extend(::ArJdbc::Oracle::Column) } ]
end

.extended(mod) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/arjdbc/oracle/adapter.rb', line 8

def self.extended(mod)
  unless @@_lob_callback_added
    ActiveRecord::Base.class_eval do
      def after_save_with_oracle_lob
        self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each do |column|
          value = ::ArJdbc::SerializedAttributesHelper.dump_column_value(self, column)
          next if value.nil? || (value == '')

          connection.write_large_object(
            column.type == :binary, column.name, 
            self.class.table_name, self.class.primary_key, 
            quote_value(id), value
          )
        end
      end
    end

    ActiveRecord::Base.after_save :after_save_with_oracle_lob
    
    @@_lob_callback_added = true
  end

  unless ActiveRecord::ConnectionAdapters::AbstractAdapter.
      instance_methods(false).detect { |m| m.to_s == "prefetch_primary_key?" }
    require 'arjdbc/jdbc/quoted_primary_key'
    ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
  end
  
  (class << mod; self; end).class_eval do
    alias_chained_method :insert, :query_dirty, :ora_insert
    alias_chained_method :columns, :query_cache, :ora_columns
  end
end

.jdbc_connection_classObject



46
47
48
# File 'lib/arjdbc/oracle/adapter.rb', line 46

def self.jdbc_connection_class
  ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
end

Instance Method Details

#adapter_nameObject



144
145
146
# File 'lib/arjdbc/oracle/adapter.rb', line 144

def adapter_name
  'Oracle'
end

#add_column_options!(sql, options) ⇒ Object

:nodoc:



313
314
315
316
317
318
319
# File 'lib/arjdbc/oracle/adapter.rb', line 313

def add_column_options!(sql, options) #:nodoc:
  # handle case  of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
  if options_include_default?(options) && (column = options[:column]) && column.type == :text
    sql << " DEFAULT #{quote(options.delete(:default))}"
  end
  super
end

#add_limit_offset!(sql, options) ⇒ Object

:nodoc:



266
267
268
269
270
271
272
273
274
# File 'lib/arjdbc/oracle/adapter.rb', line 266

def add_limit_offset!(sql, options) #:nodoc:
  offset = options[:offset] || 0

  if limit = options[:limit]
    sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
  elsif offset > 0
    sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
  end
end

#add_order_by_for_association_limiting!(sql, options) ⇒ Object

ORDER BY clause for the passed order option.

Uses column aliases as defined by #distinct.



406
407
408
409
410
411
412
413
414
415
# File 'lib/arjdbc/oracle/adapter.rb', line 406

def add_order_by_for_association_limiting!(sql, options)
  return sql if options[:order].blank?

  order_columns = extract_order_columns(options[:order]) do |columns|
    columns.map! { |s| $1 if s =~ / (.*)/ }; columns
  end
  order = order_columns.map { |s, i| "alias_#{i}__ #{s}" } # @see {#distinct}

  sql << "ORDER BY #{order.join(', ')}"
end

#change_column(table_name, column_name, type, options = {}) ⇒ Object

:nodoc:



321
322
323
324
325
326
# File 'lib/arjdbc/oracle/adapter.rb', line 321

def change_column(table_name, column_name, type, options = {}) #:nodoc:
  change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} " <<
    "MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}"
  add_column_options!(change_column_sql, options)
  execute(change_column_sql)
end

#change_column_default(table_name, column_name, default) ⇒ Object

:nodoc:



308
309
310
311
# File 'lib/arjdbc/oracle/adapter.rb', line 308

def change_column_default(table_name, column_name, default) #:nodoc:
  execute "ALTER TABLE #{quote_table_name(table_name)} " + 
    "MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
end

#column_name_lengthObject

:nodoc:



166
# File 'lib/arjdbc/oracle/adapter.rb', line 166

def column_name_length; 30; end

#create_savepointObject

:nodoc:



292
293
294
# File 'lib/arjdbc/oracle/adapter.rb', line 292

def create_savepoint # :nodoc:
  execute("SAVEPOINT #{current_savepoint_name}")
end

#create_table(name, options = {}) ⇒ Object

:nodoc:

Raises:

  • (ActiveRecord::StatementInvalid)


172
173
174
175
176
177
178
# File 'lib/arjdbc/oracle/adapter.rb', line 172

def create_table(name, options = {}) #:nodoc:
  super(name, options)
  seq_name = options[:sequence_name] || default_sequence_name(name)
  start_value = options[:sequence_start_value] || 10000
  raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
  execute "CREATE SEQUENCE #{seq_name} START WITH #{start_value}" unless options[:id] == false
end

#current_databaseObject

:nodoc:



280
281
282
# File 'lib/arjdbc/oracle/adapter.rb', line 280

def current_database # :nodoc:
  @current_database ||= execute("SELECT sys_context('userenv', 'db_name') db FROM dual").first['db']
end

#current_schemaObject

:nodoc:



284
285
286
# File 'lib/arjdbc/oracle/adapter.rb', line 284

def current_schema # :nodoc:
  execute("SELECT sys_context('userenv', 'current_schema') schema FROM dual").first['schema']
end

#current_schema=(schema_owner) ⇒ Object



288
289
290
# File 'lib/arjdbc/oracle/adapter.rb', line 288

def current_schema=(schema_owner)
  execute("ALTER SESSION SET current_schema=#{schema_owner}")
end

#current_userObject

:nodoc:



276
277
278
# File 'lib/arjdbc/oracle/adapter.rb', line 276

def current_user # :nodoc:
  @current_user ||= execute("SELECT sys_context('userenv', 'session_user') su FROM dual").first['su']
end

#default_sequence_name(table, column = nil) ⇒ Object

:nodoc:



168
169
170
# File 'lib/arjdbc/oracle/adapter.rb', line 168

def default_sequence_name(table, column = nil) #:nodoc:
  "#{table}_seq"
end

#distinct(columns, order_by) ⇒ Object

SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.

Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT queries. However, with those columns included in the SELECT DISTINCT list, you won’t actually get a distinct list of the column you want (presuming the column has duplicates with multiple values for the ordered-by columns. So we use the FIRST_VALUE function to get a single (first) value for each column, effectively making every row the same.

distinct("posts.id", "posts.created_at desc")


391
392
393
394
395
396
397
398
399
400
401
# File 'lib/arjdbc/oracle/adapter.rb', line 391

def distinct(columns, order_by)
  return "DISTINCT #{columns}" if order_by.blank?

  # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
  # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
  order_columns = extract_order_columns(order_by).map do |c, i|
    "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
  end
  sql = "DISTINCT #{columns}, "
  sql << order_columns * ", "
end

#drop_database(name) ⇒ Object



195
196
197
# File 'lib/arjdbc/oracle/adapter.rb', line 195

def drop_database(name)
  recreate_database(name)
end

#drop_table(name, options = {}) ⇒ Object

:nodoc:



185
186
187
188
189
# File 'lib/arjdbc/oracle/adapter.rb', line 185

def drop_table(name, options = {}) #:nodoc:
  super(name) rescue nil
  seq_name = options[:sequence_name] || default_sequence_name(name)
  execute "DROP SEQUENCE #{seq_name}" rescue nil
end

#explain(arel, binds = []) ⇒ Object



493
494
495
496
497
498
# File 'lib/arjdbc/oracle/adapter.rb', line 493

def explain(arel, binds = [])
  sql = "EXPLAIN PLAN FOR #{to_sql(arel)}"
  return if sql =~ /FROM all_/
  exec_query(sql, 'EXPLAIN', binds)
  select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
end

#in_clause_lengthObject Also known as: ids_in_list_limit

Prevent ORA-01795 for in clauses with more than 1000



157
158
159
# File 'lib/arjdbc/oracle/adapter.rb', line 157

def in_clause_length # :nodoc:
  1000
end

#index_name_lengthObject

:nodoc:



165
# File 'lib/arjdbc/oracle/adapter.rb', line 165

def index_name_length;  30; end

#indexes(table, name = nil) ⇒ Object



251
252
253
# File 'lib/arjdbc/oracle/adapter.rb', line 251

def indexes(table, name = nil)
  @connection.indexes(table, name, @connection.connection..user_name)
end

#jdbc_column_classObject



50
51
52
# File 'lib/arjdbc/oracle/adapter.rb', line 50

def jdbc_column_class
  ::ActiveRecord::ConnectionAdapters::OracleColumn
end

#modify_types(types) ⇒ Object



255
256
257
258
259
260
261
262
263
264
# File 'lib/arjdbc/oracle/adapter.rb', line 255

def modify_types(types)
  super(types)
  types[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
  types[:integer] = { :name => "NUMBER", :limit => 38 }
  types[:datetime] = { :name => "DATE" }
  types[:timestamp] = { :name => "TIMESTAMP" }
  types[:time] = { :name => "DATE" }
  types[:date] = { :name => "DATE" }
  types
end

#next_sequence_value(sequence_name) ⇒ Object



223
224
225
226
# File 'lib/arjdbc/oracle/adapter.rb', line 223

def next_sequence_value(sequence_name)
  # avoid #select or #select_one so that the sequence values aren't cached
  execute("SELECT #{quote_table_name(sequence_name)}.nextval id FROM dual").first['id'].to_i
end

#ora_columns(table_name, name = nil) ⇒ Object

NOTE: better to use current_schema instead of the configured one ?!



431
432
433
# File 'lib/arjdbc/oracle/adapter.rb', line 431

def ora_columns(table_name, name = nil)
  @connection.columns_internal(table_name.to_s, name, oracle_schema)
end

#ora_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) ⇒ Object

:nodoc:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/arjdbc/oracle/adapter.rb', line 232

def ora_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) #:nodoc:
  if (id_value && ! sql_literal?(id_value)) || pk.nil?
    # Pre-assigned id or table without a primary key
    # Presence of #to_sql means an Arel literal bind variable
    # that should use #execute_id_insert below
    execute sql, name, binds
  else
    # Assume the sql contains a bind-variable for the id
    # Extract the table from the insert sql. Yuck.
    table = sql.split(" ", 4)[2].gsub('"', '')
    sequence_name ||= default_sequence_name(table)
    id_value = next_sequence_value(sequence_name)
    log(sql, name) do
      @connection.execute_id_insert(sql, id_value)
    end
  end
  id_value
end

#prefetch_primary_key?(table_name = nil) ⇒ Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/arjdbc/oracle/adapter.rb', line 152

def prefetch_primary_key?(table_name = nil)
  columns(table_name).detect {|c| c.primary } if table_name
end

#quote(value, column = nil) ⇒ Object

:nodoc:



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/arjdbc/oracle/adapter.rb', line 453

def quote(value, column = nil) # :nodoc:
  # Arel 2 passes SqlLiterals through
  return value if sql_literal?(value)

  if column && [:text, :binary].include?(column.type)
    if /(.*?)\([0-9]+\)/ =~ column.sql_type
      %Q{empty_#{ $1.downcase }()}
    else
      %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
    end
  else
    if column.respond_to?(:primary) && column.primary && column.klass != String
      return value.to_i.to_s
    end
    quoted = super
    if value.acts_like?(:date)
      quoted = %Q{DATE'#{quoted_date(value)}'}
    elsif value.acts_like?(:time)
      quoted = %Q{TIMESTAMP'#{quoted_date(value)}'}
    end
    quoted
  end
end

#quote_column_name(name) ⇒ Object

Camelcase column names need to be quoted. Nonquoted identifiers can contain only alphanumeric characters from your database character set and the underscore (_), dollar sign ($), and pound sign (#). Database links can also contain periods (.) and β€œat” signs (@). Oracle strongly discourages you from using $ and # in nonquoted identifiers. Source: download.oracle.com/docs/cd/B28359_01/server.111/b28286/sql_elements008.htm



449
450
451
# File 'lib/arjdbc/oracle/adapter.rb', line 449

def quote_column_name(name) #:nodoc:
  name.to_s =~ /^[a-z0-9_$#]+$/ ? name.to_s : "\"#{name}\""
end

#quote_table_name(name) ⇒ Object

See ACTIVERECORD_JDBC-33 for details – better to not quote table names, esp. if they have schemas.



439
440
441
# File 'lib/arjdbc/oracle/adapter.rb', line 439

def quote_table_name(name) # :nodoc:
  name.to_s
end

#recreate_database(name, options = {}) ⇒ Object



191
192
193
# File 'lib/arjdbc/oracle/adapter.rb', line 191

def recreate_database(name, options = {})
  tables.each{ |table| drop_table(table) }
end

#release_savepointObject

:nodoc:



300
301
302
# File 'lib/arjdbc/oracle/adapter.rb', line 300

def release_savepoint # :nodoc:
  # no RELEASE SAVEPOINT statement in Oracle
end

#remove_column(table_name, *column_names) ⇒ Object

:nodoc:



333
334
335
336
337
# File 'lib/arjdbc/oracle/adapter.rb', line 333

def remove_column(table_name, *column_names) #:nodoc:
  for column_name in column_names.flatten
    execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
  end
end

#remove_index(table_name, options = {}) ⇒ Object

:nodoc:



304
305
306
# File 'lib/arjdbc/oracle/adapter.rb', line 304

def remove_index(table_name, options = {}) #:nodoc:
  execute "DROP INDEX #{index_name(table_name, options)}"
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object

:nodoc:



328
329
330
331
# File 'lib/arjdbc/oracle/adapter.rb', line 328

def rename_column(table_name, column_name, new_column_name) #:nodoc:
  execute "ALTER TABLE #{quote_table_name(table_name)} " <<
    "RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
end

#rename_table(name, new_name) ⇒ Object

:nodoc:



180
181
182
183
# File 'lib/arjdbc/oracle/adapter.rb', line 180

def rename_table(name, new_name) #:nodoc:
  execute "RENAME #{name} TO #{new_name}"
  execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
end

#rollback_to_savepointObject

:nodoc:



296
297
298
# File 'lib/arjdbc/oracle/adapter.rb', line 296

def rollback_to_savepoint # :nodoc:
  execute("ROLLBACK TO #{current_savepoint_name}")
end

#select(sql, name = nil, binds = []) ⇒ Object



500
501
502
503
504
505
506
# File 'lib/arjdbc/oracle/adapter.rb', line 500

def select(sql, name = nil, binds = [])
  records = execute(sql, name, binds)
  for column in records
    column.delete('raw_rnum_')
  end
  records
end

#sql_literal?(value) ⇒ Boolean

Returns:

  • (Boolean)


228
229
230
# File 'lib/arjdbc/oracle/adapter.rb', line 228

def sql_literal?(value)
  defined?(::Arel::SqlLiteral) && ::Arel::SqlLiteral === value
end

#structure_dropObject

:nodoc:



371
372
373
374
375
376
377
378
379
# File 'lib/arjdbc/oracle/adapter.rb', line 371

def structure_drop #:nodoc:
  s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
    drop << "drop sequence #{seq.to_a.first.last};\n\n"
  end

  select_all("select table_name from user_tables").inject(s) do |drop, table|
    drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
  end
end

#structure_dumpObject

:nodoc:



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/arjdbc/oracle/adapter.rb', line 339

def structure_dump #:nodoc:
  s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
    structure << "create sequence #{seq.to_a.first.last};\n\n"
  end

  select_all("select table_name from user_tables").inject(s) do |structure, table|
    ddl = "create table #{table.to_a.first.last} (\n "
    cols = select_all(%Q{
      select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
      from user_tab_columns
      where table_name = '#{table.to_a.first.last}'
      order by column_id
    }).map do |row|
      row = row.inject({}) { |h, args| h[ args[0].downcase ] = args[1]; h }
      col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
      if row['data_type'] == 'NUMBER' and ! row['data_precision'].nil?
        col << "(#{row['data_precision'].to_i}"
        col << ",#{row['data_scale'].to_i}" if ! row['data_scale'].nil?
        col << ')'
      elsif row['data_type'].include?('CHAR')
        col << "(#{row['data_length'].to_i})"
      end
      col << " default #{row['data_default']}" if !row['data_default'].nil?
      col << ' not null' if row['nullable'] == 'N'
      col
    end
    ddl << cols.join(",\n ")
    ddl << ");\n\n"
    structure << ddl
  end
end

#supports_explain?Boolean

:nodoc:

Returns:

  • (Boolean)


489
490
491
# File 'lib/arjdbc/oracle/adapter.rb', line 489

def supports_explain? # :nodoc:
  true
end

#supports_migrations?Boolean

:nodoc:

Returns:

  • (Boolean)


477
478
479
# File 'lib/arjdbc/oracle/adapter.rb', line 477

def supports_migrations? # :nodoc:
  true
end

#supports_primary_key?Boolean

:nodoc:

Returns:

  • (Boolean)


481
482
483
# File 'lib/arjdbc/oracle/adapter.rb', line 481

def supports_primary_key? # :nodoc:
  true
end

#supports_savepoints?Boolean

:nodoc:

Returns:

  • (Boolean)


485
486
487
# File 'lib/arjdbc/oracle/adapter.rb', line 485

def supports_savepoints? # :nodoc:
  true
end

#table_alias_lengthObject

maximum length of Oracle identifiers is 30



163
# File 'lib/arjdbc/oracle/adapter.rb', line 163

def table_alias_length; 30; end

#table_name_lengthObject

:nodoc:



164
# File 'lib/arjdbc/oracle/adapter.rb', line 164

def table_name_length;  30; end

#tablesObject



425
426
427
# File 'lib/arjdbc/oracle/adapter.rb', line 425

def tables
  @connection.tables(nil, oracle_schema)
end

#type_to_sql(type, limit = nil, precision = nil, scale = nil) ⇒ Object

:nodoc:



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/arjdbc/oracle/adapter.rb', line 199

def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
  case type.to_sym
  when :binary
    # { BLOB | BINARY LARGE OBJECT } [ ( length [{K |M |G }] ) ]
    # although Oracle does not like limit (length) with BLOB (or CLOB) :
    # 
    # CREATE TABLE binaries (data BLOB, short_data BLOB(1024));
    # ORA-00907: missing right parenthesis             *
    #
    # TODO do we need to worry about NORMAL vs. non IN-TABLE BLOBs ?!
    # http://dba.stackexchange.com/questions/8770/improve-blob-writing-performance-in-oracle-11g
    # - if the LOB is smaller than 3900 bytes it can be stored inside the 
    #   table row; by default this is enabled, 
    #   unless you specify DISABLE STORAGE IN ROW
    # - normal LOB - stored in a separate segment, outside of table, 
    #   you may even put it in another tablespace;
    super(type, nil, nil, nil)
  when :text
    super(type, nil, nil, nil)
  else
    super
  end
end