activerecord 2.3.5 → 2.3.6
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 +33 -0
- data/Rakefile +1 -1
- data/examples/performance.sql +85 -0
- data/lib/active_record.rb +1 -2
- data/lib/active_record/association_preload.rb +9 -2
- data/lib/active_record/associations.rb +48 -38
- data/lib/active_record/associations/association_collection.rb +15 -11
- data/lib/active_record/associations/association_proxy.rb +16 -6
- data/lib/active_record/associations/belongs_to_association.rb +11 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -10
- data/lib/active_record/associations/has_many_association.rb +5 -0
- data/lib/active_record/associations/has_many_through_association.rb +5 -5
- data/lib/active_record/associations/has_one_association.rb +10 -1
- data/lib/active_record/attribute_methods.rb +5 -1
- data/lib/active_record/autosave_association.rb +66 -35
- data/lib/active_record/base.rb +77 -36
- data/lib/active_record/batches.rb +13 -9
- data/lib/active_record/calculations.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +64 -10
- data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +31 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -66
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
- data/lib/active_record/dirty.rb +2 -2
- data/lib/active_record/fixtures.rb +1 -0
- data/lib/active_record/locking/optimistic.rb +34 -1
- data/lib/active_record/migration.rb +5 -0
- data/lib/active_record/nested_attributes.rb +64 -52
- data/lib/active_record/reflection.rb +66 -1
- data/lib/active_record/schema.rb +5 -1
- data/lib/active_record/schema_dumper.rb +3 -0
- data/lib/active_record/serializers/json_serializer.rb +1 -1
- data/lib/active_record/validations.rb +13 -1
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_mysql.rb +22 -0
- data/test/cases/associations/belongs_to_associations_test.rb +13 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +8 -7
- data/test/cases/associations/eager_test.rb +7 -1
- data/test/cases/associations/has_many_associations_test.rb +26 -0
- data/test/cases/associations/inverse_associations_test.rb +566 -0
- data/test/cases/associations_test.rb +10 -0
- data/test/cases/autosave_association_test.rb +86 -10
- data/test/cases/base_test.rb +29 -0
- data/test/cases/batches_test.rb +20 -0
- data/test/cases/calculations_test.rb +2 -3
- data/test/cases/encoding_test.rb +6 -0
- data/test/cases/finder_test.rb +19 -3
- data/test/cases/fixtures_test.rb +5 -0
- data/test/cases/json_serialization_test.rb +14 -0
- data/test/cases/locking_test.rb +48 -3
- data/test/cases/migration_test.rb +115 -0
- data/test/cases/modules_test.rb +28 -0
- data/test/cases/named_scope_test.rb +1 -1
- data/test/cases/nested_attributes_test.rb +239 -7
- data/test/cases/query_cache_test.rb +7 -1
- data/test/cases/reflection_test.rb +47 -7
- data/test/cases/schema_test_postgresql.rb +2 -2
- data/test/cases/validations_i18n_test.rb +6 -36
- data/test/cases/validations_test.rb +33 -1
- data/test/cases/yaml_serialization_test.rb +11 -0
- data/test/fixtures/faces.yml +11 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/interests.yml +33 -0
- data/test/fixtures/men.yml +5 -0
- data/test/fixtures/zines.yml +5 -0
- data/test/models/author.rb +3 -0
- data/test/models/bird.rb +6 -0
- data/test/models/company_in_module.rb +17 -0
- data/test/models/event_author.rb +5 -0
- data/test/models/face.rb +7 -0
- data/test/models/interest.rb +5 -0
- data/test/models/invoice.rb +4 -0
- data/test/models/line_item.rb +3 -0
- data/test/models/man.rb +9 -0
- data/test/models/parrot.rb +6 -0
- data/test/models/pirate.rb +10 -0
- data/test/models/ship.rb +10 -1
- data/test/models/ship_part.rb +3 -1
- data/test/models/zine.rb +3 -0
- data/test/schema/schema.rb +41 -0
- metadata +37 -11
- data/lib/active_record/i18n_interpolation_deprecation.rb +0 -26
@@ -59,19 +59,23 @@ module ActiveRecord
|
|
59
59
|
start = options.delete(:start).to_i
|
60
60
|
batch_size = options.delete(:batch_size) || 1000
|
61
61
|
|
62
|
-
|
63
|
-
|
62
|
+
proxy = scoped(options.merge(:order => batch_order, :limit => batch_size))
|
63
|
+
records = proxy.find(:all, :conditions => [ "#{table_name}.#{primary_key} >= ?", start ])
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
while records.any?
|
66
|
+
yield records
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
break if records.size < batch_size
|
69
|
+
|
70
|
+
last_value = records.last.id
|
71
|
+
|
72
|
+
raise "You must include the primary key if you define a select" unless last_value.present?
|
73
|
+
|
74
|
+
records = proxy.find(:all, :conditions => [ "#{table_name}.#{primary_key} > ?", last_value ])
|
71
75
|
end
|
72
76
|
end
|
73
|
-
|
74
|
-
|
77
|
+
|
78
|
+
|
75
79
|
private
|
76
80
|
def batch_order
|
77
81
|
"#{table_name}.#{primary_key} ASC"
|
@@ -294,12 +294,15 @@ module ActiveRecord
|
|
294
294
|
end
|
295
295
|
|
296
296
|
def type_cast_calculated_value(value, column, operation = nil)
|
297
|
-
|
298
|
-
|
297
|
+
if value.is_a?(String) || value.nil?
|
298
|
+
case operation.to_s.downcase
|
299
299
|
when 'count' then value.to_i
|
300
300
|
when 'sum' then type_cast_using_column(value || '0', column)
|
301
|
-
when 'avg'
|
301
|
+
when 'avg' then value.try(:to_d)
|
302
302
|
else type_cast_using_column(value, column)
|
303
|
+
end
|
304
|
+
else
|
305
|
+
value
|
303
306
|
end
|
304
307
|
end
|
305
308
|
|
@@ -10,8 +10,8 @@ module ActiveRecord
|
|
10
10
|
##
|
11
11
|
# :singleton-method:
|
12
12
|
# The connection handler
|
13
|
-
|
14
|
-
|
13
|
+
superclass_delegating_accessor :connection_handler
|
14
|
+
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
|
15
15
|
|
16
16
|
# Returns the connection currently associated with the class. This can
|
17
17
|
# also be used to "borrow" the connection to do database work that isn't
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
54
54
|
raise AdapterNotSpecified unless defined? RAILS_ENV
|
55
55
|
establish_connection(RAILS_ENV)
|
56
56
|
when ConnectionSpecification
|
57
|
-
|
57
|
+
self.connection_handler.establish_connection(name, spec)
|
58
58
|
when Symbol, String
|
59
59
|
if configuration = configurations[spec.to_s]
|
60
60
|
establish_connection(configuration)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module DatabaseLimits
|
4
|
+
|
5
|
+
# the maximum length of a table alias
|
6
|
+
def table_alias_length
|
7
|
+
255
|
8
|
+
end
|
9
|
+
|
10
|
+
# the maximum length of a column name
|
11
|
+
def column_name_length
|
12
|
+
64
|
13
|
+
end
|
14
|
+
|
15
|
+
# the maximum length of a table name
|
16
|
+
def table_name_length
|
17
|
+
64
|
18
|
+
end
|
19
|
+
|
20
|
+
# the maximum length of an index name
|
21
|
+
def index_name_length
|
22
|
+
64
|
23
|
+
end
|
24
|
+
|
25
|
+
# the maximum number of columns per table
|
26
|
+
def columns_per_table
|
27
|
+
1024
|
28
|
+
end
|
29
|
+
|
30
|
+
# the maximum number of indexes per table
|
31
|
+
def indexes_per_table
|
32
|
+
16
|
33
|
+
end
|
34
|
+
|
35
|
+
# the maximum number of columns in a multicolumn index
|
36
|
+
def columns_per_multicolumn_index
|
37
|
+
16
|
38
|
+
end
|
39
|
+
|
40
|
+
# the maximum number of elements in an IN (x,y,z) clause
|
41
|
+
def in_clause_length
|
42
|
+
65535
|
43
|
+
end
|
44
|
+
|
45
|
+
# the maximum length of a SQL query
|
46
|
+
def sql_query_length
|
47
|
+
1048575
|
48
|
+
end
|
49
|
+
|
50
|
+
# maximum number of joins in a single query
|
51
|
+
def joins_per_query
|
52
|
+
256
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
def dirties_query_cache(base, *method_names)
|
15
15
|
method_names.each do |method_name|
|
16
|
-
base.class_eval <<-end_code, __FILE__, __LINE__
|
16
|
+
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
17
17
|
def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
|
18
18
|
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
|
19
19
|
#{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
|
@@ -11,12 +11,12 @@ module ActiveRecord
|
|
11
11
|
when String, ActiveSupport::Multibyte::Chars
|
12
12
|
value = value.to_s
|
13
13
|
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
14
|
-
"
|
14
|
+
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
15
15
|
elsif column && [:integer, :float].include?(column.type)
|
16
16
|
value = column.type == :integer ? value.to_i : value.to_f
|
17
17
|
value.to_s
|
18
18
|
else
|
19
|
-
"
|
19
|
+
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
20
20
|
end
|
21
21
|
when NilClass then "NULL"
|
22
22
|
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
if value.acts_like?(:date) || value.acts_like?(:time)
|
29
29
|
"'#{quoted_date(value)}'"
|
30
30
|
else
|
31
|
-
"
|
31
|
+
"'#{quote_string(value.to_yaml)}'"
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -60,10 +60,6 @@ module ActiveRecord
|
|
60
60
|
def quoted_date(value)
|
61
61
|
value.to_s(:db)
|
62
62
|
end
|
63
|
-
|
64
|
-
def quoted_string_prefix
|
65
|
-
''
|
66
|
-
end
|
67
63
|
end
|
68
64
|
end
|
69
65
|
end
|
@@ -256,7 +256,7 @@ module ActiveRecord
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
-
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
|
259
|
+
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
|
260
260
|
end
|
261
261
|
|
262
262
|
# Abstract representation of a column definition. Instances of this type
|
@@ -8,11 +8,6 @@ module ActiveRecord
|
|
8
8
|
{}
|
9
9
|
end
|
10
10
|
|
11
|
-
# This is the maximum length a table alias can be
|
12
|
-
def table_alias_length
|
13
|
-
255
|
14
|
-
end
|
15
|
-
|
16
11
|
# Truncates a table alias according to the limits of the current adapter.
|
17
12
|
def table_alias_for(table_name)
|
18
13
|
table_name[0..table_alias_length-1].gsub(/\./, '_')
|
@@ -101,7 +96,7 @@ module ActiveRecord
|
|
101
96
|
table_definition = TableDefinition.new(self)
|
102
97
|
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
103
98
|
|
104
|
-
yield table_definition
|
99
|
+
yield table_definition if block_given?
|
105
100
|
|
106
101
|
if options[:force] && table_exists?(table_name)
|
107
102
|
drop_table(table_name, options)
|
@@ -246,18 +241,32 @@ module ActiveRecord
|
|
246
241
|
# name.
|
247
242
|
#
|
248
243
|
# ===== Examples
|
244
|
+
#
|
249
245
|
# ====== Creating a simple index
|
250
246
|
# add_index(:suppliers, :name)
|
251
247
|
# generates
|
252
248
|
# CREATE INDEX suppliers_name_index ON suppliers(name)
|
249
|
+
#
|
253
250
|
# ====== Creating a unique index
|
254
251
|
# add_index(:accounts, [:branch_id, :party_id], :unique => true)
|
255
252
|
# generates
|
256
253
|
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
|
254
|
+
#
|
257
255
|
# ====== Creating a named index
|
258
256
|
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
|
259
257
|
# generates
|
260
258
|
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
|
259
|
+
#
|
260
|
+
# ====== Creating an index with specific key length
|
261
|
+
# add_index(:accounts, :name, :name => 'by_name', :length => 10)
|
262
|
+
# generates
|
263
|
+
# CREATE INDEX by_name ON accounts(name(10))
|
264
|
+
#
|
265
|
+
# add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
|
266
|
+
# generates
|
267
|
+
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
|
268
|
+
#
|
269
|
+
# Note: SQLite doesn't support index length
|
261
270
|
def add_index(table_name, column_name, options = {})
|
262
271
|
column_names = Array(column_name)
|
263
272
|
index_name = index_name(table_name, :column => column_names)
|
@@ -268,7 +277,17 @@ module ActiveRecord
|
|
268
277
|
else
|
269
278
|
index_type = options
|
270
279
|
end
|
271
|
-
|
280
|
+
|
281
|
+
if index_name.length > index_name_length
|
282
|
+
@logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
|
283
|
+
return
|
284
|
+
end
|
285
|
+
if index_exists?(table_name, index_name, false)
|
286
|
+
@logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
|
287
|
+
return
|
288
|
+
end
|
289
|
+
quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
|
290
|
+
|
272
291
|
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
|
273
292
|
end
|
274
293
|
|
@@ -283,7 +302,28 @@ module ActiveRecord
|
|
283
302
|
# Remove the index named by_branch_party in the accounts table.
|
284
303
|
# remove_index :accounts, :name => :by_branch_party
|
285
304
|
def remove_index(table_name, options = {})
|
286
|
-
|
305
|
+
index_name = index_name(table_name, options)
|
306
|
+
unless index_exists?(table_name, index_name, true)
|
307
|
+
@logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
|
308
|
+
return
|
309
|
+
end
|
310
|
+
remove_index!(table_name, index_name)
|
311
|
+
end
|
312
|
+
|
313
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
314
|
+
execute "DROP INDEX #{quote_column_name(index_name)} ON #{table_name}"
|
315
|
+
end
|
316
|
+
|
317
|
+
# Rename an index.
|
318
|
+
#
|
319
|
+
# Rename the index_people_on_last_name index to index_users_on_last_name
|
320
|
+
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
|
321
|
+
def rename_index(table_name, old_name, new_name)
|
322
|
+
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
|
323
|
+
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
|
324
|
+
return unless old_index_def
|
325
|
+
remove_index(table_name, :name => old_name)
|
326
|
+
add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
|
287
327
|
end
|
288
328
|
|
289
329
|
def index_name(table_name, options) #:nodoc:
|
@@ -300,6 +340,15 @@ module ActiveRecord
|
|
300
340
|
end
|
301
341
|
end
|
302
342
|
|
343
|
+
# Verify the existence of an index.
|
344
|
+
#
|
345
|
+
# The default argument is returned if the underlying implementation does not define the indexes method,
|
346
|
+
# as there's no way to determine the correct answer in that case.
|
347
|
+
def index_exists?(table_name, index_name, default)
|
348
|
+
return default unless respond_to?(:indexes)
|
349
|
+
indexes(table_name).detect { |i| i.name == index_name }
|
350
|
+
end
|
351
|
+
|
303
352
|
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
|
304
353
|
# entire structure of the database.
|
305
354
|
def structure_dump
|
@@ -336,12 +385,12 @@ module ActiveRecord
|
|
336
385
|
end
|
337
386
|
end
|
338
387
|
|
339
|
-
def assume_migrated_upto_version(version)
|
388
|
+
def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
|
340
389
|
version = version.to_i
|
341
390
|
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
|
342
391
|
|
343
392
|
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
|
344
|
-
versions = Dir[
|
393
|
+
versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
|
345
394
|
filename.split('/').last.split('_').first.to_i
|
346
395
|
end
|
347
396
|
|
@@ -426,6 +475,11 @@ module ActiveRecord
|
|
426
475
|
end
|
427
476
|
|
428
477
|
protected
|
478
|
+
# Overridden by the mysql adapter for supporting index lengths
|
479
|
+
def quoted_columns_for_index(column_names, options = {})
|
480
|
+
column_names.map {|name| quote_column_name(name) }
|
481
|
+
end
|
482
|
+
|
429
483
|
def options_include_default?(options)
|
430
484
|
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
431
485
|
end
|
@@ -11,6 +11,7 @@ require 'active_record/connection_adapters/abstract/quoting'
|
|
11
11
|
require 'active_record/connection_adapters/abstract/connection_pool'
|
12
12
|
require 'active_record/connection_adapters/abstract/connection_specification'
|
13
13
|
require 'active_record/connection_adapters/abstract/query_cache'
|
14
|
+
require 'active_record/connection_adapters/abstract/database_limits'
|
14
15
|
|
15
16
|
module ActiveRecord
|
16
17
|
module ConnectionAdapters # :nodoc:
|
@@ -29,6 +30,7 @@ module ActiveRecord
|
|
29
30
|
# notably, the instance methods provided by SchemaStatement are very useful.
|
30
31
|
class AbstractAdapter
|
31
32
|
include Quoting, DatabaseStatements, SchemaStatements
|
33
|
+
include DatabaseLimits
|
32
34
|
include QueryCache
|
33
35
|
include ActiveSupport::Callbacks
|
34
36
|
define_callbacks :checkout, :checkin
|
@@ -454,10 +454,11 @@ module ActiveRecord
|
|
454
454
|
if current_index != row[2]
|
455
455
|
next if row[2] == "PRIMARY" # skip the primary key
|
456
456
|
current_index = row[2]
|
457
|
-
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
457
|
+
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
|
458
458
|
end
|
459
459
|
|
460
460
|
indexes.last.columns << row[4]
|
461
|
+
indexes.last.lengths << row[7]
|
461
462
|
end
|
462
463
|
result.free
|
463
464
|
indexes
|
@@ -480,6 +481,13 @@ module ActiveRecord
|
|
480
481
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
481
482
|
end
|
482
483
|
|
484
|
+
def add_column(table_name, column_name, type, options = {})
|
485
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
486
|
+
add_column_options!(add_column_sql, options)
|
487
|
+
add_column_position!(add_column_sql, options)
|
488
|
+
execute(add_column_sql)
|
489
|
+
end
|
490
|
+
|
483
491
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
484
492
|
column = column_for(table_name, column_name)
|
485
493
|
change_column table_name, column_name, column.sql_type, :default => default
|
@@ -508,6 +516,7 @@ module ActiveRecord
|
|
508
516
|
|
509
517
|
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
510
518
|
add_column_options!(change_column_sql, options)
|
519
|
+
add_column_position!(change_column_sql, options)
|
511
520
|
execute(change_column_sql)
|
512
521
|
end
|
513
522
|
|
@@ -539,6 +548,13 @@ module ActiveRecord
|
|
539
548
|
end
|
540
549
|
end
|
541
550
|
|
551
|
+
def add_column_position!(sql, options)
|
552
|
+
if options[:first]
|
553
|
+
sql << " FIRST"
|
554
|
+
elsif options[:after]
|
555
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
556
|
+
end
|
557
|
+
end
|
542
558
|
|
543
559
|
# SHOW VARIABLES LIKE 'name'
|
544
560
|
def show_variable(name)
|
@@ -571,6 +587,20 @@ module ActiveRecord
|
|
571
587
|
where_sql
|
572
588
|
end
|
573
589
|
|
590
|
+
protected
|
591
|
+
def quoted_columns_for_index(column_names, options = {})
|
592
|
+
length = options[:length] if options.is_a?(Hash)
|
593
|
+
|
594
|
+
quoted_column_names = case length
|
595
|
+
when Hash
|
596
|
+
column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
|
597
|
+
when Fixnum
|
598
|
+
column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
|
599
|
+
else
|
600
|
+
column_names.map {|name| quote_column_name(name) }
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
574
604
|
private
|
575
605
|
def connect
|
576
606
|
encoding = @config[:encoding]
|
@@ -261,20 +261,12 @@ module ActiveRecord
|
|
261
261
|
true
|
262
262
|
end
|
263
263
|
|
264
|
-
#
|
265
|
-
def
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
self.client_min_messages = 'panic'
|
271
|
-
|
272
|
-
# postgres-pr does not raise an exception when client_min_messages is set higher
|
273
|
-
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
|
274
|
-
# PGresult instead.
|
275
|
-
has_support = query('SHOW standard_conforming_strings')[0][0] rescue false
|
276
|
-
self.client_min_messages = client_min_messages_old
|
277
|
-
has_support
|
264
|
+
# Enable standard-conforming strings if available.
|
265
|
+
def set_standard_conforming_strings
|
266
|
+
old, self.client_min_messages = client_min_messages, 'panic'
|
267
|
+
execute('SET standard_conforming_strings = on') rescue nil
|
268
|
+
ensure
|
269
|
+
self.client_min_messages = old
|
278
270
|
end
|
279
271
|
|
280
272
|
def supports_insert_with_returning?
|
@@ -298,7 +290,7 @@ module ActiveRecord
|
|
298
290
|
# QUOTING ==================================================
|
299
291
|
|
300
292
|
# Escapes binary strings for bytea input to the database.
|
301
|
-
def escape_bytea(
|
293
|
+
def escape_bytea(original_value)
|
302
294
|
if @connection.respond_to?(:escape_bytea)
|
303
295
|
self.class.instance_eval do
|
304
296
|
define_method(:escape_bytea) do |value|
|
@@ -322,62 +314,40 @@ module ActiveRecord
|
|
322
314
|
end
|
323
315
|
end
|
324
316
|
end
|
325
|
-
escape_bytea(
|
317
|
+
escape_bytea(original_value)
|
326
318
|
end
|
327
319
|
|
328
320
|
# Unescapes bytea output from a database to the binary string it represents.
|
329
321
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
330
322
|
# on escaped binary output from database drive.
|
331
|
-
def unescape_bytea(
|
323
|
+
def unescape_bytea(original_value)
|
332
324
|
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
333
325
|
# or an unescaped Active Record attribute that was just written.
|
334
|
-
if
|
326
|
+
if @connection.respond_to?(:unescape_bytea)
|
335
327
|
self.class.instance_eval do
|
336
328
|
define_method(:unescape_bytea) do |value|
|
337
|
-
if value
|
338
|
-
PGconn.unescape_bytea(value)
|
339
|
-
else
|
340
|
-
value
|
341
|
-
end
|
329
|
+
@connection.unescape_bytea(value) if value
|
342
330
|
end
|
343
331
|
end
|
344
|
-
|
332
|
+
elsif PGconn.respond_to?(:unescape_bytea)
|
345
333
|
self.class.instance_eval do
|
346
334
|
define_method(:unescape_bytea) do |value|
|
347
|
-
if value
|
348
|
-
result = ''
|
349
|
-
i, max = 0, value.size
|
350
|
-
while i < max
|
351
|
-
char = value[i]
|
352
|
-
if char == ?\\
|
353
|
-
if value[i+1] == ?\\
|
354
|
-
char = ?\\
|
355
|
-
i += 1
|
356
|
-
else
|
357
|
-
char = value[i+1..i+3].oct
|
358
|
-
i += 3
|
359
|
-
end
|
360
|
-
end
|
361
|
-
result << char
|
362
|
-
i += 1
|
363
|
-
end
|
364
|
-
result
|
365
|
-
else
|
366
|
-
value
|
367
|
-
end
|
335
|
+
PGconn.unescape_bytea(value) if value
|
368
336
|
end
|
369
337
|
end
|
338
|
+
else
|
339
|
+
raise 'Your PostgreSQL connection does not support unescape_bytea. Try upgrading to pg 0.9.0 or later.'
|
370
340
|
end
|
371
|
-
unescape_bytea(
|
341
|
+
unescape_bytea(original_value)
|
372
342
|
end
|
373
343
|
|
374
344
|
# Quotes PostgreSQL-specific data types for SQL input.
|
375
345
|
def quote(value, column = nil) #:nodoc:
|
376
346
|
if value.kind_of?(String) && column && column.type == :binary
|
377
|
-
"
|
378
|
-
elsif value.kind_of?(String) && column && column.sql_type
|
379
|
-
"xml
|
380
|
-
elsif value.kind_of?(Numeric) && column && column.sql_type
|
347
|
+
"'#{escape_bytea(value)}'"
|
348
|
+
elsif value.kind_of?(String) && column && column.sql_type == 'xml'
|
349
|
+
"xml '#{quote_string(value)}'"
|
350
|
+
elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
|
381
351
|
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
382
352
|
"'#{value.to_s}'"
|
383
353
|
elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
|
@@ -393,7 +363,7 @@ module ActiveRecord
|
|
393
363
|
end
|
394
364
|
|
395
365
|
# Quotes strings for use in SQL input in the postgres driver for better performance.
|
396
|
-
def quote_string(
|
366
|
+
def quote_string(original_value) #:nodoc:
|
397
367
|
if @connection.respond_to?(:escape)
|
398
368
|
self.class.instance_eval do
|
399
369
|
define_method(:quote_string) do |s|
|
@@ -413,7 +383,7 @@ module ActiveRecord
|
|
413
383
|
remove_method(:quote_string)
|
414
384
|
end
|
415
385
|
end
|
416
|
-
quote_string(
|
386
|
+
quote_string(original_value)
|
417
387
|
end
|
418
388
|
|
419
389
|
# Checks the following cases:
|
@@ -889,9 +859,12 @@ module ActiveRecord
|
|
889
859
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
890
860
|
end
|
891
861
|
|
892
|
-
|
893
|
-
|
894
|
-
|
862
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
863
|
+
execute "DROP INDEX #{quote_table_name(index_name)}"
|
864
|
+
end
|
865
|
+
|
866
|
+
def index_name_length
|
867
|
+
63
|
895
868
|
end
|
896
869
|
|
897
870
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
@@ -971,17 +944,6 @@ module ActiveRecord
|
|
971
944
|
# Ignore async_exec and async_query when using postgres-pr.
|
972
945
|
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
973
946
|
|
974
|
-
# Use escape string syntax if available. We cannot do this lazily when encountering
|
975
|
-
# the first string, because that could then break any transactions in progress.
|
976
|
-
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
977
|
-
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
978
|
-
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
979
|
-
if supports_standard_conforming_strings?
|
980
|
-
self.class.instance_eval do
|
981
|
-
define_method(:quoted_string_prefix) { 'E' }
|
982
|
-
end
|
983
|
-
end
|
984
|
-
|
985
947
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
986
948
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
987
949
|
# should know about this but can't detect it there, so deal with it here.
|
@@ -1011,6 +973,9 @@ module ActiveRecord
|
|
1011
973
|
end
|
1012
974
|
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
|
1013
975
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
976
|
+
|
977
|
+
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
978
|
+
set_standard_conforming_strings
|
1014
979
|
end
|
1015
980
|
|
1016
981
|
# Returns the current ID of a table's sequence.
|