activerecord 1.15.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -1,350 +0,0 @@
|
|
1
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
class Base
|
5
|
-
# Establishes a connection to the database that's used by all Active Record objects
|
6
|
-
def self.openbase_connection(config) # :nodoc:
|
7
|
-
require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
|
8
|
-
|
9
|
-
config = config.symbolize_keys
|
10
|
-
host = config[:host]
|
11
|
-
username = config[:username].to_s
|
12
|
-
password = config[:password].to_s
|
13
|
-
|
14
|
-
|
15
|
-
if config.has_key?(:database)
|
16
|
-
database = config[:database]
|
17
|
-
else
|
18
|
-
raise ArgumentError, "No database specified. Missing argument: database."
|
19
|
-
end
|
20
|
-
|
21
|
-
oba = ConnectionAdapters::OpenBaseAdapter.new(
|
22
|
-
OpenBase.new(database, host, username, password), logger
|
23
|
-
)
|
24
|
-
|
25
|
-
oba
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
module ConnectionAdapters
|
31
|
-
class OpenBaseColumn < Column #:nodoc:
|
32
|
-
private
|
33
|
-
def simplified_type(field_type)
|
34
|
-
return :integer if field_type.downcase =~ /long/
|
35
|
-
return :decimal if field_type.downcase == "money"
|
36
|
-
return :binary if field_type.downcase == "object"
|
37
|
-
super
|
38
|
-
end
|
39
|
-
end
|
40
|
-
# The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
|
41
|
-
# http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
|
42
|
-
#
|
43
|
-
# Options:
|
44
|
-
#
|
45
|
-
# * <tt>:host</tt> -- Defaults to localhost
|
46
|
-
# * <tt>:username</tt> -- Defaults to nothing
|
47
|
-
# * <tt>:password</tt> -- Defaults to nothing
|
48
|
-
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
49
|
-
#
|
50
|
-
# The OpenBase adapter will make use of OpenBase's ability to generate unique ids
|
51
|
-
# for any column with an unique index applied. Thus, if the value of a primary
|
52
|
-
# key is not specified at the time an INSERT is performed, the adapter will prefetch
|
53
|
-
# a unique id for the primary key. This prefetching is also necessary in order
|
54
|
-
# to return the id after an insert.
|
55
|
-
#
|
56
|
-
# Caveat: Operations involving LIMIT and OFFSET do not yet work!
|
57
|
-
#
|
58
|
-
# Maintainer: derrick.spell@gmail.com
|
59
|
-
class OpenBaseAdapter < AbstractAdapter
|
60
|
-
def adapter_name
|
61
|
-
'OpenBase'
|
62
|
-
end
|
63
|
-
|
64
|
-
def native_database_types
|
65
|
-
{
|
66
|
-
:primary_key => "integer UNIQUE INDEX DEFAULT _rowid",
|
67
|
-
:string => { :name => "char", :limit => 4096 },
|
68
|
-
:text => { :name => "text" },
|
69
|
-
:integer => { :name => "integer" },
|
70
|
-
:float => { :name => "float" },
|
71
|
-
:decimal => { :name => "decimal" },
|
72
|
-
:datetime => { :name => "datetime" },
|
73
|
-
:timestamp => { :name => "timestamp" },
|
74
|
-
:time => { :name => "time" },
|
75
|
-
:date => { :name => "date" },
|
76
|
-
:binary => { :name => "object" },
|
77
|
-
:boolean => { :name => "boolean" }
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
def supports_migrations?
|
82
|
-
false
|
83
|
-
end
|
84
|
-
|
85
|
-
def prefetch_primary_key?(table_name = nil)
|
86
|
-
true
|
87
|
-
end
|
88
|
-
|
89
|
-
def default_sequence_name(table_name, primary_key) # :nodoc:
|
90
|
-
"#{table_name} #{primary_key}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def next_sequence_value(sequence_name)
|
94
|
-
ary = sequence_name.split(' ')
|
95
|
-
if (!ary[1]) then
|
96
|
-
ary[0] =~ /(\w+)_nonstd_seq/
|
97
|
-
ary[0] = $1
|
98
|
-
end
|
99
|
-
@connection.unique_row_id(ary[0], ary[1])
|
100
|
-
end
|
101
|
-
|
102
|
-
|
103
|
-
# QUOTING ==================================================
|
104
|
-
|
105
|
-
def quote(value, column = nil)
|
106
|
-
if value.kind_of?(String) && column && column.type == :binary
|
107
|
-
"'#{@connection.insert_binary(value)}'"
|
108
|
-
else
|
109
|
-
super
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def quoted_true
|
114
|
-
"1"
|
115
|
-
end
|
116
|
-
|
117
|
-
def quoted_false
|
118
|
-
"0"
|
119
|
-
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
# DATABASE STATEMENTS ======================================
|
124
|
-
|
125
|
-
def add_limit_offset!(sql, options) #:nodoc:
|
126
|
-
if limit = options[:limit]
|
127
|
-
unless offset = options[:offset]
|
128
|
-
sql << " RETURN RESULTS #{limit}"
|
129
|
-
else
|
130
|
-
limit = limit + offset
|
131
|
-
sql << " RETURN RESULTS #{offset} TO #{limit}"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def select_all(sql, name = nil) #:nodoc:
|
137
|
-
select(sql, name)
|
138
|
-
end
|
139
|
-
|
140
|
-
def select_one(sql, name = nil) #:nodoc:
|
141
|
-
add_limit_offset!(sql,{:limit => 1})
|
142
|
-
results = select(sql, name)
|
143
|
-
results.first if results
|
144
|
-
end
|
145
|
-
|
146
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
147
|
-
execute(sql, name)
|
148
|
-
update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
|
149
|
-
id_value
|
150
|
-
end
|
151
|
-
|
152
|
-
def execute(sql, name = nil) #:nodoc:
|
153
|
-
log(sql, name) { @connection.execute(sql) }
|
154
|
-
end
|
155
|
-
|
156
|
-
def update(sql, name = nil) #:nodoc:
|
157
|
-
execute(sql, name).rows_affected
|
158
|
-
end
|
159
|
-
|
160
|
-
alias_method :delete, :update #:nodoc:
|
161
|
-
#=begin
|
162
|
-
def begin_db_transaction #:nodoc:
|
163
|
-
execute "START TRANSACTION"
|
164
|
-
rescue Exception
|
165
|
-
# Transactions aren't supported
|
166
|
-
end
|
167
|
-
|
168
|
-
def commit_db_transaction #:nodoc:
|
169
|
-
execute "COMMIT"
|
170
|
-
rescue Exception
|
171
|
-
# Transactions aren't supported
|
172
|
-
end
|
173
|
-
|
174
|
-
def rollback_db_transaction #:nodoc:
|
175
|
-
execute "ROLLBACK"
|
176
|
-
rescue Exception
|
177
|
-
# Transactions aren't supported
|
178
|
-
end
|
179
|
-
#=end
|
180
|
-
|
181
|
-
# SCHEMA STATEMENTS ========================================
|
182
|
-
|
183
|
-
# Return the list of all tables in the schema search path.
|
184
|
-
def tables(name = nil) #:nodoc:
|
185
|
-
tables = @connection.tables
|
186
|
-
tables.reject! { |t| /\A_SYS_/ === t }
|
187
|
-
tables
|
188
|
-
end
|
189
|
-
|
190
|
-
def columns(table_name, name = nil) #:nodoc:
|
191
|
-
sql = "SELECT * FROM _sys_tables "
|
192
|
-
sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
|
193
|
-
sql << "ORDER BY columnNumber"
|
194
|
-
columns = []
|
195
|
-
select_all(sql, name).each do |row|
|
196
|
-
columns << OpenBaseColumn.new(row["fieldname"],
|
197
|
-
default_value(row["defaultvalue"]),
|
198
|
-
sql_type_name(row["typename"],row["length"]),
|
199
|
-
row["notnull"]
|
200
|
-
)
|
201
|
-
# breakpoint() if row["fieldname"] == "content"
|
202
|
-
end
|
203
|
-
columns
|
204
|
-
end
|
205
|
-
|
206
|
-
def indexes(table_name, name = nil)#:nodoc:
|
207
|
-
sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
|
208
|
-
sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
|
209
|
-
sql << "AND primarykey=0 "
|
210
|
-
sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
|
211
|
-
sql << "ORDER BY columnNumber"
|
212
|
-
indexes = []
|
213
|
-
execute(sql, name).each do |row|
|
214
|
-
indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
|
215
|
-
end
|
216
|
-
indexes
|
217
|
-
end
|
218
|
-
|
219
|
-
|
220
|
-
private
|
221
|
-
def select(sql, name = nil)
|
222
|
-
sql = translate_sql(sql)
|
223
|
-
results = execute(sql, name)
|
224
|
-
|
225
|
-
date_cols = []
|
226
|
-
col_names = []
|
227
|
-
results.column_infos.each do |info|
|
228
|
-
col_names << info.name
|
229
|
-
date_cols << info.name if info.type == "date"
|
230
|
-
end
|
231
|
-
|
232
|
-
rows = []
|
233
|
-
if ( results.rows_affected )
|
234
|
-
results.each do |row| # loop through result rows
|
235
|
-
hashed_row = {}
|
236
|
-
row.each_index do |index|
|
237
|
-
hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
|
238
|
-
end
|
239
|
-
date_cols.each do |name|
|
240
|
-
unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
|
241
|
-
hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
|
242
|
-
end
|
243
|
-
end
|
244
|
-
rows << hashed_row
|
245
|
-
end
|
246
|
-
end
|
247
|
-
rows
|
248
|
-
end
|
249
|
-
|
250
|
-
def default_value(value)
|
251
|
-
# Boolean type values
|
252
|
-
return true if value =~ /true/
|
253
|
-
return false if value =~ /false/
|
254
|
-
|
255
|
-
# Date / Time magic values
|
256
|
-
return Time.now.to_s if value =~ /^now\(\)/i
|
257
|
-
|
258
|
-
# Empty strings should be set to null
|
259
|
-
return nil if value.empty?
|
260
|
-
|
261
|
-
# Otherwise return what we got from OpenBase
|
262
|
-
# and hope for the best...
|
263
|
-
return value
|
264
|
-
end
|
265
|
-
|
266
|
-
def sql_type_name(type_name, length)
|
267
|
-
return "#{type_name}(#{length})" if ( type_name =~ /char/ )
|
268
|
-
type_name
|
269
|
-
end
|
270
|
-
|
271
|
-
def index_name(row = [])
|
272
|
-
name = ""
|
273
|
-
name << "UNIQUE " if row[3]
|
274
|
-
name << "CLUSTERED " if row[4]
|
275
|
-
name << "INDEX"
|
276
|
-
name
|
277
|
-
end
|
278
|
-
|
279
|
-
def translate_sql(sql)
|
280
|
-
|
281
|
-
# Change table.* to list of columns in table
|
282
|
-
while (sql =~ /SELECT.*\s(\w+)\.\*/)
|
283
|
-
table = $1
|
284
|
-
cols = columns(table)
|
285
|
-
if ( cols.size == 0 ) then
|
286
|
-
# Maybe this is a table alias
|
287
|
-
sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
|
288
|
-
$1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
|
289
|
-
cols = columns($1)
|
290
|
-
end
|
291
|
-
select_columns = []
|
292
|
-
cols.each do |col|
|
293
|
-
select_columns << table + '.' + col.name
|
294
|
-
end
|
295
|
-
sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
|
296
|
-
end
|
297
|
-
|
298
|
-
# Change JOIN clause to table list and WHERE condition
|
299
|
-
while (sql =~ /JOIN/)
|
300
|
-
sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
|
301
|
-
join_clause = $1 + $5
|
302
|
-
is_outer_join = $3
|
303
|
-
join_table = $4
|
304
|
-
join_condition = $5
|
305
|
-
join_condition.gsub!(/=/,"*") if is_outer_join
|
306
|
-
if (sql =~ /WHERE/)
|
307
|
-
sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
|
308
|
-
else
|
309
|
-
sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
|
310
|
-
end
|
311
|
-
sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
|
312
|
-
from_clause = $1
|
313
|
-
sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
|
314
|
-
sql.gsub!(join_clause,"")
|
315
|
-
end
|
316
|
-
|
317
|
-
# ORDER BY _rowid if no explicit ORDER BY
|
318
|
-
# This will ensure that find(:first) returns the first inserted row
|
319
|
-
if (sql !~ /(ORDER BY)|(GROUP BY)/)
|
320
|
-
if (sql =~ /RETURN RESULTS/)
|
321
|
-
sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
|
322
|
-
else
|
323
|
-
sql << " ORDER BY _rowid"
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
sql
|
328
|
-
end
|
329
|
-
|
330
|
-
def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
331
|
-
sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
|
332
|
-
table = $1
|
333
|
-
cols = $2
|
334
|
-
values = $3
|
335
|
-
cols = cols.split(',')
|
336
|
-
values.gsub!(/'[^']*'/,"''")
|
337
|
-
values.gsub!(/"[^"]*"/,"\"\"")
|
338
|
-
values = values.split(',')
|
339
|
-
update_cols = []
|
340
|
-
values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
|
341
|
-
update_sql = "UPDATE #{table} SET"
|
342
|
-
update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
|
343
|
-
update_sql.chop!()
|
344
|
-
update_sql << " WHERE #{pk}=#{quote(id_value)}"
|
345
|
-
execute(update_sql, name + " NULL Correction") if update_cols.size > 0
|
346
|
-
end
|
347
|
-
|
348
|
-
end
|
349
|
-
end
|
350
|
-
end
|
@@ -1,690 +0,0 @@
|
|
1
|
-
# oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
|
2
|
-
#
|
3
|
-
# Original author: Graham Jenkins
|
4
|
-
#
|
5
|
-
# Current maintainer: Michael Schoen <schoenm@earthlink.net>
|
6
|
-
#
|
7
|
-
#########################################################################
|
8
|
-
#
|
9
|
-
# Implementation notes:
|
10
|
-
# 1. Redefines (safely) a method in ActiveRecord to make it possible to
|
11
|
-
# implement an autonumbering solution for Oracle.
|
12
|
-
# 2. The OCI8 driver is patched to properly handle values for LONG and
|
13
|
-
# TIMESTAMP columns. The driver-author has indicated that a future
|
14
|
-
# release of the driver will obviate this patch.
|
15
|
-
# 3. LOB support is implemented through an after_save callback.
|
16
|
-
# 4. Oracle does not offer native LIMIT and OFFSET options; this
|
17
|
-
# functionality is mimiced through the use of nested selects.
|
18
|
-
# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
|
19
|
-
#
|
20
|
-
# Do what you want with this code, at your own peril, but if any
|
21
|
-
# significant portion of my code remains then please acknowledge my
|
22
|
-
# contribution.
|
23
|
-
# portions Copyright 2005 Graham Jenkins
|
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) #:nodoc:
|
34
|
-
# Use OCI8AutoRecover instead of normal OCI8 driver.
|
35
|
-
ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger
|
36
|
-
end
|
37
|
-
|
38
|
-
# for backwards-compatibility
|
39
|
-
def self.oci_connection(config) #:nodoc:
|
40
|
-
config[:database] = config[:host]
|
41
|
-
self.oracle_connection(config)
|
42
|
-
end
|
43
|
-
|
44
|
-
# After setting large objects to empty, select the OCI8::LOB
|
45
|
-
# and write back the data.
|
46
|
-
after_save :write_lobs
|
47
|
-
def write_lobs() #:nodoc:
|
48
|
-
if connection.is_a?(ConnectionAdapters::OracleAdapter)
|
49
|
-
self.class.columns.select { |c| c.sql_type =~ /LOB$/i }.each { |c|
|
50
|
-
value = self[c.name]
|
51
|
-
value = value.to_yaml if unserializable_attribute?(c.name, c)
|
52
|
-
next if value.nil? || (value == '')
|
53
|
-
lob = connection.select_one(
|
54
|
-
"SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote_value(id)}",
|
55
|
-
'Writable Large Object')[c.name]
|
56
|
-
lob.write value
|
57
|
-
}
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private :write_lobs
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
module ConnectionAdapters #:nodoc:
|
66
|
-
class OracleColumn < Column #:nodoc:
|
67
|
-
|
68
|
-
def type_cast(value)
|
69
|
-
return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates
|
70
|
-
super
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
def simplified_type(field_type)
|
75
|
-
return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)'
|
76
|
-
case field_type
|
77
|
-
when /date|time/i then :datetime
|
78
|
-
else super
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def guess_date_or_time(value)
|
83
|
-
(value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
84
|
-
Date.new(value.year, value.month, value.day) : value
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
# This is an Oracle/OCI adapter for the ActiveRecord persistence
|
90
|
-
# framework. It relies upon the OCI8 driver, which works with Oracle 8i
|
91
|
-
# and above. Most recent development has been on Debian Linux against
|
92
|
-
# a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
|
93
|
-
# See: http://rubyforge.org/projects/ruby-oci8/
|
94
|
-
#
|
95
|
-
# Usage notes:
|
96
|
-
# * Key generation assumes a "${table_name}_seq" sequence is available
|
97
|
-
# for all tables; the sequence name can be changed using
|
98
|
-
# ActiveRecord::Base.set_sequence_name. When using Migrations, these
|
99
|
-
# sequences are created automatically.
|
100
|
-
# * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
|
101
|
-
# Consequently some hacks are employed to map data back to Date or Time
|
102
|
-
# in Ruby. If the column_name ends in _time it's created as a Ruby Time.
|
103
|
-
# Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
|
104
|
-
# it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
|
105
|
-
# you'll probably not care very much. In 9i and up it's tempting to
|
106
|
-
# map DATE to Date and TIMESTAMP to Time, but too many databases use
|
107
|
-
# DATE for both. Timezones and sub-second precision on timestamps are
|
108
|
-
# not supported.
|
109
|
-
# * Default values that are functions (such as "SYSDATE") are not
|
110
|
-
# supported. This is a restriction of the way ActiveRecord supports
|
111
|
-
# default values.
|
112
|
-
# * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
|
113
|
-
# is supported in Oracle9i and later. You will need to use #finder_sql for
|
114
|
-
# has_and_belongs_to_many associations to run against Oracle8.
|
115
|
-
#
|
116
|
-
# Required parameters:
|
117
|
-
#
|
118
|
-
# * <tt>:username</tt>
|
119
|
-
# * <tt>:password</tt>
|
120
|
-
# * <tt>:database</tt>
|
121
|
-
class OracleAdapter < AbstractAdapter
|
122
|
-
|
123
|
-
@@emulate_booleans = true
|
124
|
-
cattr_accessor :emulate_booleans
|
125
|
-
|
126
|
-
@@emulate_dates = false
|
127
|
-
cattr_accessor :emulate_dates
|
128
|
-
|
129
|
-
def adapter_name #:nodoc:
|
130
|
-
'Oracle'
|
131
|
-
end
|
132
|
-
|
133
|
-
def supports_migrations? #:nodoc:
|
134
|
-
true
|
135
|
-
end
|
136
|
-
|
137
|
-
def native_database_types #:nodoc:
|
138
|
-
{
|
139
|
-
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
140
|
-
:string => { :name => "VARCHAR2", :limit => 255 },
|
141
|
-
:text => { :name => "CLOB" },
|
142
|
-
:integer => { :name => "NUMBER", :limit => 38 },
|
143
|
-
:float => { :name => "NUMBER" },
|
144
|
-
:decimal => { :name => "DECIMAL" },
|
145
|
-
:datetime => { :name => "DATE" },
|
146
|
-
:timestamp => { :name => "DATE" },
|
147
|
-
:time => { :name => "DATE" },
|
148
|
-
:date => { :name => "DATE" },
|
149
|
-
:binary => { :name => "BLOB" },
|
150
|
-
:boolean => { :name => "NUMBER", :limit => 1 }
|
151
|
-
}
|
152
|
-
end
|
153
|
-
|
154
|
-
def table_alias_length
|
155
|
-
30
|
156
|
-
end
|
157
|
-
|
158
|
-
# QUOTING ==================================================
|
159
|
-
#
|
160
|
-
# see: abstract/quoting.rb
|
161
|
-
|
162
|
-
# camelCase column names need to be quoted; not that anyone using Oracle
|
163
|
-
# would really do this, but handling this case means we pass the test...
|
164
|
-
def quote_column_name(name) #:nodoc:
|
165
|
-
name =~ /[A-Z]/ ? "\"#{name}\"" : name
|
166
|
-
end
|
167
|
-
|
168
|
-
def quote_string(s) #:nodoc:
|
169
|
-
s.gsub(/'/, "''")
|
170
|
-
end
|
171
|
-
|
172
|
-
def quote(value, column = nil) #:nodoc:
|
173
|
-
if column && [:text, :binary].include?(column.type)
|
174
|
-
%Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
|
175
|
-
else
|
176
|
-
super
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def quoted_true
|
181
|
-
"1"
|
182
|
-
end
|
183
|
-
|
184
|
-
def quoted_false
|
185
|
-
"0"
|
186
|
-
end
|
187
|
-
|
188
|
-
|
189
|
-
# CONNECTION MANAGEMENT ====================================
|
190
|
-
#
|
191
|
-
|
192
|
-
# Returns true if the connection is active.
|
193
|
-
def active?
|
194
|
-
# Pings the connection to check if it's still good. Note that an
|
195
|
-
# #active? method is also available, but that simply returns the
|
196
|
-
# last known state, which isn't good enough if the connection has
|
197
|
-
# gone stale since the last use.
|
198
|
-
@connection.ping
|
199
|
-
rescue OCIException
|
200
|
-
false
|
201
|
-
end
|
202
|
-
|
203
|
-
# Reconnects to the database.
|
204
|
-
def reconnect!
|
205
|
-
@connection.reset!
|
206
|
-
rescue OCIException => e
|
207
|
-
@logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
|
208
|
-
end
|
209
|
-
|
210
|
-
# Disconnects from the database.
|
211
|
-
def disconnect!
|
212
|
-
@connection.logoff rescue nil
|
213
|
-
@connection.active = false
|
214
|
-
end
|
215
|
-
|
216
|
-
|
217
|
-
# DATABASE STATEMENTS ======================================
|
218
|
-
#
|
219
|
-
# see: abstract/database_statements.rb
|
220
|
-
|
221
|
-
def execute(sql, name = nil) #:nodoc:
|
222
|
-
log(sql, name) { @connection.exec sql }
|
223
|
-
end
|
224
|
-
|
225
|
-
# Returns the next sequence value from a sequence generator. Not generally
|
226
|
-
# called directly; used by ActiveRecord to get the next primary key value
|
227
|
-
# when inserting a new database record (see #prefetch_primary_key?).
|
228
|
-
def next_sequence_value(sequence_name)
|
229
|
-
id = 0
|
230
|
-
@connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
|
231
|
-
id
|
232
|
-
end
|
233
|
-
|
234
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
235
|
-
execute(sql, name)
|
236
|
-
id_value
|
237
|
-
end
|
238
|
-
|
239
|
-
def begin_db_transaction #:nodoc:
|
240
|
-
@connection.autocommit = false
|
241
|
-
end
|
242
|
-
|
243
|
-
def commit_db_transaction #:nodoc:
|
244
|
-
@connection.commit
|
245
|
-
ensure
|
246
|
-
@connection.autocommit = true
|
247
|
-
end
|
248
|
-
|
249
|
-
def rollback_db_transaction #:nodoc:
|
250
|
-
@connection.rollback
|
251
|
-
ensure
|
252
|
-
@connection.autocommit = true
|
253
|
-
end
|
254
|
-
|
255
|
-
def add_limit_offset!(sql, options) #:nodoc:
|
256
|
-
offset = options[:offset] || 0
|
257
|
-
|
258
|
-
if limit = options[:limit]
|
259
|
-
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
260
|
-
elsif offset > 0
|
261
|
-
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
# Returns true for Oracle adapter (since Oracle requires primary key
|
266
|
-
# values to be pre-fetched before insert). See also #next_sequence_value.
|
267
|
-
def prefetch_primary_key?(table_name = nil)
|
268
|
-
true
|
269
|
-
end
|
270
|
-
|
271
|
-
def default_sequence_name(table, column) #:nodoc:
|
272
|
-
"#{table}_seq"
|
273
|
-
end
|
274
|
-
|
275
|
-
|
276
|
-
# SCHEMA STATEMENTS ========================================
|
277
|
-
#
|
278
|
-
# see: abstract/schema_statements.rb
|
279
|
-
|
280
|
-
def current_database #:nodoc:
|
281
|
-
select_one("select sys_context('userenv','db_name') db from dual")["db"]
|
282
|
-
end
|
283
|
-
|
284
|
-
def tables(name = nil) #:nodoc:
|
285
|
-
select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
|
286
|
-
tabs << t.to_a.first.last
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
def indexes(table_name, name = nil) #:nodoc:
|
291
|
-
result = select_all(<<-SQL, name)
|
292
|
-
SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
|
293
|
-
FROM user_indexes i, user_ind_columns c
|
294
|
-
WHERE i.table_name = '#{table_name.to_s.upcase}'
|
295
|
-
AND c.index_name = i.index_name
|
296
|
-
AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P')
|
297
|
-
ORDER BY i.index_name, c.column_position
|
298
|
-
SQL
|
299
|
-
|
300
|
-
current_index = nil
|
301
|
-
indexes = []
|
302
|
-
|
303
|
-
result.each do |row|
|
304
|
-
if current_index != row['index_name']
|
305
|
-
indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
|
306
|
-
current_index = row['index_name']
|
307
|
-
end
|
308
|
-
|
309
|
-
indexes.last.columns << row['column_name']
|
310
|
-
end
|
311
|
-
|
312
|
-
indexes
|
313
|
-
end
|
314
|
-
|
315
|
-
def columns(table_name, name = nil) #:nodoc:
|
316
|
-
(owner, table_name) = @connection.describe(table_name)
|
317
|
-
|
318
|
-
table_cols = <<-SQL
|
319
|
-
select column_name as name, data_type as sql_type, data_default, nullable,
|
320
|
-
decode(data_type, 'NUMBER', data_precision,
|
321
|
-
'FLOAT', data_precision,
|
322
|
-
'VARCHAR2', data_length,
|
323
|
-
'CHAR', data_length,
|
324
|
-
null) as limit,
|
325
|
-
decode(data_type, 'NUMBER', data_scale, null) as scale
|
326
|
-
from all_tab_columns
|
327
|
-
where owner = '#{owner}'
|
328
|
-
and table_name = '#{table_name}'
|
329
|
-
order by column_id
|
330
|
-
SQL
|
331
|
-
|
332
|
-
select_all(table_cols, name).map do |row|
|
333
|
-
limit, scale = row['limit'], row['scale']
|
334
|
-
if limit || scale
|
335
|
-
row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
|
336
|
-
end
|
337
|
-
|
338
|
-
# clean up odd default spacing from Oracle
|
339
|
-
if row['data_default']
|
340
|
-
row['data_default'].sub!(/^(.*?)\s*$/, '\1')
|
341
|
-
row['data_default'].sub!(/^'(.*)'$/, '\1')
|
342
|
-
row['data_default'] = nil if row['data_default'] =~ /^null$/i
|
343
|
-
end
|
344
|
-
|
345
|
-
OracleColumn.new(oracle_downcase(row['name']),
|
346
|
-
row['data_default'],
|
347
|
-
row['sql_type'],
|
348
|
-
row['nullable'] == 'Y')
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
|
-
def create_table(name, options = {}) #:nodoc:
|
353
|
-
super(name, options)
|
354
|
-
seq_name = options[:sequence_name] || "#{name}_seq"
|
355
|
-
execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
|
356
|
-
end
|
357
|
-
|
358
|
-
def rename_table(name, new_name) #:nodoc:
|
359
|
-
execute "RENAME #{name} TO #{new_name}"
|
360
|
-
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
|
361
|
-
end
|
362
|
-
|
363
|
-
def drop_table(name, options = {}) #:nodoc:
|
364
|
-
super(name)
|
365
|
-
seq_name = options[:sequence_name] || "#{name}_seq"
|
366
|
-
execute "DROP SEQUENCE #{seq_name}" rescue nil
|
367
|
-
end
|
368
|
-
|
369
|
-
def remove_index(table_name, options = {}) #:nodoc:
|
370
|
-
execute "DROP INDEX #{index_name(table_name, options)}"
|
371
|
-
end
|
372
|
-
|
373
|
-
def change_column_default(table_name, column_name, default) #:nodoc:
|
374
|
-
execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
|
375
|
-
end
|
376
|
-
|
377
|
-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
378
|
-
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
379
|
-
add_column_options!(change_column_sql, options)
|
380
|
-
execute(change_column_sql)
|
381
|
-
end
|
382
|
-
|
383
|
-
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
384
|
-
execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
|
385
|
-
end
|
386
|
-
|
387
|
-
def remove_column(table_name, column_name) #:nodoc:
|
388
|
-
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
|
389
|
-
end
|
390
|
-
|
391
|
-
# Find a table's primary key and sequence.
|
392
|
-
# *Note*: Only primary key is implemented - sequence will be nil.
|
393
|
-
def pk_and_sequence_for(table_name)
|
394
|
-
(owner, table_name) = @connection.describe(table_name)
|
395
|
-
|
396
|
-
pks = select_values(<<-SQL, 'Primary Key')
|
397
|
-
select cc.column_name
|
398
|
-
from all_constraints c, all_cons_columns cc
|
399
|
-
where c.owner = '#{owner}'
|
400
|
-
and c.table_name = '#{table_name}'
|
401
|
-
and c.constraint_type = 'P'
|
402
|
-
and cc.owner = c.owner
|
403
|
-
and cc.constraint_name = c.constraint_name
|
404
|
-
SQL
|
405
|
-
|
406
|
-
# only support single column keys
|
407
|
-
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
|
408
|
-
end
|
409
|
-
|
410
|
-
def structure_dump #:nodoc:
|
411
|
-
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
412
|
-
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
413
|
-
end
|
414
|
-
|
415
|
-
select_all("select table_name from user_tables").inject(s) do |structure, table|
|
416
|
-
ddl = "create table #{table.to_a.first.last} (\n "
|
417
|
-
cols = select_all(%Q{
|
418
|
-
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
|
419
|
-
from user_tab_columns
|
420
|
-
where table_name = '#{table.to_a.first.last}'
|
421
|
-
order by column_id
|
422
|
-
}).map do |row|
|
423
|
-
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
424
|
-
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
|
425
|
-
col << "(#{row['data_precision'].to_i}"
|
426
|
-
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
|
427
|
-
col << ')'
|
428
|
-
elsif row['data_type'].include?('CHAR')
|
429
|
-
col << "(#{row['data_length'].to_i})"
|
430
|
-
end
|
431
|
-
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
432
|
-
col << ' not null' if row['nullable'] == 'N'
|
433
|
-
col
|
434
|
-
end
|
435
|
-
ddl << cols.join(",\n ")
|
436
|
-
ddl << ");\n\n"
|
437
|
-
structure << ddl
|
438
|
-
end
|
439
|
-
end
|
440
|
-
|
441
|
-
def structure_drop #:nodoc:
|
442
|
-
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
|
443
|
-
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
444
|
-
end
|
445
|
-
|
446
|
-
select_all("select table_name from user_tables").inject(s) do |drop, table|
|
447
|
-
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
|
-
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
452
|
-
#
|
453
|
-
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
|
454
|
-
# queries. However, with those columns included in the SELECT DISTINCT list, you
|
455
|
-
# won't actually get a distinct list of the column you want (presuming the column
|
456
|
-
# has duplicates with multiple values for the ordered-by columns. So we use the
|
457
|
-
# FIRST_VALUE function to get a single (first) value for each column, effectively
|
458
|
-
# making every row the same.
|
459
|
-
#
|
460
|
-
# distinct("posts.id", "posts.created_at desc")
|
461
|
-
def distinct(columns, order_by)
|
462
|
-
return "DISTINCT #{columns}" if order_by.blank?
|
463
|
-
|
464
|
-
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
|
465
|
-
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
|
466
|
-
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
|
467
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
|
468
|
-
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
|
469
|
-
end
|
470
|
-
sql = "DISTINCT #{columns}, "
|
471
|
-
sql << order_columns * ", "
|
472
|
-
end
|
473
|
-
|
474
|
-
# ORDER BY clause for the passed order option.
|
475
|
-
#
|
476
|
-
# Uses column aliases as defined by #distinct.
|
477
|
-
def add_order_by_for_association_limiting!(sql, options)
|
478
|
-
return sql if options[:order].blank?
|
479
|
-
|
480
|
-
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
481
|
-
order.map! {|s| $1 if s =~ / (.*)/}
|
482
|
-
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
|
483
|
-
|
484
|
-
sql << "ORDER BY #{order}"
|
485
|
-
end
|
486
|
-
|
487
|
-
private
|
488
|
-
|
489
|
-
def select(sql, name = nil)
|
490
|
-
cursor = execute(sql, name)
|
491
|
-
cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
|
492
|
-
rows = []
|
493
|
-
|
494
|
-
while row = cursor.fetch
|
495
|
-
hash = Hash.new
|
496
|
-
|
497
|
-
cols.each_with_index do |col, i|
|
498
|
-
hash[col] =
|
499
|
-
case row[i]
|
500
|
-
when OCI8::LOB
|
501
|
-
name == 'Writable Large Object' ? row[i]: row[i].read
|
502
|
-
when OraDate
|
503
|
-
(row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
|
504
|
-
row[i].to_date : row[i].to_time
|
505
|
-
else row[i]
|
506
|
-
end unless col == 'raw_rnum_'
|
507
|
-
end
|
508
|
-
|
509
|
-
rows << hash
|
510
|
-
end
|
511
|
-
|
512
|
-
rows
|
513
|
-
ensure
|
514
|
-
cursor.close if cursor
|
515
|
-
end
|
516
|
-
|
517
|
-
# Oracle column names by default are case-insensitive, but treated as upcase;
|
518
|
-
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
519
|
-
# their column names when creating Oracle tables, which makes then case-sensitive.
|
520
|
-
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
521
|
-
# camelCase column name. I imagine other dbs handle this different, since there's a
|
522
|
-
# unit test that's currently failing test_oci.
|
523
|
-
def oracle_downcase(column_name)
|
524
|
-
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
525
|
-
end
|
526
|
-
|
527
|
-
end
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
|
532
|
-
class OCI8 #:nodoc:
|
533
|
-
|
534
|
-
# This OCI8 patch may not longer be required with the upcoming
|
535
|
-
# release of version 0.2.
|
536
|
-
class Cursor #:nodoc:
|
537
|
-
alias :define_a_column_pre_ar :define_a_column
|
538
|
-
def define_a_column(i)
|
539
|
-
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
|
540
|
-
when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values
|
541
|
-
when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
|
542
|
-
when 108
|
543
|
-
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
|
544
|
-
@stmt.defineByPos(i, String, 65535)
|
545
|
-
else
|
546
|
-
raise 'unsupported datatype'
|
547
|
-
end
|
548
|
-
else define_a_column_pre_ar i
|
549
|
-
end
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
# missing constant from oci8 < 0.1.14
|
554
|
-
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
|
555
|
-
|
556
|
-
# Uses the describeAny OCI call to find the target owner and table_name
|
557
|
-
# indicated by +name+, parsing through synonynms as necessary. Returns
|
558
|
-
# an array of [owner, table_name].
|
559
|
-
def describe(name)
|
560
|
-
@desc ||= @@env.alloc(OCIDescribe)
|
561
|
-
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
562
|
-
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
563
|
-
info = @desc.attrGet(OCI_ATTR_PARAM)
|
564
|
-
|
565
|
-
case info.attrGet(OCI_ATTR_PTYPE)
|
566
|
-
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
|
567
|
-
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
|
568
|
-
table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
|
569
|
-
[owner, table_name]
|
570
|
-
when OCI_PTYPE_SYN
|
571
|
-
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
572
|
-
name = info.attrGet(OCI_ATTR_NAME)
|
573
|
-
describe(schema + '.' + name)
|
574
|
-
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
end
|
579
|
-
|
580
|
-
|
581
|
-
# The OracleConnectionFactory factors out the code necessary to connect and
|
582
|
-
# configure an Oracle/OCI connection.
|
583
|
-
class OracleConnectionFactory #:nodoc:
|
584
|
-
def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
|
585
|
-
conn = OCI8.new username, password, database
|
586
|
-
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
587
|
-
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
|
588
|
-
conn.autocommit = true
|
589
|
-
conn.non_blocking = true if async
|
590
|
-
conn.prefetch_rows = prefetch_rows
|
591
|
-
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
592
|
-
conn
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
|
597
|
-
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
598
|
-
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
599
|
-
# (ie., we're not in the middle of a longer transaction), it will
|
600
|
-
# automatically reconnect and try again. If autocommit is turned off,
|
601
|
-
# this would be dangerous (as the earlier part of the implied transaction
|
602
|
-
# may have failed silently if the connection died) -- so instead the
|
603
|
-
# connection is marked as dead, to be reconnected on it's next use.
|
604
|
-
class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
|
605
|
-
attr_accessor :active
|
606
|
-
alias :active? :active
|
607
|
-
|
608
|
-
cattr_accessor :auto_retry
|
609
|
-
class << self
|
610
|
-
alias :auto_retry? :auto_retry
|
611
|
-
end
|
612
|
-
@@auto_retry = false
|
613
|
-
|
614
|
-
def initialize(config, factory = OracleConnectionFactory.new)
|
615
|
-
@active = true
|
616
|
-
@username, @password, @database, = config[:username], config[:password], config[:database]
|
617
|
-
@async = config[:allow_concurrency]
|
618
|
-
@prefetch_rows = config[:prefetch_rows] || 100
|
619
|
-
@cursor_sharing = config[:cursor_sharing] || 'similar'
|
620
|
-
@factory = factory
|
621
|
-
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
|
622
|
-
super @connection
|
623
|
-
end
|
624
|
-
|
625
|
-
# Checks connection, returns true if active. Note that ping actively
|
626
|
-
# checks the connection, while #active? simply returns the last
|
627
|
-
# known state.
|
628
|
-
def ping
|
629
|
-
@connection.exec("select 1 from dual") { |r| nil }
|
630
|
-
@active = true
|
631
|
-
rescue
|
632
|
-
@active = false
|
633
|
-
raise
|
634
|
-
end
|
635
|
-
|
636
|
-
# Resets connection, by logging off and creating a new connection.
|
637
|
-
def reset!
|
638
|
-
logoff rescue nil
|
639
|
-
begin
|
640
|
-
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
|
641
|
-
__setobj__ @connection
|
642
|
-
@active = true
|
643
|
-
rescue
|
644
|
-
@active = false
|
645
|
-
raise
|
646
|
-
end
|
647
|
-
end
|
648
|
-
|
649
|
-
# ORA-00028: your session has been killed
|
650
|
-
# ORA-01012: not logged on
|
651
|
-
# ORA-03113: end-of-file on communication channel
|
652
|
-
# ORA-03114: not connected to ORACLE
|
653
|
-
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
|
654
|
-
|
655
|
-
# Adds auto-recovery functionality.
|
656
|
-
#
|
657
|
-
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
658
|
-
def exec(sql, *bindvars, &block)
|
659
|
-
should_retry = self.class.auto_retry? && autocommit?
|
660
|
-
|
661
|
-
begin
|
662
|
-
@connection.exec(sql, *bindvars, &block)
|
663
|
-
rescue OCIException => e
|
664
|
-
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
665
|
-
@active = false
|
666
|
-
raise unless should_retry
|
667
|
-
should_retry = false
|
668
|
-
reset! rescue nil
|
669
|
-
retry
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
|
-
end
|
674
|
-
|
675
|
-
rescue LoadError
|
676
|
-
# OCI8 driver is unavailable.
|
677
|
-
module ActiveRecord # :nodoc:
|
678
|
-
class Base
|
679
|
-
@@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
|
680
|
-
def self.oracle_connection(config) # :nodoc:
|
681
|
-
# Set up a reasonable error message
|
682
|
-
raise LoadError, @@oracle_error_message
|
683
|
-
end
|
684
|
-
def self.oci_connection(config) # :nodoc:
|
685
|
-
# Set up a reasonable error message
|
686
|
-
raise LoadError, @@oracle_error_message
|
687
|
-
end
|
688
|
-
end
|
689
|
-
end
|
690
|
-
end
|