| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
require 'active_record/connection_adapters/abstract_adapter' |
|---|
| 26 |
require 'delegate' |
|---|
| 27 |
|
|---|
| 28 |
begin |
|---|
| 29 |
require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8 |
|---|
| 30 |
|
|---|
| 31 |
module ActiveRecord |
|---|
| 32 |
class Base |
|---|
| 33 |
def self.oracle_connection(config) |
|---|
| 34 |
|
|---|
| 35 |
ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger |
|---|
| 36 |
end |
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
def self.oci_connection(config) |
|---|
| 40 |
config[:database] = config[:host] |
|---|
| 41 |
self.oracle_connection(config) |
|---|
| 42 |
end |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 |
after_save :write_lobs |
|---|
| 47 |
def write_lobs |
|---|
| 48 |
if connection.is_a?(ConnectionAdapters::OracleAdapter) |
|---|
| 49 |
connection.write_lobs(self.class.table_name, self.class, attributes) |
|---|
| 50 |
end |
|---|
| 51 |
end |
|---|
| 52 |
|
|---|
| 53 |
private :write_lobs |
|---|
| 54 |
end |
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 |
module ConnectionAdapters |
|---|
| 58 |
class OracleColumn < Column |
|---|
| 59 |
|
|---|
| 60 |
def type_cast(value) |
|---|
| 61 |
return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates |
|---|
| 62 |
super |
|---|
| 63 |
end |
|---|
| 64 |
|
|---|
| 65 |
private |
|---|
| 66 |
def simplified_type(field_type) |
|---|
| 67 |
return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)' |
|---|
| 68 |
case field_type |
|---|
| 69 |
when /date|time/i then :datetime |
|---|
| 70 |
else super |
|---|
| 71 |
end |
|---|
| 72 |
end |
|---|
| 73 |
|
|---|
| 74 |
def guess_date_or_time(value) |
|---|
| 75 |
(value.hour == 0 and value.min == 0 and value.sec == 0) ? |
|---|
| 76 |
Date.new(value.year, value.month, value.day) : value |
|---|
| 77 |
end |
|---|
| 78 |
end |
|---|
| 79 |
|
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 |
|
|---|
| 83 |
|
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 |
|
|---|
| 87 |
|
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 |
|
|---|
| 91 |
|
|---|
| 92 |
|
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 |
|
|---|
| 96 |
|
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 |
|
|---|
| 102 |
|
|---|
| 103 |
|
|---|
| 104 |
|
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
|
|---|
| 108 |
|
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
class OracleAdapter < AbstractAdapter |
|---|
| 114 |
|
|---|
| 115 |
@@emulate_booleans = true |
|---|
| 116 |
cattr_accessor :emulate_booleans |
|---|
| 117 |
|
|---|
| 118 |
@@emulate_dates = false |
|---|
| 119 |
cattr_accessor :emulate_dates |
|---|
| 120 |
|
|---|
| 121 |
def adapter_name |
|---|
| 122 |
'Oracle' |
|---|
| 123 |
end |
|---|
| 124 |
|
|---|
| 125 |
def supports_migrations? |
|---|
| 126 |
true |
|---|
| 127 |
end |
|---|
| 128 |
|
|---|
| 129 |
def native_database_types |
|---|
| 130 |
{ |
|---|
| 131 |
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY", |
|---|
| 132 |
:string => { :name => "VARCHAR2", :limit => 255 }, |
|---|
| 133 |
:text => { :name => "CLOB" }, |
|---|
| 134 |
:integer => { :name => "NUMBER", :limit => 38 }, |
|---|
| 135 |
:float => { :name => "NUMBER" }, |
|---|
| 136 |
:decimal => { :name => "DECIMAL" }, |
|---|
| 137 |
:datetime => { :name => "DATE" }, |
|---|
| 138 |
:timestamp => { :name => "DATE" }, |
|---|
| 139 |
:time => { :name => "DATE" }, |
|---|
| 140 |
:date => { :name => "DATE" }, |
|---|
| 141 |
:binary => { :name => "BLOB" }, |
|---|
| 142 |
:boolean => { :name => "NUMBER", :limit => 1 } |
|---|
| 143 |
} |
|---|
| 144 |
end |
|---|
| 145 |
|
|---|
| 146 |
def table_alias_length |
|---|
| 147 |
30 |
|---|
| 148 |
end |
|---|
| 149 |
|
|---|
| 150 |
|
|---|
| 151 |
|
|---|
| 152 |
def select_rows(sql, name = nil) |
|---|
| 153 |
result = select(sql, name) |
|---|
| 154 |
result.map{ |v| v.values} |
|---|
| 155 |
end |
|---|
| 156 |
|
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 |
|
|---|
| 160 |
|
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
def quote_column_name(name) |
|---|
| 164 |
name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : name |
|---|
| 165 |
end |
|---|
| 166 |
|
|---|
| 167 |
def quote_string(s) |
|---|
| 168 |
s.gsub(/'/, "''") |
|---|
| 169 |
end |
|---|
| 170 |
|
|---|
| 171 |
def quote(value, column = nil) |
|---|
| 172 |
if value && column && [:text, :binary].include?(column.type) |
|---|
| 173 |
%Q{empty_ |
|---|
| 174 |
else |
|---|
| 175 |
super |
|---|
| 176 |
end |
|---|
| 177 |
end |
|---|
| 178 |
|
|---|
| 179 |
def quoted_true |
|---|
| 180 |
"1" |
|---|
| 181 |
end |
|---|
| 182 |
|
|---|
| 183 |
def quoted_false |
|---|
| 184 |
"0" |
|---|
| 185 |
end |
|---|
| 186 |
|
|---|
| 187 |
|
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 192 |
def active? |
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
|
|---|
| 196 |
|
|---|
| 197 |
@connection.ping |
|---|
| 198 |
rescue OCIException |
|---|
| 199 |
false |
|---|
| 200 |
end |
|---|
| 201 |
|
|---|
| 202 |
|
|---|
| 203 |
def reconnect! |
|---|
| 204 |
@connection.reset! |
|---|
| 205 |
rescue OCIException => e |
|---|
| 206 |
@logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" |
|---|
| 207 |
end |
|---|
| 208 |
|
|---|
| 209 |
|
|---|
| 210 |
def disconnect! |
|---|
| 211 |
@connection.logoff rescue nil |
|---|
| 212 |
@connection.active = false |
|---|
| 213 |
end |
|---|
| 214 |
|
|---|
| 215 |
|
|---|
| 216 |
|
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 |
|
|---|
| 220 |
def execute(sql, name = nil) |
|---|
| 221 |
log(sql, name) { @connection.exec sql } |
|---|
| 222 |
end |
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 |
|
|---|
| 226 |
|
|---|
| 227 |
def next_sequence_value(sequence_name) |
|---|
| 228 |
id = 0 |
|---|
| 229 |
@connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i } |
|---|
| 230 |
id |
|---|
| 231 |
end |
|---|
| 232 |
|
|---|
| 233 |
def begin_db_transaction |
|---|
| 234 |
@connection.autocommit = false |
|---|
| 235 |
end |
|---|
| 236 |
|
|---|
| 237 |
def commit_db_transaction |
|---|
| 238 |
@connection.commit |
|---|
| 239 |
ensure |
|---|
| 240 |
@connection.autocommit = true |
|---|
| 241 |
end |
|---|
| 242 |
|
|---|
| 243 |
def rollback_db_transaction |
|---|
| 244 |
@connection.rollback |
|---|
| 245 |
ensure |
|---|
| 246 |
@connection.autocommit = true |
|---|
| 247 |
end |
|---|
| 248 |
|
|---|
| 249 |
def add_limit_offset!(sql, options) |
|---|
| 250 |
offset = options[:offset] || 0 |
|---|
| 251 |
|
|---|
| 252 |
if limit = options[:limit] |
|---|
| 253 |
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}" |
|---|
| 254 |
elsif offset > 0 |
|---|
| 255 |
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}" |
|---|
| 256 |
end |
|---|
| 257 |
end |
|---|
| 258 |
|
|---|
| 259 |
|
|---|
| 260 |
|
|---|
| 261 |
def prefetch_primary_key?(table_name = nil) |
|---|
| 262 |
true |
|---|
| 263 |
end |
|---|
| 264 |
|
|---|
| 265 |
def default_sequence_name(table, column) |
|---|
| 266 |
"#{table}_seq" |
|---|
| 267 |
end |
|---|
| 268 |
|
|---|
| 269 |
|
|---|
| 270 |
|
|---|
| 271 |
def insert_fixture(fixture, table_name) |
|---|
| 272 |
super |
|---|
| 273 |
|
|---|
| 274 |
klass = fixture.class_name.constantize rescue nil |
|---|
| 275 |
if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base) |
|---|
| 276 |
write_lobs(table_name, klass, fixture) |
|---|
| 277 |
end |
|---|
| 278 |
end |
|---|
| 279 |
|
|---|
| 280 |
|
|---|
| 281 |
def write_lobs(table_name, klass, attributes) |
|---|
| 282 |
id = quote(attributes[klass.primary_key]) |
|---|
| 283 |
klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col| |
|---|
| 284 |
value = attributes[col.name] |
|---|
| 285 |
value = value.to_yaml if col.text? && klass.serialized_attributes[col.name] |
|---|
| 286 |
next if value.nil? || (value == '') |
|---|
| 287 |
lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id}", |
|---|
| 288 |
'Writable Large Object')[col.name] |
|---|
| 289 |
lob.write value |
|---|
| 290 |
end |
|---|
| 291 |
end |
|---|
| 292 |
|
|---|
| 293 |
|
|---|
| 294 |
|
|---|
| 295 |
|
|---|
| 296 |
|
|---|
| 297 |
|
|---|
| 298 |
def current_database |
|---|
| 299 |
select_one("select sys_context('userenv','db_name') db from dual")["db"] |
|---|
| 300 |
end |
|---|
| 301 |
|
|---|
| 302 |
def tables(name = nil) |
|---|
| 303 |
select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t | |
|---|
| 304 |
tabs << t.to_a.first.last |
|---|
| 305 |
end |
|---|
| 306 |
end |
|---|
| 307 |
|
|---|
| 308 |
def indexes(table_name, name = nil) |
|---|
| 309 |
result = select_all(<<-SQL, name) |
|---|
| 310 |
SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name |
|---|
| 311 |
FROM user_indexes i, user_ind_columns c |
|---|
| 312 |
WHERE i.table_name = '#{table_name.to_s.upcase}' |
|---|
| 313 |
AND c.index_name = i.index_name |
|---|
| 314 |
AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P') |
|---|
| 315 |
ORDER BY i.index_name, c.column_position |
|---|
| 316 |
SQL |
|---|
| 317 |
|
|---|
| 318 |
current_index = nil |
|---|
| 319 |
indexes = [] |
|---|
| 320 |
|
|---|
| 321 |
result.each do |row| |
|---|
| 322 |
if current_index != row['index_name'] |
|---|
| 323 |
indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", []) |
|---|
| 324 |
current_index = row['index_name'] |
|---|
| 325 |
end |
|---|
| 326 |
|
|---|
| 327 |
indexes.last.columns << row['column_name'] |
|---|
| 328 |
end |
|---|
| 329 |
|
|---|
| 330 |
indexes |
|---|
| 331 |
end |
|---|
| 332 |
|
|---|
| 333 |
def columns(table_name, name = nil) |
|---|
| 334 |
(owner, table_name) = @connection.describe(table_name) |
|---|
| 335 |
|
|---|
| 336 |
table_cols = <<-SQL |
|---|
| 337 |
select column_name as name, data_type as sql_type, data_default, nullable, |
|---|
| 338 |
decode(data_type, 'NUMBER', data_precision, |
|---|
| 339 |
'FLOAT', data_precision, |
|---|
| 340 |
'VARCHAR2', data_length, |
|---|
| 341 |
'CHAR', data_length, |
|---|
| 342 |
null) as limit, |
|---|
| 343 |
decode(data_type, 'NUMBER', data_scale, null) as scale |
|---|
| 344 |
from all_tab_columns |
|---|
| 345 |
where owner = '#{owner}' |
|---|
| 346 |
and table_name = '#{table_name}' |
|---|
| 347 |
order by column_id |
|---|
| 348 |
SQL |
|---|
| 349 |
|
|---|
| 350 |
select_all(table_cols, name).map do |row| |
|---|
| 351 |
limit, scale = row['limit'], row['scale'] |
|---|
| 352 |
if limit || scale |
|---|
| 353 |
row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")") |
|---|
| 354 |
end |
|---|
| 355 |
|
|---|
| 356 |
|
|---|
| 357 |
if row['data_default'] |
|---|
| 358 |
row['data_default'].sub!(/^(.*?)\s*$/, '\1') |
|---|
| 359 |
row['data_default'].sub!(/^'(.*)'$/, '\1') |
|---|
| 360 |
row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i |
|---|
| 361 |
end |
|---|
| 362 |
|
|---|
| 363 |
OracleColumn.new(oracle_downcase(row['name']), |
|---|
| 364 |
row['data_default'], |
|---|
| 365 |
row['sql_type'], |
|---|
| 366 |
row['nullable'] == 'Y') |
|---|
| 367 |
end |
|---|
| 368 |
end |
|---|
| 369 |
|
|---|
| 370 |
def create_table(name, options = {}) |
|---|
| 371 |
super(name, options) |
|---|
| 372 |
seq_name = options[:sequence_name] || "#{name}_seq" |
|---|
| 373 |
execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false |
|---|
| 374 |
end |
|---|
| 375 |
|
|---|
| 376 |
def rename_table(name, new_name) |
|---|
| 377 |
execute "RENAME #{name} TO #{new_name}" |
|---|
| 378 |
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil |
|---|
| 379 |
end |
|---|
| 380 |
|
|---|
| 381 |
def drop_table(name, options = {}) |
|---|
| 382 |
super(name) |
|---|
| 383 |
seq_name = options[:sequence_name] || "#{name}_seq" |
|---|
| 384 |
execute "DROP SEQUENCE #{seq_name}" rescue nil |
|---|
| 385 |
end |
|---|
| 386 |
|
|---|
| 387 |
def remove_index(table_name, options = {}) |
|---|
| 388 |
execute "DROP INDEX #{index_name(table_name, options)}" |
|---|
| 389 |
end |
|---|
| 390 |
|
|---|
| 391 |
def change_column_default(table_name, column_name, default) |
|---|
| 392 |
execute "ALTER TABLE #{table_name} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}" |
|---|
| 393 |
end |
|---|
| 394 |
|
|---|
| 395 |
def change_column(table_name, column_name, type, options = {}) |
|---|
| 396 |
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" |
|---|
| 397 |
add_column_options!(change_column_sql, options) |
|---|
| 398 |
execute(change_column_sql) |
|---|
| 399 |
end |
|---|
| 400 |
|
|---|
| 401 |
def rename_column(table_name, column_name, new_column_name) |
|---|
| 402 |
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}" |
|---|
| 403 |
end |
|---|
| 404 |
|
|---|
| 405 |
def remove_column(table_name, column_name) |
|---|
| 406 |
execute "ALTER TABLE #{table_name} DROP COLUMN #{quote_column_name(column_name)}" |
|---|
| 407 |
end |
|---|
| 408 |
|
|---|
| 409 |
|
|---|
| 410 |
|
|---|
| 411 |
def pk_and_sequence_for(table_name) |
|---|
| 412 |
(owner, table_name) = @connection.describe(table_name) |
|---|
| 413 |
|
|---|
| 414 |
pks = select_values(<<-SQL, 'Primary Key') |
|---|
| 415 |
select cc.column_name |
|---|
| 416 |
from all_constraints c, all_cons_columns cc |
|---|
| 417 |
where c.owner = '#{owner}' |
|---|
| 418 |
and c.table_name = '#{table_name}' |
|---|
| 419 |
and c.constraint_type = 'P' |
|---|
| 420 |
and cc.owner = c.owner |
|---|
| 421 |
and cc.constraint_name = c.constraint_name |
|---|
| 422 |
SQL |
|---|
| 423 |
|
|---|
| 424 |
|
|---|
| 425 |
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil |
|---|
| 426 |
end |
|---|
| 427 |
|
|---|
| 428 |
def structure_dump |
|---|
| 429 |
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq| |
|---|
| 430 |
structure << "create sequence #{seq.to_a.first.last};\n\n" |
|---|
| 431 |
end |
|---|
| 432 |
|
|---|
| 433 |
select_all("select table_name from user_tables").inject(s) do |structure, table| |
|---|
| 434 |
ddl = "create table #{table.to_a.first.last} (\n " |
|---|
| 435 |
cols = select_all(%Q{ |
|---|
| 436 |
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable |
|---|
| 437 |
from user_tab_columns |
|---|
| 438 |
where table_name = '#{table.to_a.first.last}' |
|---|
| 439 |
order by column_id |
|---|
| 440 |
}).map do |row| |
|---|
| 441 |
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}" |
|---|
| 442 |
if row['data_type'] =='NUMBER' and !row['data_precision'].nil? |
|---|
| 443 |
col << "(#{row['data_precision'].to_i}" |
|---|
| 444 |
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil? |
|---|
| 445 |
col << ')' |
|---|
| 446 |
elsif row['data_type'].include?('CHAR') |
|---|
| 447 |
col << "(#{row['data_length'].to_i})" |
|---|
| 448 |
end |
|---|
| 449 |
col << " default #{row['data_default']}" if !row['data_default'].nil? |
|---|
| 450 |
col << ' not null' if row['nullable'] == 'N' |
|---|
| 451 |
col |
|---|
| 452 |
end |
|---|
| 453 |
ddl << cols.join(",\n ") |
|---|
| 454 |
ddl << ");\n\n" |
|---|
| 455 |
structure << ddl |
|---|
| 456 |
end |
|---|
| 457 |
end |
|---|
| 458 |
|
|---|
| 459 |
def structure_drop |
|---|
| 460 |
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq| |
|---|
| 461 |
drop << "drop sequence #{seq.to_a.first.last};\n\n" |
|---|
| 462 |
end |
|---|
| 463 |
|
|---|
| 464 |
select_all("select table_name from user_tables").inject(s) do |drop, table| |
|---|
| 465 |
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n" |
|---|
| 466 |
end |
|---|
| 467 |
end |
|---|
| 468 |
|
|---|
| 469 |
def add_column_options!(sql, options) |
|---|
| 470 |
|
|---|
| 471 |
if options_include_default?(options) && (column = options[:column]) && column.type == :text |
|---|
| 472 |
sql << " DEFAULT #{quote(options.delete(:default))}" |
|---|
| 473 |
end |
|---|
| 474 |
super |
|---|
| 475 |
end |
|---|
| 476 |
|
|---|
| 477 |
|
|---|
| 478 |
|
|---|
| 479 |
|
|---|
| 480 |
|
|---|
| 481 |
|
|---|
| 482 |
|
|---|
| 483 |
|
|---|
| 484 |
|
|---|
| 485 |
|
|---|
| 486 |
|
|---|
| 487 |
def distinct(columns, order_by) |
|---|
| 488 |
return "DISTINCT #{columns}" if order_by.blank? |
|---|
| 489 |
|
|---|
| 490 |
|
|---|
| 491 |
|
|---|
| 492 |
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?) |
|---|
| 493 |
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i| |
|---|
| 494 |
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__" |
|---|
| 495 |
end |
|---|
| 496 |
sql = "DISTINCT #{columns}, " |
|---|
| 497 |
sql << order_columns * ", " |
|---|
| 498 |
end |
|---|
| 499 |
|
|---|
| 500 |
|
|---|
| 501 |
|
|---|
| 502 |
|
|---|
| 503 |
def add_order_by_for_association_limiting!(sql, options) |
|---|
| 504 |
return sql if options[:order].blank? |
|---|
| 505 |
|
|---|
| 506 |
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?) |
|---|
| 507 |
order.map! {|s| $1 if s =~ / (.*)/} |
|---|
| 508 |
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ') |
|---|
| 509 |
|
|---|
| 510 |
sql << " ORDER BY #{order}" |
|---|
| 511 |
end |
|---|
| 512 |
|
|---|
| 513 |
private |
|---|
| 514 |
|
|---|
| 515 |
def select(sql, name = nil) |
|---|
| 516 |
cursor = execute(sql, name) |
|---|
| 517 |
cols = cursor.get_col_names.map { |x| oracle_downcase(x) } |
|---|
| 518 |
rows = [] |
|---|
| 519 |
|
|---|
| 520 |
while row = cursor.fetch |
|---|
| 521 |
hash = Hash.new |
|---|
| 522 |
|
|---|
| 523 |
cols.each_with_index do |col, i| |
|---|
| 524 |
hash[col] = |
|---|
| 525 |
case row[i] |
|---|
| 526 |
when OCI8::LOB |
|---|
| 527 |
name == 'Writable Large Object' ? row[i]: row[i].read |
|---|
| 528 |
when OraDate |
|---|
| 529 |
d = row[i] |
|---|
| 530 |
if emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0) |
|---|
| 531 |
d.to_date |
|---|
| 532 |
else |
|---|
| 533 |
|
|---|
| 534 |
time_array = [d.year, d.month, d.day, d.hour, d.minute, d.second] |
|---|
| 535 |
begin |
|---|
| 536 |
Time.send(Base.default_timezone, *time_array) |
|---|
| 537 |
rescue |
|---|
| 538 |
zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end |
|---|
| 539 |
|
|---|
| 540 |
DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil |
|---|
| 541 |
end |
|---|
| 542 |
end |
|---|
| 543 |
else row[i] |
|---|
| 544 |
end unless col == 'raw_rnum_' |
|---|
| 545 |
end |
|---|
| 546 |
|
|---|
| 547 |
rows << hash |
|---|
| 548 |
end |
|---|
| 549 |
|
|---|
| 550 |
rows |
|---|
| 551 |
ensure |
|---|
| 552 |
cursor.close if cursor |
|---|
| 553 |
end |
|---|
| 554 |
|
|---|
| 555 |
|
|---|
| 556 |
|
|---|
| 557 |
|
|---|
| 558 |
|
|---|
| 559 |
|
|---|
| 560 |
|
|---|
| 561 |
def oracle_downcase(column_name) |
|---|
| 562 |
column_name =~ /[a-z]/ ? column_name : column_name.downcase |
|---|
| 563 |
end |
|---|
| 564 |
|
|---|
| 565 |
end |
|---|
| 566 |
end |
|---|
| 567 |
end |
|---|
| 568 |
|
|---|
| 569 |
|
|---|
| 570 |
class OCI8 |
|---|
| 571 |
|
|---|
| 572 |
|
|---|
| 573 |
|
|---|
| 574 |
class Cursor |
|---|
| 575 |
alias :define_a_column_pre_ar :define_a_column |
|---|
| 576 |
def define_a_column(i) |
|---|
| 577 |
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) } |
|---|
| 578 |
when 8; @stmt.defineByPos(i, String, 65535) |
|---|
| 579 |
when 187; @stmt.defineByPos(i, OraDate) |
|---|
| 580 |
when 108 |
|---|
| 581 |
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE' |
|---|
| 582 |
@stmt.defineByPos(i, String, 65535) |
|---|
| 583 |
else |
|---|
| 584 |
raise 'unsupported datatype' |
|---|
| 585 |
end |
|---|
| 586 |
else define_a_column_pre_ar i |
|---|
| 587 |
end |
|---|
| 588 |
end |
|---|
| 589 |
end |
|---|
| 590 |
|
|---|
| 591 |
|
|---|
| 592 |
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK) |
|---|
| 593 |
|
|---|
| 594 |
|
|---|
| 595 |
|
|---|
| 596 |
|
|---|
| 597 |
def describe(name) |
|---|
| 598 |
@desc ||= @@env.alloc(OCIDescribe) |
|---|
| 599 |
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14' |
|---|
| 600 |
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?} |
|---|
| 601 |
info = @desc.attrGet(OCI_ATTR_PARAM) |
|---|
| 602 |
|
|---|
| 603 |
case info.attrGet(OCI_ATTR_PTYPE) |
|---|
| 604 |
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW |
|---|
| 605 |
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA) |
|---|
| 606 |
table_name = info.attrGet(OCI_ATTR_OBJ_NAME) |
|---|
| 607 |
[owner, table_name] |
|---|
| 608 |
when OCI_PTYPE_SYN |
|---|
| 609 |
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME) |
|---|
| 610 |
name = info.attrGet(OCI_ATTR_NAME) |
|---|
| 611 |
describe(schema + '.' + name) |
|---|
| 612 |
else raise %Q{"DESC #{name}" failed; not a table or view.} |
|---|
| 613 |
end |
|---|
| 614 |
end |
|---|
| 615 |
|
|---|
| 616 |
end |
|---|
| 617 |
|
|---|
| 618 |
|
|---|
| 619 |
|
|---|
| 620 |
|
|---|
| 621 |
class OracleConnectionFactory |
|---|
| 622 |
def new_connection(username, password, database, async, prefetch_rows, cursor_sharing) |
|---|
| 623 |
conn = OCI8.new username, password, database |
|---|
| 624 |
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} |
|---|
| 625 |
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil |
|---|
| 626 |
conn.autocommit = true |
|---|
| 627 |
conn.non_blocking = true if async |
|---|
| 628 |
conn.prefetch_rows = prefetch_rows |
|---|
| 629 |
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil |
|---|
| 630 |
conn |
|---|
| 631 |
end |
|---|
| 632 |
end |
|---|
| 633 |
|
|---|
| 634 |
|
|---|
| 635 |
|
|---|
| 636 |
|
|---|
| 637 |
|
|---|
| 638 |
|
|---|
| 639 |
|
|---|
| 640 |
|
|---|
| 641 |
|
|---|
| 642 |
class OCI8AutoRecover < DelegateClass(OCI8) |
|---|
| 643 |
attr_accessor :active |
|---|
| 644 |
alias :active? :active |
|---|
| 645 |
|
|---|
| 646 |
cattr_accessor :auto_retry |
|---|
| 647 |
class << self |
|---|
| 648 |
alias :auto_retry? :auto_retry |
|---|
| 649 |
end |
|---|
| 650 |
@@auto_retry = false |
|---|
| 651 |
|
|---|
| 652 |
def initialize(config, factory = OracleConnectionFactory.new) |
|---|
| 653 |
@active = true |
|---|
| 654 |
@username, @password, @database, = config[:username].to_s, config[:password].to_s, config[:database].to_s |
|---|
| 655 |
@async = config[:allow_concurrency] |
|---|
| 656 |
@prefetch_rows = config[:prefetch_rows] || 100 |
|---|
| 657 |
@cursor_sharing = config[:cursor_sharing] || 'similar' |
|---|
| 658 |
@factory = factory |
|---|
| 659 |
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing |
|---|
| 660 |
super @connection |
|---|
| 661 |
end |
|---|
| 662 |
|
|---|
| 663 |
|
|---|
| 664 |
|
|---|
| 665 |
|
|---|
| 666 |
def ping |
|---|
| 667 |
@connection.exec("select 1 from dual") { |r| nil } |
|---|
| 668 |
@active = true |
|---|
| 669 |
rescue |
|---|
| 670 |
@active = false |
|---|
| 671 |
raise |
|---|
| 672 |
end |
|---|
| 673 |
|
|---|
| 674 |
|
|---|
| 675 |
def reset! |
|---|
| 676 |
logoff rescue nil |
|---|
| 677 |
begin |
|---|
| 678 |
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing |
|---|
| 679 |
__setobj__ @connection |
|---|
| 680 |
@active = true |
|---|
| 681 |
rescue |
|---|
| 682 |
@active = false |
|---|
| 683 |
raise |
|---|
| 684 |
end |
|---|
| 685 |
end |
|---|
| 686 |
|
|---|
| 687 |
|
|---|
| 688 |
|
|---|
| 689 |
|
|---|
| 690 |
|
|---|
| 691 |
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ] |
|---|
| 692 |
|
|---|
| 693 |
|
|---|
| 694 |
|
|---|
| 695 |
|
|---|
| 696 |
def exec(sql, *bindvars, &block) |
|---|
| 697 |
should_retry = self.class.auto_retry? && autocommit? |
|---|
| 698 |
|
|---|
| 699 |
begin |
|---|
| 700 |
@connection.exec(sql, *bindvars, &block) |
|---|
| 701 |
rescue OCIException => e |
|---|
| 702 |
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code) |
|---|
| 703 |
@active = false |
|---|
| 704 |
raise unless should_retry |
|---|
| 705 |
should_retry = false |
|---|
| 706 |
reset! rescue nil |
|---|
| 707 |
retry |
|---|
| 708 |
end |
|---|
| 709 |
end |
|---|
| 710 |
|
|---|
| 711 |
end |
|---|
| 712 |
|
|---|
| 713 |
rescue LoadError |
|---|
| 714 |
|
|---|
| 715 |
module ActiveRecord |
|---|
| 716 |
class Base |
|---|
| 717 |
@@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}" |
|---|
| 718 |
def self.oracle_connection(config) |
|---|
| 719 |
|
|---|
| 720 |
raise LoadError, @@oracle_error_message |
|---|
| 721 |
end |
|---|
| 722 |
def self.oci_connection(config) |
|---|
| 723 |
|
|---|
| 724 |
raise LoadError, @@oracle_error_message |
|---|
| 725 |
end |
|---|
| 726 |
end |
|---|
| 727 |
end |
|---|
| 728 |
end |
|---|