activerecord 3.0.20 → 3.1.0.beta1
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 +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -12,11 +12,15 @@ module ActiveRecord
|
|
12
12
|
|
13
13
|
# Truncates a table alias according to the limits of the current adapter.
|
14
14
|
def table_alias_for(table_name)
|
15
|
-
table_name[0
|
15
|
+
table_name[0...table_alias_length].gsub(/\./, '_')
|
16
16
|
end
|
17
17
|
|
18
18
|
# def tables(name = nil) end
|
19
19
|
|
20
|
+
# Checks to see if the table +table_name+ exists on the database.
|
21
|
+
#
|
22
|
+
# === Example
|
23
|
+
# table_exists?(:developers)
|
20
24
|
def table_exists?(table_name)
|
21
25
|
tables.include?(table_name.to_s)
|
22
26
|
end
|
@@ -24,7 +28,7 @@ module ActiveRecord
|
|
24
28
|
# Returns an array of indexes for the given table.
|
25
29
|
# def indexes(table_name, name = nil) end
|
26
30
|
|
27
|
-
# Checks to see if an index exists on a table for a given index definition
|
31
|
+
# Checks to see if an index exists on a table for a given index definition.
|
28
32
|
#
|
29
33
|
# === Examples
|
30
34
|
# # Check an index exists
|
@@ -151,10 +155,10 @@ module ActiveRecord
|
|
151
155
|
#
|
152
156
|
# See also TableDefinition#column for details on how to create columns.
|
153
157
|
def create_table(table_name, options = {})
|
154
|
-
|
155
|
-
|
158
|
+
td = table_definition
|
159
|
+
td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
156
160
|
|
157
|
-
yield
|
161
|
+
yield td if block_given?
|
158
162
|
|
159
163
|
if options[:force] && table_exists?(table_name)
|
160
164
|
drop_table(table_name, options)
|
@@ -162,7 +166,7 @@ module ActiveRecord
|
|
162
166
|
|
163
167
|
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
164
168
|
create_sql << "#{quote_table_name(table_name)} ("
|
165
|
-
create_sql <<
|
169
|
+
create_sql << td.to_sql
|
166
170
|
create_sql << ") #{options[:options]}"
|
167
171
|
execute create_sql
|
168
172
|
end
|
@@ -176,6 +180,13 @@ module ActiveRecord
|
|
176
180
|
# # Other column alterations here
|
177
181
|
# end
|
178
182
|
#
|
183
|
+
# The +options+ hash can include the following keys:
|
184
|
+
# [<tt>:bulk</tt>]
|
185
|
+
# Set this to true to make this a bulk alter query, such as
|
186
|
+
# ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
|
187
|
+
#
|
188
|
+
# Defaults to false.
|
189
|
+
#
|
179
190
|
# ===== Examples
|
180
191
|
# ====== Add a column
|
181
192
|
# change_table(:suppliers) do |t|
|
@@ -224,8 +235,14 @@ module ActiveRecord
|
|
224
235
|
#
|
225
236
|
# See also Table for details on
|
226
237
|
# all of the various column transformation
|
227
|
-
def change_table(table_name)
|
228
|
-
|
238
|
+
def change_table(table_name, options = {})
|
239
|
+
if supports_bulk_alter? && options[:bulk]
|
240
|
+
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
241
|
+
yield Table.new(table_name, recorder)
|
242
|
+
bulk_change_table(table_name, recorder.commands)
|
243
|
+
else
|
244
|
+
yield Table.new(table_name, self)
|
245
|
+
end
|
229
246
|
end
|
230
247
|
|
231
248
|
# Renames a table.
|
@@ -253,10 +270,7 @@ module ActiveRecord
|
|
253
270
|
# remove_column(:suppliers, :qualification)
|
254
271
|
# remove_columns(:suppliers, :qualification, :experience)
|
255
272
|
def remove_column(table_name, *column_names)
|
256
|
-
|
257
|
-
column_names.flatten.each do |column_name|
|
258
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
|
259
|
-
end
|
273
|
+
columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
|
260
274
|
end
|
261
275
|
alias :remove_columns :remove_column
|
262
276
|
|
@@ -269,12 +283,11 @@ module ActiveRecord
|
|
269
283
|
raise NotImplementedError, "change_column is not implemented"
|
270
284
|
end
|
271
285
|
|
272
|
-
# Sets a new default value for a column.
|
273
|
-
# value to +NULL+, you are out of luck. You need to
|
274
|
-
# DatabaseStatements#execute the appropriate SQL statement yourself.
|
286
|
+
# Sets a new default value for a column.
|
275
287
|
# ===== Examples
|
276
288
|
# change_column_default(:suppliers, :qualification, 'new')
|
277
289
|
# change_column_default(:accounts, :authorized, 1)
|
290
|
+
# change_column_default(:users, :email, nil)
|
278
291
|
def change_column_default(table_name, column_name, default)
|
279
292
|
raise NotImplementedError, "change_column_default is not implemented"
|
280
293
|
end
|
@@ -327,25 +340,8 @@ module ActiveRecord
|
|
327
340
|
#
|
328
341
|
# Note: SQLite doesn't support index length
|
329
342
|
def add_index(table_name, column_name, options = {})
|
330
|
-
|
331
|
-
index_name
|
332
|
-
|
333
|
-
if Hash === options # legacy support, since this param was a string
|
334
|
-
index_type = options[:unique] ? "UNIQUE" : ""
|
335
|
-
index_name = options[:name].to_s if options.key?(:name)
|
336
|
-
else
|
337
|
-
index_type = options
|
338
|
-
end
|
339
|
-
|
340
|
-
if index_name.length > index_name_length
|
341
|
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
|
342
|
-
end
|
343
|
-
if index_name_exists?(table_name, index_name, false)
|
344
|
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
345
|
-
end
|
346
|
-
quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
|
347
|
-
|
348
|
-
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
|
343
|
+
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
344
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
|
349
345
|
end
|
350
346
|
|
351
347
|
# Remove the given index from the table.
|
@@ -359,11 +355,7 @@ module ActiveRecord
|
|
359
355
|
# Remove the index named by_branch_party in the accounts table.
|
360
356
|
# remove_index :accounts, :name => :by_branch_party
|
361
357
|
def remove_index(table_name, options = {})
|
362
|
-
|
363
|
-
unless index_name_exists?(table_name, index_name, true)
|
364
|
-
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
365
|
-
end
|
366
|
-
remove_index!(table_name, index_name)
|
358
|
+
remove_index!(table_name, index_name_for_remove(table_name, options))
|
367
359
|
end
|
368
360
|
|
369
361
|
def remove_index!(table_name, index_name) #:nodoc:
|
@@ -442,12 +434,14 @@ module ActiveRecord
|
|
442
434
|
end
|
443
435
|
end
|
444
436
|
|
445
|
-
def assume_migrated_upto_version(version,
|
437
|
+
def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
|
438
|
+
migrations_paths = Array.wrap(migrations_paths)
|
446
439
|
version = version.to_i
|
447
440
|
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
|
448
441
|
|
449
442
|
migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
|
450
|
-
|
443
|
+
paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
|
444
|
+
versions = Dir[*paths].map do |filename|
|
451
445
|
filename.split('/').last.split('_').first.to_i
|
452
446
|
end
|
453
447
|
|
@@ -467,7 +461,7 @@ module ActiveRecord
|
|
467
461
|
end
|
468
462
|
|
469
463
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
470
|
-
if native = native_database_types[type]
|
464
|
+
if native = native_database_types[type.to_sym]
|
471
465
|
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
|
472
466
|
|
473
467
|
if type == :decimal # ignore limit, use precision and scale
|
@@ -534,6 +528,50 @@ module ActiveRecord
|
|
534
528
|
def options_include_default?(options)
|
535
529
|
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
536
530
|
end
|
531
|
+
|
532
|
+
def add_index_options(table_name, column_name, options = {})
|
533
|
+
column_names = Array.wrap(column_name)
|
534
|
+
index_name = index_name(table_name, :column => column_names)
|
535
|
+
|
536
|
+
if Hash === options # legacy support, since this param was a string
|
537
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
538
|
+
index_name = options[:name].to_s if options.key?(:name)
|
539
|
+
else
|
540
|
+
index_type = options
|
541
|
+
end
|
542
|
+
|
543
|
+
if index_name.length > index_name_length
|
544
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
|
545
|
+
end
|
546
|
+
if index_name_exists?(table_name, index_name, false)
|
547
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
548
|
+
end
|
549
|
+
index_columns = quoted_columns_for_index(column_names, options).join(", ")
|
550
|
+
|
551
|
+
[index_name, index_type, index_columns]
|
552
|
+
end
|
553
|
+
|
554
|
+
def index_name_for_remove(table_name, options = {})
|
555
|
+
index_name = index_name(table_name, options)
|
556
|
+
|
557
|
+
unless index_name_exists?(table_name, index_name, true)
|
558
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
559
|
+
end
|
560
|
+
|
561
|
+
index_name
|
562
|
+
end
|
563
|
+
|
564
|
+
def columns_for_remove(table_name, *column_names)
|
565
|
+
column_names = column_names.flatten
|
566
|
+
|
567
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
|
568
|
+
column_names.map {|column_name| quote_column_name(column_name) }
|
569
|
+
end
|
570
|
+
|
571
|
+
private
|
572
|
+
def table_definition
|
573
|
+
TableDefinition.new(self)
|
574
|
+
end
|
537
575
|
end
|
538
576
|
end
|
539
577
|
end
|
@@ -4,6 +4,7 @@ require 'bigdecimal/util'
|
|
4
4
|
require 'active_support/core_ext/benchmark'
|
5
5
|
|
6
6
|
# TODO: Autoload these files
|
7
|
+
require 'active_record/connection_adapters/column'
|
7
8
|
require 'active_record/connection_adapters/abstract/schema_definitions'
|
8
9
|
require 'active_record/connection_adapters/abstract/schema_statements'
|
9
10
|
require 'active_record/connection_adapters/abstract/database_statements'
|
@@ -12,6 +13,7 @@ require 'active_record/connection_adapters/abstract/connection_pool'
|
|
12
13
|
require 'active_record/connection_adapters/abstract/connection_specification'
|
13
14
|
require 'active_record/connection_adapters/abstract/query_cache'
|
14
15
|
require 'active_record/connection_adapters/abstract/database_limits'
|
16
|
+
require 'active_record/result'
|
15
17
|
|
16
18
|
module ActiveRecord
|
17
19
|
module ConnectionAdapters # :nodoc:
|
@@ -40,61 +42,60 @@ module ActiveRecord
|
|
40
42
|
@active = nil
|
41
43
|
@connection, @logger = connection, logger
|
42
44
|
@query_cache_enabled = false
|
43
|
-
@query_cache = {}
|
45
|
+
@query_cache = Hash.new { |h,sql| h[sql] = {} }
|
44
46
|
@instrumenter = ActiveSupport::Notifications.instrumenter
|
45
47
|
end
|
46
48
|
|
47
|
-
# Returns the human-readable name of the adapter.
|
49
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
48
50
|
# can always use downcase if needed.
|
49
51
|
def adapter_name
|
50
52
|
'Abstract'
|
51
53
|
end
|
52
54
|
|
53
|
-
# Does this adapter support migrations?
|
55
|
+
# Does this adapter support migrations? Backend specific, as the
|
54
56
|
# abstract adapter always returns +false+.
|
55
57
|
def supports_migrations?
|
56
58
|
false
|
57
59
|
end
|
58
60
|
|
59
61
|
# Can this adapter determine the primary key for tables not attached
|
60
|
-
# to an Active Record class, such as join tables?
|
62
|
+
# to an Active Record class, such as join tables? Backend specific, as
|
61
63
|
# the abstract adapter always returns +false+.
|
62
64
|
def supports_primary_key?
|
63
65
|
false
|
64
66
|
end
|
65
67
|
|
66
|
-
# Does this adapter support using DISTINCT within COUNT?
|
68
|
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
67
69
|
# for all adapters except sqlite.
|
68
70
|
def supports_count_distinct?
|
69
71
|
true
|
70
72
|
end
|
71
73
|
|
72
|
-
# Does this adapter support DDL rollbacks in transactions?
|
73
|
-
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
|
74
|
-
# SQL Server, and others support this.
|
74
|
+
# Does this adapter support DDL rollbacks in transactions? That is, would
|
75
|
+
# CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
|
76
|
+
# SQL Server, and others support this. MySQL and others do not.
|
75
77
|
def supports_ddl_transactions?
|
76
78
|
false
|
77
79
|
end
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
+
def supports_bulk_alter?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
# Does this adapter support savepoints? PostgreSQL and MySQL do,
|
86
|
+
# SQLite < 3.6.8 does not.
|
81
87
|
def supports_savepoints?
|
82
88
|
false
|
83
89
|
end
|
84
90
|
|
85
91
|
# Should primary key values be selected from their corresponding
|
86
|
-
# sequence before the insert statement?
|
92
|
+
# sequence before the insert statement? If true, next_sequence_value
|
87
93
|
# is called before each insert to set the record's primary key.
|
88
94
|
# This is false for all adapters but Firebird.
|
89
95
|
def prefetch_primary_key?(table_name = nil)
|
90
96
|
false
|
91
97
|
end
|
92
98
|
|
93
|
-
# Does this adapter restrict the number of ids you can use in a list. Oracle has a limit of 1000.
|
94
|
-
def ids_in_list_limit
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
99
|
# QUOTING ==================================================
|
99
100
|
|
100
101
|
# Override to return the quoted table name. Defaults to column quoting.
|
@@ -102,6 +103,12 @@ module ActiveRecord
|
|
102
103
|
quote_column_name(name)
|
103
104
|
end
|
104
105
|
|
106
|
+
# Returns a bind substitution value given a +column+ and list of current
|
107
|
+
# +binds+
|
108
|
+
def substitute_at(column, index)
|
109
|
+
Arel.sql '?'
|
110
|
+
end
|
111
|
+
|
105
112
|
# REFERENTIAL INTEGRITY ====================================
|
106
113
|
|
107
114
|
# Override to turn off referential integrity while executing <tt>&block</tt>.
|
@@ -140,6 +147,13 @@ module ActiveRecord
|
|
140
147
|
# this should be overridden by concrete adapters
|
141
148
|
end
|
142
149
|
|
150
|
+
###
|
151
|
+
# Clear any caching the database adapter may be doing, for example
|
152
|
+
# clearing the prepared statement cache. This is database specific.
|
153
|
+
def clear_cache!
|
154
|
+
# this should be overridden by concrete adapters
|
155
|
+
end
|
156
|
+
|
143
157
|
# Returns true if its required to reload the connection between requests for development mode.
|
144
158
|
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
145
159
|
def requires_reloading?
|
@@ -189,24 +203,29 @@ module ActiveRecord
|
|
189
203
|
def release_savepoint
|
190
204
|
end
|
191
205
|
|
206
|
+
def case_sensitive_modifier(node)
|
207
|
+
node
|
208
|
+
end
|
209
|
+
|
192
210
|
def current_savepoint_name
|
193
211
|
"active_record_#{open_transactions}"
|
194
212
|
end
|
195
213
|
|
196
214
|
protected
|
197
215
|
|
198
|
-
def log(sql, name)
|
199
|
-
|
200
|
-
|
201
|
-
:sql
|
202
|
-
|
203
|
-
|
216
|
+
def log(sql, name = "SQL", binds = [])
|
217
|
+
@instrumenter.instrument(
|
218
|
+
"sql.active_record",
|
219
|
+
:sql => sql,
|
220
|
+
:name => name,
|
221
|
+
:connection_id => object_id,
|
222
|
+
:binds => binds) { yield }
|
204
223
|
rescue Exception => e
|
205
224
|
message = "#{e.class.name}: #{e.message}: #{sql}"
|
206
225
|
@logger.debug message if @logger
|
207
|
-
|
208
|
-
|
209
|
-
raise
|
226
|
+
exception = translate_exception(e, message)
|
227
|
+
exception.set_backtrace e.backtrace
|
228
|
+
raise exception
|
210
229
|
end
|
211
230
|
|
212
231
|
def translate_exception(e, message)
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# :stopdoc:
|
3
|
+
module ConnectionAdapters
|
4
|
+
# An abstract definition of a column in a table.
|
5
|
+
class Column
|
6
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
|
7
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
|
8
|
+
|
9
|
+
module Format
|
10
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
11
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
|
15
|
+
attr_accessor :primary, :coder
|
16
|
+
|
17
|
+
alias :encoded? :coder
|
18
|
+
|
19
|
+
# Instantiates a new column in the table.
|
20
|
+
#
|
21
|
+
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
|
22
|
+
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
|
23
|
+
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
|
24
|
+
# <tt>company_name varchar(60)</tt>.
|
25
|
+
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
|
26
|
+
# +null+ determines if this column allows +NULL+ values.
|
27
|
+
def initialize(name, default, sql_type = nil, null = true)
|
28
|
+
@name = name
|
29
|
+
@sql_type = sql_type
|
30
|
+
@null = null
|
31
|
+
@limit = extract_limit(sql_type)
|
32
|
+
@precision = extract_precision(sql_type)
|
33
|
+
@scale = extract_scale(sql_type)
|
34
|
+
@type = simplified_type(sql_type)
|
35
|
+
@default = extract_default(default)
|
36
|
+
@primary = nil
|
37
|
+
@coder = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns +true+ if the column is either of type string or text.
|
41
|
+
def text?
|
42
|
+
type == :string || type == :text
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns +true+ if the column is either of type integer, float or decimal.
|
46
|
+
def number?
|
47
|
+
type == :integer || type == :float || type == :decimal
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_default?
|
51
|
+
!default.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the Ruby class that corresponds to the abstract data type.
|
55
|
+
def klass
|
56
|
+
case type
|
57
|
+
when :integer then Fixnum
|
58
|
+
when :float then Float
|
59
|
+
when :decimal then BigDecimal
|
60
|
+
when :datetime, :timestamp, :time then Time
|
61
|
+
when :date then Date
|
62
|
+
when :text, :string, :binary then String
|
63
|
+
when :boolean then Object
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Casts value (which is a String) to an appropriate instance.
|
68
|
+
def type_cast(value)
|
69
|
+
return nil if value.nil?
|
70
|
+
return coder.load(value) if encoded?
|
71
|
+
|
72
|
+
klass = self.class
|
73
|
+
|
74
|
+
case type
|
75
|
+
when :string, :text then value
|
76
|
+
when :integer then value.to_i rescue value ? 1 : 0
|
77
|
+
when :float then value.to_f
|
78
|
+
when :decimal then klass.value_to_decimal(value)
|
79
|
+
when :datetime, :timestamp then klass.string_to_time(value)
|
80
|
+
when :time then klass.string_to_dummy_time(value)
|
81
|
+
when :date then klass.string_to_date(value)
|
82
|
+
when :binary then klass.binary_to_string(value)
|
83
|
+
when :boolean then klass.value_to_boolean(value)
|
84
|
+
else value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def type_cast_code(var_name)
|
89
|
+
klass = self.class.name
|
90
|
+
|
91
|
+
case type
|
92
|
+
when :string, :text then var_name
|
93
|
+
when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
|
94
|
+
when :float then "#{var_name}.to_f"
|
95
|
+
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
|
96
|
+
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
|
97
|
+
when :time then "#{klass}.string_to_dummy_time(#{var_name})"
|
98
|
+
when :date then "#{klass}.string_to_date(#{var_name})"
|
99
|
+
when :binary then "#{klass}.binary_to_string(#{var_name})"
|
100
|
+
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
|
101
|
+
else var_name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the human name of the column name.
|
106
|
+
#
|
107
|
+
# ===== Examples
|
108
|
+
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
|
109
|
+
def human_name
|
110
|
+
Base.human_attribute_name(@name)
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_default(default)
|
114
|
+
type_cast(default)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Used to convert from Strings to BLOBs
|
118
|
+
def string_to_binary(value)
|
119
|
+
self.class.string_to_binary(value)
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
# Used to convert from Strings to BLOBs
|
124
|
+
def string_to_binary(value)
|
125
|
+
value
|
126
|
+
end
|
127
|
+
|
128
|
+
# Used to convert from BLOBs to Strings
|
129
|
+
def binary_to_string(value)
|
130
|
+
value
|
131
|
+
end
|
132
|
+
|
133
|
+
def string_to_date(string)
|
134
|
+
return string unless string.is_a?(String)
|
135
|
+
return nil if string.empty?
|
136
|
+
|
137
|
+
fast_string_to_date(string) || fallback_string_to_date(string)
|
138
|
+
end
|
139
|
+
|
140
|
+
def string_to_time(string)
|
141
|
+
return string unless string.is_a?(String)
|
142
|
+
return nil if string.empty?
|
143
|
+
|
144
|
+
fast_string_to_time(string) || fallback_string_to_time(string)
|
145
|
+
end
|
146
|
+
|
147
|
+
def string_to_dummy_time(string)
|
148
|
+
return string unless string.is_a?(String)
|
149
|
+
return nil if string.empty?
|
150
|
+
|
151
|
+
string_to_time "2000-01-01 #{string}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# convert something to a boolean
|
155
|
+
def value_to_boolean(value)
|
156
|
+
if value.is_a?(String) && value.blank?
|
157
|
+
nil
|
158
|
+
else
|
159
|
+
TRUE_VALUES.include?(value)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# convert something to a BigDecimal
|
164
|
+
def value_to_decimal(value)
|
165
|
+
# Using .class is faster than .is_a? and
|
166
|
+
# subclasses of BigDecimal will be handled
|
167
|
+
# in the else clause
|
168
|
+
if value.class == BigDecimal
|
169
|
+
value
|
170
|
+
elsif value.respond_to?(:to_d)
|
171
|
+
value.to_d
|
172
|
+
else
|
173
|
+
value.to_s.to_d
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
# '0.123456' -> 123456
|
179
|
+
# '1.123456' -> 123456
|
180
|
+
def microseconds(time)
|
181
|
+
((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
182
|
+
end
|
183
|
+
|
184
|
+
def new_date(year, mon, mday)
|
185
|
+
if year && year != 0
|
186
|
+
Date.new(year, mon, mday) rescue nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def new_time(year, mon, mday, hour, min, sec, microsec)
|
191
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
192
|
+
return nil if year.nil? || year == 0
|
193
|
+
|
194
|
+
Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def fast_string_to_date(string)
|
198
|
+
if string =~ Format::ISO_DATE
|
199
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Doesn't handle time zones.
|
204
|
+
def fast_string_to_time(string)
|
205
|
+
if string =~ Format::ISO_DATETIME
|
206
|
+
microsec = ($7.to_f * 1_000_000).to_i
|
207
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def fallback_string_to_date(string)
|
212
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
213
|
+
end
|
214
|
+
|
215
|
+
def fallback_string_to_time(string)
|
216
|
+
time_hash = Date._parse(string)
|
217
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
218
|
+
|
219
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
def extract_limit(sql_type)
|
225
|
+
$1.to_i if sql_type =~ /\((.*)\)/
|
226
|
+
end
|
227
|
+
|
228
|
+
def extract_precision(sql_type)
|
229
|
+
$2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
|
230
|
+
end
|
231
|
+
|
232
|
+
def extract_scale(sql_type)
|
233
|
+
case sql_type
|
234
|
+
when /^(numeric|decimal|number)\((\d+)\)/i then 0
|
235
|
+
when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def simplified_type(field_type)
|
240
|
+
case field_type
|
241
|
+
when /int/i
|
242
|
+
:integer
|
243
|
+
when /float|double/i
|
244
|
+
:float
|
245
|
+
when /decimal|numeric|number/i
|
246
|
+
extract_scale(field_type) == 0 ? :integer : :decimal
|
247
|
+
when /datetime/i
|
248
|
+
:datetime
|
249
|
+
when /timestamp/i
|
250
|
+
:timestamp
|
251
|
+
when /time/i
|
252
|
+
:time
|
253
|
+
when /date/i
|
254
|
+
:date
|
255
|
+
when /clob/i, /text/i
|
256
|
+
:text
|
257
|
+
when /blob/i, /binary/i
|
258
|
+
:binary
|
259
|
+
when /char/i, /string/i
|
260
|
+
:string
|
261
|
+
when /boolean/i
|
262
|
+
:boolean
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
# :startdoc:
|
268
|
+
end
|