activerecord 1.11.1 → 1.12.1
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 +198 -0
- data/lib/active_record.rb +19 -14
- data/lib/active_record/acts/list.rb +8 -6
- data/lib/active_record/acts/tree.rb +33 -10
- data/lib/active_record/aggregations.rb +1 -7
- data/lib/active_record/associations.rb +151 -82
- data/lib/active_record/associations/association_collection.rb +25 -0
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +19 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
- data/lib/active_record/associations/has_many_association.rb +6 -14
- data/lib/active_record/associations/has_one_association.rb +5 -3
- data/lib/active_record/base.rb +344 -130
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
- data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
- data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
- data/lib/active_record/fixtures.rb +47 -24
- data/lib/active_record/migration.rb +34 -5
- data/lib/active_record/observer.rb +32 -2
- data/lib/active_record/query_cache.rb +12 -11
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +84 -0
- data/lib/active_record/transactions.rb +1 -3
- data/lib/active_record/validations.rb +40 -26
- data/lib/active_record/vendor/mysql.rb +6 -0
- data/lib/active_record/version.rb +9 -0
- data/rakefile +5 -16
- data/test/abstract_unit.rb +6 -11
- data/test/adapter_test.rb +58 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_callbacks_test.rb +14 -0
- data/test/associations_go_eager_test.rb +56 -14
- data/test/associations_test.rb +245 -25
- data/test/base_test.rb +205 -34
- data/test/binary_test.rb +25 -42
- data/test/callbacks_test.rb +75 -0
- data/test/conditions_scoping_test.rb +136 -0
- data/test/connections/native_mysql/connection.rb +0 -4
- data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/deprecated_associations_test.rb +7 -6
- data/test/deprecated_finder_test.rb +3 -3
- data/test/finder_test.rb +33 -3
- data/test/fixtures/accounts.yml +5 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/category.rb +11 -1
- data/test/fixtures/comment.rb +22 -2
- data/test/fixtures/comments.yml +6 -0
- data/test/fixtures/companies.yml +15 -0
- data/test/fixtures/company.rb +24 -1
- data/test/fixtures/db_definitions/db2.drop.sql +5 -1
- data/test/fixtures/db_definitions/db2.sql +15 -1
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +17 -2
- data/test/fixtures/db_definitions/oci.drop.sql +37 -5
- data/test/fixtures/db_definitions/oci.sql +47 -4
- data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
- data/test/fixtures/db_definitions/oci2.sql +2 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +33 -4
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +16 -2
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +16 -2
- data/test/fixtures/developer.rb +1 -1
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/mixins.yml +11 -1
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/post.rb +4 -0
- data/test/fixtures/posts.yml +7 -0
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +2 -4
- data/test/fixtures/topics.yml +2 -2
- data/test/fixtures_test.rb +79 -7
- data/test/inheritance_test.rb +2 -2
- data/test/lifecycle_test.rb +14 -6
- data/test/migration_test.rb +164 -6
- data/test/mixin_test.rb +78 -2
- data/test/pk_test.rb +25 -1
- data/test/readonly_test.rb +31 -0
- data/test/reflection_test.rb +4 -1
- data/test/schema_dumper_test.rb +19 -0
- data/test/schema_test_postgresql.rb +3 -2
- data/test/synonym_test_oci.rb +17 -0
- data/test/threaded_connections_test.rb +2 -1
- data/test/transactions_test.rb +109 -10
- data/test/validations_test.rb +70 -42
- metadata +25 -5
- data/test/fixtures/associations.png +0 -0
- data/test/thread_safety_test.rb +0 -36
@@ -11,7 +11,7 @@ begin
|
|
11
11
|
# Establishes a connection to the database that's used by
|
12
12
|
# all Active Record objects
|
13
13
|
def self.db2_connection(config) # :nodoc:
|
14
|
-
|
14
|
+
config = config.symbolize_keys
|
15
15
|
usr = config[:username]
|
16
16
|
pwd = config[:password]
|
17
17
|
|
@@ -28,7 +28,7 @@ begin
|
|
28
28
|
end
|
29
29
|
|
30
30
|
module ConnectionAdapters
|
31
|
-
# The DB2 adapter works with the C-based CLI driver (http://
|
31
|
+
# The DB2 adapter works with the C-based CLI driver (http://rubyforge.org/projects/ruby-dbi/)
|
32
32
|
#
|
33
33
|
# Options:
|
34
34
|
#
|
@@ -44,7 +44,7 @@ begin
|
|
44
44
|
select(sql, name).first
|
45
45
|
end
|
46
46
|
|
47
|
-
def insert(sql, name = nil, pk = nil, id_value = nil)
|
47
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
48
48
|
execute(sql, name = nil)
|
49
49
|
id_value || last_insert_id
|
50
50
|
end
|
@@ -91,12 +91,26 @@ begin
|
|
91
91
|
string.gsub(/'/, "''") # ' (for ruby-mode)
|
92
92
|
end
|
93
93
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
94
|
+
def add_limit_offset!(sql, options)
|
95
|
+
if options[:limit] and !options[:limit].nil?
|
96
|
+
# "FETCH FIRST 0 ROWS ONLY" is not allowed, so we have
|
97
|
+
# to use a cheap trick.
|
98
|
+
if options[:limit] == 0
|
99
|
+
if sql =~ /WHERE/i
|
100
|
+
sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
|
101
|
+
elsif
|
102
|
+
sql =~ /ORDER\s+BY/i
|
103
|
+
sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
|
104
|
+
else
|
105
|
+
sql << 'WHERE 1 = 2'
|
106
|
+
end
|
107
|
+
else
|
108
|
+
sql << " FETCH FIRST #{options[:limit]} ROWS ONLY"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
if options[:offset] and !options[:offset].nil?
|
112
|
+
raise ArgumentError, ':offset option is not yet supported!'
|
113
|
+
end
|
100
114
|
end
|
101
115
|
|
102
116
|
def columns(table_name, name = nil)
|
@@ -115,6 +129,22 @@ begin
|
|
115
129
|
result
|
116
130
|
end
|
117
131
|
|
132
|
+
def native_database_types
|
133
|
+
{
|
134
|
+
:primary_key => "int generated by default as identity primary key",
|
135
|
+
:string => { :name => "varchar", :limit => 255 },
|
136
|
+
:text => { :name => "clob", :limit => 32768 },
|
137
|
+
:integer => { :name => "int" },
|
138
|
+
:float => { :name => "float" },
|
139
|
+
:datetime => { :name => "timestamp" },
|
140
|
+
:timestamp => { :name => "timestamp" },
|
141
|
+
:time => { :name => "time" },
|
142
|
+
:date => { :name => "date" },
|
143
|
+
:binary => { :name => "blob", :limit => 32768 },
|
144
|
+
:boolean => { :name => "decimal", :limit => 1 }
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
118
148
|
private
|
119
149
|
|
120
150
|
def last_insert_id
|
@@ -128,7 +158,7 @@ begin
|
|
128
158
|
stmt = nil
|
129
159
|
log(sql, name) do
|
130
160
|
stmt = DB2::Statement.new(@connection)
|
131
|
-
stmt.exec_direct("#{sql} with ur")
|
161
|
+
stmt.exec_direct("#{sql.gsub(/=\s*null/i, 'IS NULL')} with ur")
|
132
162
|
end
|
133
163
|
|
134
164
|
rows = []
|
@@ -1,27 +1,30 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'parsedate'
|
3
2
|
|
4
3
|
module ActiveRecord
|
5
4
|
class Base
|
6
5
|
# Establishes a connection to the database that's used by all Active Record objects.
|
7
6
|
def self.mysql_connection(config) # :nodoc:
|
7
|
+
# Only include the MySQL driver if one hasn't already been loaded
|
8
8
|
unless self.class.const_defined?(:Mysql)
|
9
9
|
begin
|
10
|
-
# Only include the MySQL driver if one hasn't already been loaded
|
11
10
|
require_library_or_gem 'mysql'
|
11
|
+
# The C version of mysql returns null fields in each_hash if Mysql::VERSION is defined
|
12
|
+
ConnectionAdapters::MysqlAdapter.null_values_in_each_hash = Mysql.const_defined?(:VERSION)
|
12
13
|
rescue LoadError => cannot_require_mysql
|
13
14
|
# Only use the supplied backup Ruby/MySQL driver if no driver is already in place
|
14
15
|
begin
|
15
16
|
require 'active_record/vendor/mysql'
|
16
17
|
require 'active_record/vendor/mysql411'
|
18
|
+
# The ruby version of mysql returns null fields in each_hash
|
19
|
+
ConnectionAdapters::MysqlAdapter.null_values_in_each_hash = true
|
17
20
|
rescue LoadError
|
18
21
|
raise cannot_require_mysql
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
25
|
+
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
config = config.symbolize_keys
|
25
28
|
host = config[:host]
|
26
29
|
port = config[:port]
|
27
30
|
socket = config[:socket]
|
@@ -41,6 +44,14 @@ module ActiveRecord
|
|
41
44
|
end
|
42
45
|
|
43
46
|
module ConnectionAdapters
|
47
|
+
class MysqlColumn < Column #:nodoc:
|
48
|
+
private
|
49
|
+
def simplified_type(field_type)
|
50
|
+
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase == "tinyint(1)"
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
44
55
|
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
45
56
|
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
|
46
57
|
#
|
@@ -56,19 +67,41 @@ module ActiveRecord
|
|
56
67
|
# * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
|
57
68
|
# * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
|
58
69
|
# * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
|
70
|
+
#
|
71
|
+
# By default, the MysqlAdapter will consider all columns of type tinyint(1)
|
72
|
+
# as boolean. If you wish to disable this emulation (which was the default
|
73
|
+
# behavior in versions 0.13.1 and earlier) you can add the following line
|
74
|
+
# to your environment.rb file:
|
75
|
+
#
|
76
|
+
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
59
77
|
class MysqlAdapter < AbstractAdapter
|
78
|
+
@@emulate_booleans = true
|
79
|
+
cattr_accessor :emulate_booleans
|
80
|
+
|
81
|
+
cattr_accessor :null_values_in_each_hash
|
82
|
+
@@null_values_in_each_hash = false
|
83
|
+
|
60
84
|
LOST_CONNECTION_ERROR_MESSAGES = [
|
61
85
|
"Server shutdown in progress",
|
62
86
|
"Broken pipe",
|
63
87
|
"Lost connection to MySQL server during query",
|
64
88
|
"MySQL server has gone away"
|
65
89
|
]
|
66
|
-
|
67
|
-
def
|
90
|
+
|
91
|
+
def initialize(connection, logger, connection_options=nil)
|
92
|
+
super(connection, logger)
|
93
|
+
@connection_options = connection_options
|
94
|
+
end
|
95
|
+
|
96
|
+
def adapter_name #:nodoc:
|
97
|
+
'MySQL'
|
98
|
+
end
|
99
|
+
|
100
|
+
def supports_migrations? #:nodoc:
|
68
101
|
true
|
69
102
|
end
|
70
103
|
|
71
|
-
def native_database_types
|
104
|
+
def native_database_types #:nodoc
|
72
105
|
{
|
73
106
|
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
74
107
|
:string => { :name => "varchar", :limit => 255 },
|
@@ -84,37 +117,38 @@ module ActiveRecord
|
|
84
117
|
}
|
85
118
|
end
|
86
119
|
|
87
|
-
def initialize(connection, logger, connection_options=nil)
|
88
|
-
super(connection, logger)
|
89
|
-
@connection_options = connection_options
|
90
|
-
end
|
91
120
|
|
92
|
-
|
93
|
-
|
121
|
+
# QUOTING ==================================================
|
122
|
+
|
123
|
+
def quote_column_name(name) #:nodoc:
|
124
|
+
"`#{name}`"
|
94
125
|
end
|
95
126
|
|
96
|
-
def
|
97
|
-
|
127
|
+
def quote_string(string) #:nodoc:
|
128
|
+
Mysql::quote(string)
|
98
129
|
end
|
99
130
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
131
|
+
def quoted_true
|
132
|
+
"1"
|
133
|
+
end
|
134
|
+
|
135
|
+
def quoted_false
|
136
|
+
"0"
|
103
137
|
end
|
104
138
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
139
|
+
|
140
|
+
# DATABASE STATEMENTS ======================================
|
141
|
+
|
142
|
+
def select_all(sql, name = nil) #:nodoc:
|
143
|
+
select(sql, name)
|
110
144
|
end
|
111
145
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
146
|
+
def select_one(sql, name = nil) #:nodoc:
|
147
|
+
result = select(sql, name)
|
148
|
+
result.nil? ? nil : result.first
|
115
149
|
end
|
116
150
|
|
117
|
-
def execute(sql, name = nil, retries = 2)
|
151
|
+
def execute(sql, name = nil, retries = 2) #:nodoc:
|
118
152
|
unless @logger
|
119
153
|
@connection.query(sql)
|
120
154
|
else
|
@@ -136,78 +170,114 @@ module ActiveRecord
|
|
136
170
|
end
|
137
171
|
end
|
138
172
|
|
139
|
-
def
|
173
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
174
|
+
execute(sql, name = nil)
|
175
|
+
id_value || @connection.insert_id
|
176
|
+
end
|
177
|
+
|
178
|
+
def update(sql, name = nil) #:nodoc:
|
140
179
|
execute(sql, name)
|
141
180
|
@connection.affected_rows
|
142
181
|
end
|
143
182
|
|
144
|
-
alias_method :delete, :update
|
183
|
+
alias_method :delete, :update #:nodoc:
|
145
184
|
|
146
185
|
|
147
|
-
def begin_db_transaction
|
186
|
+
def begin_db_transaction #:nodoc:
|
148
187
|
execute "BEGIN"
|
149
188
|
rescue Exception
|
150
189
|
# Transactions aren't supported
|
151
190
|
end
|
152
191
|
|
153
|
-
def commit_db_transaction
|
192
|
+
def commit_db_transaction #:nodoc:
|
154
193
|
execute "COMMIT"
|
155
194
|
rescue Exception
|
156
195
|
# Transactions aren't supported
|
157
196
|
end
|
158
197
|
|
159
|
-
def rollback_db_transaction
|
198
|
+
def rollback_db_transaction #:nodoc:
|
160
199
|
execute "ROLLBACK"
|
161
200
|
rescue Exception
|
162
201
|
# Transactions aren't supported
|
163
202
|
end
|
164
203
|
|
165
204
|
|
166
|
-
def
|
167
|
-
|
205
|
+
def add_limit_offset!(sql, options) #:nodoc
|
206
|
+
if limit = options[:limit]
|
207
|
+
unless offset = options[:offset]
|
208
|
+
sql << " LIMIT #{limit}"
|
209
|
+
else
|
210
|
+
sql << " LIMIT #{offset}, #{limit}"
|
211
|
+
end
|
212
|
+
end
|
168
213
|
end
|
169
214
|
|
170
|
-
def quote_string(string)
|
171
|
-
Mysql::quote(string)
|
172
|
-
end
|
173
215
|
|
216
|
+
# SCHEMA STATEMENTS ========================================
|
174
217
|
|
175
|
-
def structure_dump
|
218
|
+
def structure_dump #:nodoc:
|
176
219
|
select_all("SHOW TABLES").inject("") do |structure, table|
|
177
220
|
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
178
221
|
end
|
179
222
|
end
|
180
223
|
|
181
|
-
def
|
182
|
-
if options[:limit]
|
183
|
-
if options[:offset].blank?
|
184
|
-
sql << " LIMIT #{options[:limit]}"
|
185
|
-
else
|
186
|
-
sql << " LIMIT #{options[:offset]}, #{options[:limit]}"
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def recreate_database(name)
|
224
|
+
def recreate_database(name) #:nodoc:
|
192
225
|
drop_database(name)
|
193
226
|
create_database(name)
|
194
227
|
end
|
195
228
|
|
196
|
-
def
|
229
|
+
def create_database(name) #:nodoc:
|
230
|
+
execute "CREATE DATABASE #{name}"
|
231
|
+
end
|
232
|
+
|
233
|
+
def drop_database(name) #:nodoc:
|
197
234
|
execute "DROP DATABASE IF EXISTS #{name}"
|
198
235
|
end
|
199
236
|
|
200
|
-
|
201
|
-
|
237
|
+
|
238
|
+
def tables(name = nil) #:nodoc:
|
239
|
+
tables = []
|
240
|
+
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
241
|
+
tables
|
242
|
+
end
|
243
|
+
|
244
|
+
def indexes(table_name, name = nil)#:nodoc:
|
245
|
+
indexes = []
|
246
|
+
current_index = nil
|
247
|
+
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
248
|
+
if current_index != row[2]
|
249
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
250
|
+
current_index = row[2]
|
251
|
+
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
252
|
+
end
|
253
|
+
|
254
|
+
indexes.last.columns << row[4]
|
255
|
+
end
|
256
|
+
indexes
|
257
|
+
end
|
258
|
+
|
259
|
+
def columns(table_name, name = nil)#:nodoc:
|
260
|
+
sql = "SHOW FIELDS FROM #{table_name}"
|
261
|
+
columns = []
|
262
|
+
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
263
|
+
columns
|
264
|
+
end
|
265
|
+
|
266
|
+
def create_table(name, options = {}) #:nodoc:
|
267
|
+
super(name, {:options => "ENGINE=InnoDB"}.merge(options))
|
202
268
|
end
|
203
269
|
|
204
|
-
def
|
270
|
+
def rename_table(name, new_name)
|
271
|
+
execute "RENAME TABLE #{name} TO #{new_name}"
|
272
|
+
end
|
273
|
+
|
274
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
205
275
|
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
206
276
|
|
207
277
|
change_column(table_name, column_name, current_type, { :default => default })
|
208
278
|
end
|
209
279
|
|
210
|
-
def change_column(table_name, column_name, type, options = {})
|
280
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
211
281
|
options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
|
212
282
|
|
213
283
|
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
|
@@ -215,22 +285,23 @@ module ActiveRecord
|
|
215
285
|
execute(change_column_sql)
|
216
286
|
end
|
217
287
|
|
218
|
-
def rename_column(table_name, column_name, new_column_name)
|
288
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
219
289
|
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
220
290
|
execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
|
221
291
|
end
|
222
292
|
|
223
|
-
def create_table(name, options = {})
|
224
|
-
super(name, {:options => "ENGINE=InnoDB"}.merge(options))
|
225
|
-
end
|
226
293
|
|
227
294
|
private
|
228
295
|
def select(sql, name = nil)
|
229
296
|
@connection.query_with_result = true
|
230
297
|
result = execute(sql, name)
|
231
298
|
rows = []
|
232
|
-
|
233
|
-
|
299
|
+
if @@null_values_in_each_hash
|
300
|
+
result.each_hash { |row| rows << row }
|
301
|
+
else
|
302
|
+
all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
303
|
+
result.each_hash { |row| rows << all_fields.dup.update(row) }
|
304
|
+
end
|
234
305
|
result.free
|
235
306
|
rows
|
236
307
|
end
|
@@ -15,7 +15,7 @@
|
|
15
15
|
# Do what you want with this code, at your own peril, but if any significant portion of my code
|
16
16
|
# remains then please acknowledge my contribution.
|
17
17
|
# Copyright 2005 Graham Jenkins
|
18
|
-
|
18
|
+
|
19
19
|
require 'active_record/connection_adapters/abstract_adapter'
|
20
20
|
|
21
21
|
begin
|
@@ -77,8 +77,8 @@ begin
|
|
77
77
|
# It has also been tested against a 9i database.
|
78
78
|
#
|
79
79
|
# Usage notes:
|
80
|
-
# * Key generation
|
81
|
-
#
|
80
|
+
# * Key generation assumes a "${table_name}_seq" sequence is available for all tables; the
|
81
|
+
# sequence name can be changed using ActiveRecord::Base.set_sequence_name
|
82
82
|
# * Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
|
83
83
|
# resort to some hacks to get data converted to Date or Time in Ruby.
|
84
84
|
# If the column_name ends in _time it's created as a Ruby Time. Else if the
|
@@ -99,6 +99,10 @@ begin
|
|
99
99
|
# * <tt>:password</tt> -- Defaults to nothing
|
100
100
|
# * <tt>:host</tt> -- Defaults to localhost
|
101
101
|
class OCIAdapter < AbstractAdapter
|
102
|
+
def default_sequence_name(table, column)
|
103
|
+
"#{table}_seq"
|
104
|
+
end
|
105
|
+
|
102
106
|
def quote_string(string)
|
103
107
|
string.gsub(/'/, "''")
|
104
108
|
end
|
@@ -106,31 +110,82 @@ begin
|
|
106
110
|
def quote(value, column = nil)
|
107
111
|
if column and column.type == :binary then %Q{empty_#{ column.sql_type }()}
|
108
112
|
else case value
|
109
|
-
when String
|
113
|
+
when String then %Q{'#{quote_string(value)}'}
|
110
114
|
when NilClass then 'null'
|
111
115
|
when TrueClass then '1'
|
112
116
|
when FalseClass then '0'
|
113
|
-
when Numeric
|
117
|
+
when Numeric then value.to_s
|
114
118
|
when Date, Time then %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
|
115
|
-
else
|
119
|
+
else %Q{'#{quote_string(value.to_yaml)}'}
|
116
120
|
end
|
117
121
|
end
|
118
122
|
end
|
119
123
|
|
124
|
+
# camelCase column names need to be quoted; not that anyone using Oracle
|
125
|
+
# would really do this, but handling this case means we pass the test...
|
126
|
+
def quote_column_name(name)
|
127
|
+
name =~ /[A-Z]/ ? "\"#{name}\"" : name
|
128
|
+
end
|
129
|
+
|
130
|
+
def structure_dump
|
131
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
132
|
+
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
select_all("select table_name from user_tables").inject(s) do |structure, table|
|
136
|
+
ddl = "create table #{table.to_a.first.last} (\n "
|
137
|
+
cols = select_all(%Q{
|
138
|
+
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
|
139
|
+
from user_tab_columns
|
140
|
+
where table_name = '#{table.to_a.first.last}'
|
141
|
+
order by column_id
|
142
|
+
}).map do |row|
|
143
|
+
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
144
|
+
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
|
145
|
+
col << "(#{row['data_precision'].to_i}"
|
146
|
+
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
|
147
|
+
col << ')'
|
148
|
+
elsif row['data_type'].include?('CHAR')
|
149
|
+
col << "(#{row['data_length'].to_i})"
|
150
|
+
end
|
151
|
+
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
152
|
+
col << ' not null' if row['nullable'] == 'N'
|
153
|
+
col
|
154
|
+
end
|
155
|
+
ddl << cols.join(",\n ")
|
156
|
+
ddl << ");\n\n"
|
157
|
+
structure << ddl
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def structure_drop
|
162
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
|
163
|
+
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
164
|
+
end
|
165
|
+
|
166
|
+
select_all("select table_name from user_tables").inject(s) do |drop, table|
|
167
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
120
171
|
def select_all(sql, name = nil)
|
121
172
|
offset = sql =~ /OFFSET (\d+)$/ ? $1.to_i : 0
|
122
173
|
sql, limit = $1, $2.to_i if sql =~ /(.*)(?: LIMIT[= ](\d+))(\s*OFFSET \d+)?$/
|
174
|
+
|
123
175
|
if limit
|
124
176
|
sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
125
177
|
elsif offset > 0
|
126
178
|
sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
127
179
|
end
|
180
|
+
|
128
181
|
cursor = log(sql, name) { @connection.exec sql }
|
129
|
-
cols = cursor.get_col_names.map { |x| x
|
182
|
+
cols = cursor.get_col_names.map { |x| oci_downcase(x) }
|
130
183
|
rows = []
|
184
|
+
|
131
185
|
while row = cursor.fetch
|
132
186
|
hash = Hash.new
|
133
|
-
|
187
|
+
|
188
|
+
cols.each_with_index do |col, i|
|
134
189
|
hash[col] = case row[i]
|
135
190
|
when OCI8::LOB
|
136
191
|
name == 'Writable Large Object' ? row[i]: row[i].read
|
@@ -139,9 +194,11 @@ begin
|
|
139
194
|
row[i].to_date : row[i].to_time
|
140
195
|
else row[i]
|
141
196
|
end unless col == 'raw_rnum_'
|
142
|
-
|
197
|
+
end
|
198
|
+
|
143
199
|
rows << hash
|
144
200
|
end
|
201
|
+
|
145
202
|
rows
|
146
203
|
ensure
|
147
204
|
cursor.close if cursor
|
@@ -153,25 +210,34 @@ begin
|
|
153
210
|
end
|
154
211
|
|
155
212
|
def columns(table_name, name = nil)
|
156
|
-
|
213
|
+
select_all(%Q{
|
157
214
|
select column_name, data_type, data_default, data_length, data_scale
|
158
|
-
from
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
215
|
+
from user_catalog cat, user_synonyms syn, all_tab_columns col
|
216
|
+
where cat.table_name = '#{table_name.upcase}'
|
217
|
+
and syn.synonym_name (+)= cat.table_name
|
218
|
+
and col.owner = nvl(syn.table_owner, user)
|
219
|
+
and col.table_name = nvl(syn.table_name, cat.table_name)}
|
220
|
+
).map do |row|
|
221
|
+
OCIColumn.new(
|
222
|
+
oci_downcase(row['column_name']),
|
223
|
+
row['data_default'],
|
224
|
+
row['data_length'],
|
225
|
+
row['data_type'],
|
226
|
+
row['data_scale']
|
227
|
+
)
|
228
|
+
end
|
164
229
|
end
|
165
230
|
|
166
|
-
def insert(sql, name = nil, pk = nil, id_value = nil)
|
231
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
167
232
|
if pk.nil? # Who called us? What does the sql look like? No idea!
|
168
233
|
execute sql, name
|
169
234
|
elsif id_value # Pre-assigned id
|
170
235
|
log(sql, name) { @connection.exec sql }
|
171
236
|
else # Assume the sql contains a bind-variable for the id
|
172
|
-
id_value = select_one("select
|
237
|
+
id_value = select_one("select #{sequence_name}.nextval id from dual")['id']
|
173
238
|
log(sql, name) { @connection.exec sql, id_value }
|
174
239
|
end
|
240
|
+
|
175
241
|
id_value
|
176
242
|
end
|
177
243
|
|
@@ -201,18 +267,31 @@ begin
|
|
201
267
|
def adapter_name()
|
202
268
|
'OCI'
|
203
269
|
end
|
270
|
+
|
271
|
+
private
|
272
|
+
# Oracle column names by default are case-insensitive, but treated as upcase;
|
273
|
+
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
274
|
+
# their column names when creating Oracle tables, which makes then case-sensitive.
|
275
|
+
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
276
|
+
# camelCase column name. I imagine other dbs handle this different, since there's a
|
277
|
+
# unit test that's currently failing test_oci.
|
278
|
+
def oci_downcase(column_name)
|
279
|
+
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
280
|
+
end
|
204
281
|
end
|
205
282
|
end
|
206
283
|
end
|
207
284
|
|
208
285
|
module ActiveRecord
|
209
286
|
class Base
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
287
|
+
class << self
|
288
|
+
def oci_connection(config) #:nodoc:
|
289
|
+
conn = OCI8.new config[:username], config[:password], config[:host]
|
290
|
+
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
291
|
+
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
|
292
|
+
conn.autocommit = true
|
293
|
+
ConnectionAdapters::OCIAdapter.new conn, logger
|
294
|
+
end
|
216
295
|
end
|
217
296
|
|
218
297
|
alias :attributes_with_quotes_pre_oci :attributes_with_quotes #:nodoc:
|
@@ -231,9 +310,10 @@ begin
|
|
231
310
|
|
232
311
|
# After setting large objects to empty, select the OCI8::LOB and write back the data
|
233
312
|
def write_lobs() #:nodoc:
|
234
|
-
if connection.
|
313
|
+
if connection.is_a?(ConnectionAdapters::OCIAdapter)
|
235
314
|
self.class.columns.select { |c| c.type == :binary }.each { |c|
|
236
|
-
|
315
|
+
value = self[c.name]
|
316
|
+
next if value.nil? || (value == '')
|
237
317
|
lob = connection.select_one(
|
238
318
|
"select #{ c.name} from #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
|
239
319
|
'Writable Large Object'
|