activerecord 3.2.22.4 → 4.0.13
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,63 +1,164 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'active_support/core_ext/object/blank'
|
3
2
|
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/postgresql/oid'
|
4
|
+
require 'active_record/connection_adapters/postgresql/cast'
|
5
|
+
require 'active_record/connection_adapters/postgresql/array_parser'
|
6
|
+
require 'active_record/connection_adapters/postgresql/quoting'
|
7
|
+
require 'active_record/connection_adapters/postgresql/schema_statements'
|
8
|
+
require 'active_record/connection_adapters/postgresql/database_statements'
|
9
|
+
require 'active_record/connection_adapters/postgresql/referential_integrity'
|
4
10
|
require 'arel/visitors/bind_visitor'
|
5
11
|
|
6
12
|
# Make sure we're using pg high enough for PGResult#values
|
7
13
|
gem 'pg', '~> 0.11'
|
8
14
|
require 'pg'
|
9
15
|
|
16
|
+
require 'ipaddr'
|
17
|
+
|
10
18
|
module ActiveRecord
|
11
|
-
|
19
|
+
module ConnectionHandling # :nodoc:
|
20
|
+
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
|
21
|
+
:client_encoding, :options, :application_name, :fallback_application_name,
|
22
|
+
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
|
23
|
+
:tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl,
|
24
|
+
:requirepeer, :krbsrvname, :gsslib, :service]
|
25
|
+
|
12
26
|
# Establishes a connection to the database that's used by all Active Record objects
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
27
|
+
def postgresql_connection(config)
|
28
|
+
conn_params = config.symbolize_keys
|
29
|
+
|
30
|
+
conn_params.delete_if { |_, v| v.nil? }
|
31
|
+
|
32
|
+
# Map ActiveRecords param names to PGs.
|
33
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
34
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
35
|
+
|
36
|
+
# Forward only valid config params to PGconn.connect.
|
37
|
+
conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
|
25
38
|
|
26
39
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
27
40
|
# so just pass a nil connection object for the time being.
|
28
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger,
|
41
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
32
45
|
module ConnectionAdapters
|
33
46
|
# PostgreSQL-specific extensions to column definitions in a table.
|
34
47
|
class PostgreSQLColumn < Column #:nodoc:
|
35
|
-
|
36
|
-
|
37
|
-
|
48
|
+
attr_accessor :array
|
49
|
+
|
50
|
+
def initialize(name, default, oid_type, sql_type = nil, null = true)
|
51
|
+
@oid_type = oid_type
|
52
|
+
default_value = self.class.extract_value_from_default(default)
|
53
|
+
|
54
|
+
if sql_type =~ /\[\]$/
|
55
|
+
@array = true
|
56
|
+
super(name, default_value, sql_type[0..sql_type.length - 3], null)
|
57
|
+
else
|
58
|
+
@array = false
|
59
|
+
super(name, default_value, sql_type, null)
|
60
|
+
end
|
61
|
+
|
62
|
+
@default_function = default if has_default_function?(default_value, default)
|
63
|
+
end
|
64
|
+
|
65
|
+
def number?
|
66
|
+
!array && super
|
67
|
+
end
|
68
|
+
|
69
|
+
def text?
|
70
|
+
!array && super
|
38
71
|
end
|
39
72
|
|
40
73
|
# :stopdoc:
|
41
74
|
class << self
|
75
|
+
include ConnectionAdapters::PostgreSQLColumn::Cast
|
76
|
+
include ConnectionAdapters::PostgreSQLColumn::ArrayParser
|
42
77
|
attr_accessor :money_precision
|
43
|
-
|
44
|
-
|
78
|
+
end
|
79
|
+
# :startdoc:
|
45
80
|
|
46
|
-
|
47
|
-
|
48
|
-
|
81
|
+
# Extracts the value from a PostgreSQL column default definition.
|
82
|
+
def self.extract_value_from_default(default)
|
83
|
+
# This is a performance optimization for Ruby 1.9.2 in development.
|
84
|
+
# If the value is nil, we return nil straight away without checking
|
85
|
+
# the regular expressions. If we check each regular expression,
|
86
|
+
# Regexp#=== will call NilClass#to_str, which will trigger
|
87
|
+
# method_missing (defined by whiny nil in ActiveSupport) which
|
88
|
+
# makes this method very very slow.
|
89
|
+
return default unless default
|
90
|
+
|
91
|
+
case default
|
92
|
+
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
|
93
|
+
$1
|
94
|
+
# Numeric types
|
95
|
+
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
|
96
|
+
$1
|
97
|
+
# Character types
|
98
|
+
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
99
|
+
$1
|
100
|
+
# Binary data types
|
101
|
+
when /\A'(.*)'::bytea\z/m
|
102
|
+
$1
|
103
|
+
# Date/time types
|
104
|
+
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
105
|
+
$1
|
106
|
+
when /\A'(.*)'::interval\z/
|
107
|
+
$1
|
108
|
+
# Boolean type
|
109
|
+
when 'true'
|
110
|
+
true
|
111
|
+
when 'false'
|
112
|
+
false
|
113
|
+
# Geometric types
|
114
|
+
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
115
|
+
$1
|
116
|
+
# Network address types
|
117
|
+
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
118
|
+
$1
|
119
|
+
# Bit string types
|
120
|
+
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
121
|
+
$1
|
122
|
+
# XML type
|
123
|
+
when /\A'(.*)'::xml\z/m
|
124
|
+
$1
|
125
|
+
# Arrays
|
126
|
+
when /\A'(.*)'::"?\D+"?\[\]\z/
|
127
|
+
$1
|
128
|
+
# Hstore
|
129
|
+
when /\A'(.*)'::hstore\z/
|
130
|
+
$1
|
131
|
+
# JSON
|
132
|
+
when /\A'(.*)'::json\z/
|
133
|
+
$1
|
134
|
+
# Object identifier types
|
135
|
+
when /\A-?\d+\z/
|
136
|
+
$1
|
49
137
|
else
|
50
|
-
|
51
|
-
|
138
|
+
# Anything else is blank, some user type, or some function
|
139
|
+
# and we can't know the value of that, so return nil.
|
140
|
+
nil
|
52
141
|
end
|
53
142
|
end
|
54
|
-
|
143
|
+
|
144
|
+
def type_cast(value)
|
145
|
+
return if value.nil?
|
146
|
+
return super if encoded?
|
147
|
+
|
148
|
+
@oid_type.type_cast value
|
149
|
+
end
|
55
150
|
|
56
151
|
private
|
152
|
+
|
153
|
+
def has_default_function?(default_value, default)
|
154
|
+
!default_value && (%r{\w+\(.*\)} === default)
|
155
|
+
end
|
156
|
+
|
57
157
|
def extract_limit(sql_type)
|
58
158
|
case sql_type
|
59
159
|
when /^bigint/i; 8
|
60
160
|
when /^smallint/i; 2
|
161
|
+
when /^timestamp/i; nil
|
61
162
|
else super
|
62
163
|
end
|
63
164
|
end
|
@@ -72,6 +173,8 @@ module ActiveRecord
|
|
72
173
|
def extract_precision(sql_type)
|
73
174
|
if sql_type == 'money'
|
74
175
|
self.class.money_precision
|
176
|
+
elsif sql_type =~ /timestamp/i
|
177
|
+
$1.to_i if sql_type =~ /\((\d+)\)/
|
75
178
|
else
|
76
179
|
super
|
77
180
|
end
|
@@ -80,130 +183,103 @@ module ActiveRecord
|
|
80
183
|
# Maps PostgreSQL-specific data types to logical Rails types.
|
81
184
|
def simplified_type(field_type)
|
82
185
|
case field_type
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
nil
|
144
|
-
# Numeric types
|
145
|
-
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
|
146
|
-
$1
|
147
|
-
# Character types
|
148
|
-
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
149
|
-
$1
|
150
|
-
# Binary data types
|
151
|
-
when /\A'(.*)'::bytea\z/m
|
152
|
-
$1
|
153
|
-
# Date/time types
|
154
|
-
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
155
|
-
$1
|
156
|
-
when /\A'(.*)'::interval\z/
|
157
|
-
$1
|
158
|
-
# Boolean type
|
159
|
-
when 'true'
|
160
|
-
true
|
161
|
-
when 'false'
|
162
|
-
false
|
163
|
-
# Geometric types
|
164
|
-
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
165
|
-
$1
|
166
|
-
# Network address types
|
167
|
-
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
168
|
-
$1
|
169
|
-
# Bit string types
|
170
|
-
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
171
|
-
$1
|
172
|
-
# XML type
|
173
|
-
when /\A'(.*)'::xml\z/m
|
174
|
-
$1
|
175
|
-
# Arrays
|
176
|
-
when /\A'(.*)'::"?\D+"?\[\]\z/
|
177
|
-
$1
|
178
|
-
# Object identifier types
|
179
|
-
when /\A-?\d+\z/
|
180
|
-
$1
|
181
|
-
else
|
182
|
-
# Anything else is blank, some user type, or some function
|
183
|
-
# and we can't know the value of that, so return nil.
|
184
|
-
nil
|
186
|
+
# Numeric and monetary types
|
187
|
+
when /^(?:real|double precision)$/
|
188
|
+
:float
|
189
|
+
# Monetary types
|
190
|
+
when 'money'
|
191
|
+
:decimal
|
192
|
+
when 'hstore'
|
193
|
+
:hstore
|
194
|
+
when 'ltree'
|
195
|
+
:ltree
|
196
|
+
# Network address types
|
197
|
+
when 'inet'
|
198
|
+
:inet
|
199
|
+
when 'cidr'
|
200
|
+
:cidr
|
201
|
+
when 'macaddr'
|
202
|
+
:macaddr
|
203
|
+
# Character types
|
204
|
+
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
205
|
+
:string
|
206
|
+
# Binary data types
|
207
|
+
when 'bytea'
|
208
|
+
:binary
|
209
|
+
# Date/time types
|
210
|
+
when /^timestamp with(?:out)? time zone$/
|
211
|
+
:datetime
|
212
|
+
when /^interval(?:|\(\d+\))$/
|
213
|
+
:string
|
214
|
+
# Geometric types
|
215
|
+
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
216
|
+
:string
|
217
|
+
# Bit strings
|
218
|
+
when /^bit(?: varying)?(?:\(\d+\))?$/
|
219
|
+
:string
|
220
|
+
# XML type
|
221
|
+
when 'xml'
|
222
|
+
:xml
|
223
|
+
# tsvector type
|
224
|
+
when 'tsvector'
|
225
|
+
:tsvector
|
226
|
+
# Arrays
|
227
|
+
when /^\D+\[\]$/
|
228
|
+
:string
|
229
|
+
# Object identifier types
|
230
|
+
when 'oid'
|
231
|
+
:integer
|
232
|
+
# UUID type
|
233
|
+
when 'uuid'
|
234
|
+
:uuid
|
235
|
+
# JSON type
|
236
|
+
when 'json'
|
237
|
+
:json
|
238
|
+
# Small and big integer types
|
239
|
+
when /^(?:small|big)int$/
|
240
|
+
:integer
|
241
|
+
when /(num|date|tstz|ts|int4|int8)range$/
|
242
|
+
field_type.to_sym
|
243
|
+
# Pass through all types that are not specific to PostgreSQL.
|
244
|
+
else
|
245
|
+
super
|
185
246
|
end
|
186
247
|
end
|
187
248
|
end
|
188
249
|
|
189
|
-
# The PostgreSQL adapter works
|
190
|
-
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
250
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
191
251
|
#
|
192
252
|
# Options:
|
193
253
|
#
|
194
|
-
# * <tt>:host</tt> - Defaults to
|
254
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
255
|
+
# the default is to connect to localhost.
|
195
256
|
# * <tt>:port</tt> - Defaults to 5432.
|
196
|
-
# * <tt>:username</tt> - Defaults to
|
197
|
-
# * <tt>:password</tt> -
|
198
|
-
# * <tt>:database</tt> -
|
257
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
258
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
259
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
199
260
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
261
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
262
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
263
|
# <encoding></tt> call on the connection.
|
203
264
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
265
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
266
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
267
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
268
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
269
|
+
# defaults to true.
|
270
|
+
#
|
271
|
+
# Any further options are used as connection parameters to libpq. See
|
272
|
+
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
|
273
|
+
# list of parameters.
|
274
|
+
#
|
275
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
276
|
+
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
|
205
277
|
class PostgreSQLAdapter < AbstractAdapter
|
206
|
-
class
|
278
|
+
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
|
279
|
+
attr_accessor :array
|
280
|
+
end
|
281
|
+
|
282
|
+
module ColumnMethods
|
207
283
|
def xml(*args)
|
208
284
|
options = args.extract_options!
|
209
285
|
column(args[0], 'xml', options)
|
@@ -213,32 +289,174 @@ module ActiveRecord
|
|
213
289
|
options = args.extract_options!
|
214
290
|
column(args[0], 'tsvector', options)
|
215
291
|
end
|
292
|
+
|
293
|
+
def int4range(name, options = {})
|
294
|
+
column(name, 'int4range', options)
|
295
|
+
end
|
296
|
+
|
297
|
+
def int8range(name, options = {})
|
298
|
+
column(name, 'int8range', options)
|
299
|
+
end
|
300
|
+
|
301
|
+
def tsrange(name, options = {})
|
302
|
+
column(name, 'tsrange', options)
|
303
|
+
end
|
304
|
+
|
305
|
+
def tstzrange(name, options = {})
|
306
|
+
column(name, 'tstzrange', options)
|
307
|
+
end
|
308
|
+
|
309
|
+
def numrange(name, options = {})
|
310
|
+
column(name, 'numrange', options)
|
311
|
+
end
|
312
|
+
|
313
|
+
def daterange(name, options = {})
|
314
|
+
column(name, 'daterange', options)
|
315
|
+
end
|
316
|
+
|
317
|
+
def hstore(name, options = {})
|
318
|
+
column(name, 'hstore', options)
|
319
|
+
end
|
320
|
+
|
321
|
+
def ltree(name, options = {})
|
322
|
+
column(name, 'ltree', options)
|
323
|
+
end
|
324
|
+
|
325
|
+
def inet(name, options = {})
|
326
|
+
column(name, 'inet', options)
|
327
|
+
end
|
328
|
+
|
329
|
+
def cidr(name, options = {})
|
330
|
+
column(name, 'cidr', options)
|
331
|
+
end
|
332
|
+
|
333
|
+
def macaddr(name, options = {})
|
334
|
+
column(name, 'macaddr', options)
|
335
|
+
end
|
336
|
+
|
337
|
+
def uuid(name, options = {})
|
338
|
+
column(name, 'uuid', options)
|
339
|
+
end
|
340
|
+
|
341
|
+
def json(name, options = {})
|
342
|
+
column(name, 'json', options)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
347
|
+
include ColumnMethods
|
348
|
+
|
349
|
+
# Defines the primary key field.
|
350
|
+
# Use of the native PostgreSQL UUID type is supported, and can be used
|
351
|
+
# by defining your tables as such:
|
352
|
+
#
|
353
|
+
# create_table :stuffs, id: :uuid do |t|
|
354
|
+
# t.string :content
|
355
|
+
# t.timestamps
|
356
|
+
# end
|
357
|
+
#
|
358
|
+
# By default, this will use the +uuid_generate_v4()+ function from the
|
359
|
+
# +uuid-ossp+ extension, which MUST be enabled on your databse. To enable
|
360
|
+
# the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
|
361
|
+
# migrations To use a UUID primary key without +uuid-ossp+ enabled, you can
|
362
|
+
# set the +:default+ option to nil:
|
363
|
+
#
|
364
|
+
# create_table :stuffs, id: false do |t|
|
365
|
+
# t.primary_key :id, :uuid, default: nil
|
366
|
+
# t.uuid :foo_id
|
367
|
+
# t.timestamps
|
368
|
+
# end
|
369
|
+
#
|
370
|
+
# You may also pass a different UUID generation function from +uuid-ossp+
|
371
|
+
# or another library.
|
372
|
+
#
|
373
|
+
# Note that setting the UUID primary key default value to +nil+
|
374
|
+
# will require you to assure that you always provide a UUID value
|
375
|
+
# before saving a record (as primary keys cannot be nil). This might be
|
376
|
+
# done via the SecureRandom.uuid method and a +before_save+ callback,
|
377
|
+
# for instance.
|
378
|
+
def primary_key(name, type = :primary_key, options = {})
|
379
|
+
return super unless type == :uuid
|
380
|
+
options[:default] = options.fetch(:default, 'uuid_generate_v4()')
|
381
|
+
options[:primary_key] = true
|
382
|
+
column name, type, options
|
383
|
+
end
|
384
|
+
|
385
|
+
def column(name, type = nil, options = {})
|
386
|
+
super
|
387
|
+
column = self[name]
|
388
|
+
column.array = options[:array]
|
389
|
+
|
390
|
+
self
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
def create_column_definition(name, type)
|
396
|
+
ColumnDefinition.new name, type
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
401
|
+
include ColumnMethods
|
216
402
|
end
|
217
403
|
|
218
404
|
ADAPTER_NAME = 'PostgreSQL'
|
219
405
|
|
220
406
|
NATIVE_DATABASE_TYPES = {
|
221
|
-
:
|
222
|
-
:
|
223
|
-
:
|
224
|
-
:
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
407
|
+
primary_key: "serial primary key",
|
408
|
+
string: { name: "character varying", limit: 255 },
|
409
|
+
text: { name: "text" },
|
410
|
+
integer: { name: "integer" },
|
411
|
+
float: { name: "float" },
|
412
|
+
decimal: { name: "decimal" },
|
413
|
+
datetime: { name: "timestamp" },
|
414
|
+
timestamp: { name: "timestamp" },
|
415
|
+
time: { name: "time" },
|
416
|
+
date: { name: "date" },
|
417
|
+
daterange: { name: "daterange" },
|
418
|
+
numrange: { name: "numrange" },
|
419
|
+
tsrange: { name: "tsrange" },
|
420
|
+
tstzrange: { name: "tstzrange" },
|
421
|
+
int4range: { name: "int4range" },
|
422
|
+
int8range: { name: "int8range" },
|
423
|
+
binary: { name: "bytea" },
|
424
|
+
boolean: { name: "boolean" },
|
425
|
+
xml: { name: "xml" },
|
426
|
+
tsvector: { name: "tsvector" },
|
427
|
+
hstore: { name: "hstore" },
|
428
|
+
inet: { name: "inet" },
|
429
|
+
cidr: { name: "cidr" },
|
430
|
+
macaddr: { name: "macaddr" },
|
431
|
+
uuid: { name: "uuid" },
|
432
|
+
json: { name: "json" },
|
433
|
+
ltree: { name: "ltree" }
|
235
434
|
}
|
236
435
|
|
436
|
+
include Quoting
|
437
|
+
include ReferentialIntegrity
|
438
|
+
include SchemaStatements
|
439
|
+
include DatabaseStatements
|
440
|
+
|
237
441
|
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
238
442
|
def adapter_name
|
239
443
|
ADAPTER_NAME
|
240
444
|
end
|
241
445
|
|
446
|
+
# Adds `:array` option to the default set provided by the
|
447
|
+
# AbstractAdapter
|
448
|
+
def prepare_column_options(column, types)
|
449
|
+
spec = super
|
450
|
+
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
451
|
+
spec[:default] = "\"#{column.default_function}\"" if column.default_function
|
452
|
+
spec
|
453
|
+
end
|
454
|
+
|
455
|
+
# Adds `:array` as a valid migration key
|
456
|
+
def migration_keys
|
457
|
+
super + [:array]
|
458
|
+
end
|
459
|
+
|
242
460
|
# Returns +true+, since this connection adapter supports prepared statement
|
243
461
|
# caching.
|
244
462
|
def supports_statement_cache?
|
@@ -249,6 +467,18 @@ module ActiveRecord
|
|
249
467
|
true
|
250
468
|
end
|
251
469
|
|
470
|
+
def supports_partial_index?
|
471
|
+
true
|
472
|
+
end
|
473
|
+
|
474
|
+
def supports_transaction_isolation?
|
475
|
+
true
|
476
|
+
end
|
477
|
+
|
478
|
+
def index_algorithms
|
479
|
+
{ concurrently: 'CONCURRENTLY' }
|
480
|
+
end
|
481
|
+
|
252
482
|
class StatementPool < ConnectionAdapters::StatementPool
|
253
483
|
def initialize(connection, max)
|
254
484
|
super
|
@@ -286,19 +516,20 @@ module ActiveRecord
|
|
286
516
|
end
|
287
517
|
|
288
518
|
private
|
289
|
-
def cache
|
290
|
-
@cache[$$]
|
291
|
-
end
|
292
519
|
|
293
|
-
|
294
|
-
|
295
|
-
|
520
|
+
def cache
|
521
|
+
@cache[Process.pid]
|
522
|
+
end
|
296
523
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
524
|
+
def dealloc(key)
|
525
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
526
|
+
end
|
527
|
+
|
528
|
+
def connection_active?
|
529
|
+
@connection.status == PGconn::CONNECTION_OK
|
530
|
+
rescue PGError
|
531
|
+
false
|
532
|
+
end
|
302
533
|
end
|
303
534
|
|
304
535
|
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
|
@@ -309,10 +540,11 @@ module ActiveRecord
|
|
309
540
|
def initialize(connection, logger, connection_parameters, config)
|
310
541
|
super(connection, logger)
|
311
542
|
|
312
|
-
if config.fetch(:prepared_statements) { true }
|
543
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
544
|
+
@prepared_statements = true
|
313
545
|
@visitor = Arel::Visitors::PostgreSQL.new self
|
314
546
|
else
|
315
|
-
@visitor =
|
547
|
+
@visitor = unprepared_visitor
|
316
548
|
end
|
317
549
|
|
318
550
|
@connection_parameters, @config = connection_parameters, config
|
@@ -323,13 +555,15 @@ module ActiveRecord
|
|
323
555
|
|
324
556
|
connect
|
325
557
|
@statements = StatementPool.new @connection,
|
326
|
-
config.fetch(:statement_limit) { 1000 }
|
558
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
|
327
559
|
|
328
560
|
if postgresql_version < 80200
|
329
561
|
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
330
562
|
end
|
331
563
|
|
564
|
+
initialize_type_map
|
332
565
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
566
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
333
567
|
end
|
334
568
|
|
335
569
|
# Clears the prepared statements cache.
|
@@ -345,11 +579,14 @@ module ActiveRecord
|
|
345
579
|
false
|
346
580
|
end
|
347
581
|
|
582
|
+
def active_threadsafe?
|
583
|
+
@connection.connect_poll != PG::PGRES_POLLING_FAILED
|
584
|
+
end
|
585
|
+
|
348
586
|
# Close then reopen the connection.
|
349
587
|
def reconnect!
|
350
|
-
|
588
|
+
super
|
351
589
|
@connection.reset
|
352
|
-
@open_transactions = 0
|
353
590
|
configure_connection
|
354
591
|
end
|
355
592
|
|
@@ -361,7 +598,7 @@ module ActiveRecord
|
|
361
598
|
# Disconnects from the database if already connected. Otherwise, this
|
362
599
|
# method does nothing.
|
363
600
|
def disconnect!
|
364
|
-
|
601
|
+
super
|
365
602
|
@connection.close rescue nil
|
366
603
|
end
|
367
604
|
|
@@ -405,103 +642,48 @@ module ActiveRecord
|
|
405
642
|
true
|
406
643
|
end
|
407
644
|
|
408
|
-
# Returns
|
409
|
-
def
|
410
|
-
|
645
|
+
# Returns true if pg > 9.1
|
646
|
+
def supports_extensions?
|
647
|
+
postgresql_version >= 90100
|
411
648
|
end
|
412
649
|
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
def escape_bytea(value)
|
417
|
-
@connection.escape_bytea(value) if value
|
650
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
651
|
+
def supports_ranges?
|
652
|
+
postgresql_version >= 90200
|
418
653
|
end
|
419
654
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
@connection.unescape_bytea(value) if value
|
655
|
+
def enable_extension(name)
|
656
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
657
|
+
reload_type_map
|
658
|
+
}
|
425
659
|
end
|
426
660
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
case value
|
432
|
-
when Float
|
433
|
-
return super unless value.infinite? && column.type == :datetime
|
434
|
-
"'#{value.to_s.downcase}'"
|
435
|
-
when Numeric
|
436
|
-
return super unless column.sql_type == 'money'
|
437
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
438
|
-
"'#{value}'"
|
439
|
-
when String
|
440
|
-
case column.sql_type
|
441
|
-
when 'bytea' then "'#{escape_bytea(value)}'"
|
442
|
-
when 'xml' then "xml '#{quote_string(value)}'"
|
443
|
-
when /^bit/
|
444
|
-
case value
|
445
|
-
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
446
|
-
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
447
|
-
end
|
448
|
-
else
|
449
|
-
super
|
450
|
-
end
|
451
|
-
else
|
452
|
-
super
|
453
|
-
end
|
661
|
+
def disable_extension(name)
|
662
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
663
|
+
reload_type_map
|
664
|
+
}
|
454
665
|
end
|
455
666
|
|
456
|
-
def
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
return super unless 'bytea' == column.sql_type
|
462
|
-
{ :value => value, :format => 1 }
|
463
|
-
else
|
464
|
-
super
|
667
|
+
def extension_enabled?(name)
|
668
|
+
if supports_extensions?
|
669
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
|
670
|
+
'SCHEMA'
|
671
|
+
res.column_types['enabled'].type_cast res.rows.first.first
|
465
672
|
end
|
466
673
|
end
|
467
674
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
# Checks the following cases:
|
474
|
-
#
|
475
|
-
# - table_name
|
476
|
-
# - "table.name"
|
477
|
-
# - schema_name.table_name
|
478
|
-
# - schema_name."table.name"
|
479
|
-
# - "schema.name".table_name
|
480
|
-
# - "schema.name"."table.name"
|
481
|
-
def quote_table_name(name)
|
482
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
483
|
-
|
484
|
-
unless name_part
|
485
|
-
quote_column_name(schema)
|
675
|
+
def extensions
|
676
|
+
if supports_extensions?
|
677
|
+
res = exec_query "SELECT extname from pg_extension", "SCHEMA"
|
678
|
+
res.rows.map { |r| res.column_types['extname'].type_cast r.first }
|
486
679
|
else
|
487
|
-
|
488
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
680
|
+
super
|
489
681
|
end
|
490
682
|
end
|
491
683
|
|
492
|
-
#
|
493
|
-
def
|
494
|
-
|
495
|
-
end
|
496
|
-
|
497
|
-
# Quote date/time values for use in SQL input. Includes microseconds
|
498
|
-
# if the value is a Time responding to usec.
|
499
|
-
def quoted_date(value) #:nodoc:
|
500
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
501
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
502
|
-
else
|
503
|
-
super
|
504
|
-
end
|
684
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
685
|
+
def table_alias_length
|
686
|
+
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
|
505
687
|
end
|
506
688
|
|
507
689
|
# Set the authorized user for this session
|
@@ -510,610 +692,6 @@ module ActiveRecord
|
|
510
692
|
exec_query "SET SESSION AUTHORIZATION #{user}"
|
511
693
|
end
|
512
694
|
|
513
|
-
# REFERENTIAL INTEGRITY ====================================
|
514
|
-
|
515
|
-
def supports_disable_referential_integrity? #:nodoc:
|
516
|
-
true
|
517
|
-
end
|
518
|
-
|
519
|
-
def disable_referential_integrity #:nodoc:
|
520
|
-
if supports_disable_referential_integrity? then
|
521
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
522
|
-
end
|
523
|
-
yield
|
524
|
-
ensure
|
525
|
-
if supports_disable_referential_integrity? then
|
526
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
# DATABASE STATEMENTS ======================================
|
531
|
-
|
532
|
-
def explain(arel, binds = [])
|
533
|
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
534
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
535
|
-
end
|
536
|
-
|
537
|
-
class ExplainPrettyPrinter # :nodoc:
|
538
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
539
|
-
# PostgreSQL shell:
|
540
|
-
#
|
541
|
-
# QUERY PLAN
|
542
|
-
# ------------------------------------------------------------------------------
|
543
|
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
544
|
-
# Join Filter: (posts.user_id = users.id)
|
545
|
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
546
|
-
# Index Cond: (id = 1)
|
547
|
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
548
|
-
# Filter: (posts.user_id = 1)
|
549
|
-
# (6 rows)
|
550
|
-
#
|
551
|
-
def pp(result)
|
552
|
-
header = result.columns.first
|
553
|
-
lines = result.rows.map(&:first)
|
554
|
-
|
555
|
-
# We add 2 because there's one char of padding at both sides, note
|
556
|
-
# the extra hyphens in the example above.
|
557
|
-
width = [header, *lines].map(&:length).max + 2
|
558
|
-
|
559
|
-
pp = []
|
560
|
-
|
561
|
-
pp << header.center(width).rstrip
|
562
|
-
pp << '-' * width
|
563
|
-
|
564
|
-
pp += lines.map {|line| " #{line}"}
|
565
|
-
|
566
|
-
nrows = result.rows.length
|
567
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
568
|
-
pp << "(#{nrows} #{rows_label})"
|
569
|
-
|
570
|
-
pp.join("\n") + "\n"
|
571
|
-
end
|
572
|
-
end
|
573
|
-
|
574
|
-
# Executes a SELECT query and returns an array of rows. Each row is an
|
575
|
-
# array of field values.
|
576
|
-
def select_rows(sql, name = nil)
|
577
|
-
select_raw(sql, name).last
|
578
|
-
end
|
579
|
-
|
580
|
-
# Executes an INSERT query and returns the new record's ID
|
581
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
582
|
-
unless pk
|
583
|
-
# Extract the table from the insert sql. Yuck.
|
584
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
585
|
-
pk = primary_key(table_ref) if table_ref
|
586
|
-
end
|
587
|
-
|
588
|
-
if pk
|
589
|
-
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
590
|
-
else
|
591
|
-
super
|
592
|
-
end
|
593
|
-
end
|
594
|
-
alias :create :insert
|
595
|
-
|
596
|
-
# create a 2D array representing the result set
|
597
|
-
def result_as_array(res) #:nodoc:
|
598
|
-
# check if we have any binary column and if they need escaping
|
599
|
-
ftypes = Array.new(res.nfields) do |i|
|
600
|
-
[i, res.ftype(i)]
|
601
|
-
end
|
602
|
-
|
603
|
-
rows = res.values
|
604
|
-
return rows unless ftypes.any? { |_, x|
|
605
|
-
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
606
|
-
}
|
607
|
-
|
608
|
-
typehash = ftypes.group_by { |_, type| type }
|
609
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
610
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
611
|
-
|
612
|
-
rows.each do |row|
|
613
|
-
# unescape string passed BYTEA field (OID == 17)
|
614
|
-
binaries.each do |index, _|
|
615
|
-
row[index] = unescape_bytea(row[index])
|
616
|
-
end
|
617
|
-
|
618
|
-
# If this is a money type column and there are any currency symbols,
|
619
|
-
# then strip them off. Indeed it would be prettier to do this in
|
620
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
621
|
-
# fields that call value_before_type_cast.
|
622
|
-
monies.each do |index, _|
|
623
|
-
data = row[index]
|
624
|
-
# Because money output is formatted according to the locale, there are two
|
625
|
-
# cases to consider (note the decimal separators):
|
626
|
-
# (1) $12,345,678.12
|
627
|
-
# (2) $12.345.678,12
|
628
|
-
case data
|
629
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
630
|
-
data.gsub!(/[^-\d.]/, '')
|
631
|
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
632
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
633
|
-
end
|
634
|
-
end
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
|
639
|
-
# Queries the database and returns the results in an Array-like object
|
640
|
-
def query(sql, name = nil) #:nodoc:
|
641
|
-
log(sql, name) do
|
642
|
-
result_as_array @connection.async_exec(sql)
|
643
|
-
end
|
644
|
-
end
|
645
|
-
|
646
|
-
# Executes an SQL statement, returning a PGresult object on success
|
647
|
-
# or raising a PGError exception otherwise.
|
648
|
-
def execute(sql, name = nil)
|
649
|
-
log(sql, name) do
|
650
|
-
@connection.async_exec(sql)
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
def substitute_at(column, index)
|
655
|
-
Arel::Nodes::BindParam.new "$#{index + 1}"
|
656
|
-
end
|
657
|
-
|
658
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
659
|
-
log(sql, name, binds) do
|
660
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
661
|
-
exec_cache(sql, binds)
|
662
|
-
|
663
|
-
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
|
664
|
-
result.clear
|
665
|
-
return ret
|
666
|
-
end
|
667
|
-
end
|
668
|
-
|
669
|
-
def exec_delete(sql, name = 'SQL', binds = [])
|
670
|
-
log(sql, name, binds) do
|
671
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
672
|
-
exec_cache(sql, binds)
|
673
|
-
affected = result.cmd_tuples
|
674
|
-
result.clear
|
675
|
-
affected
|
676
|
-
end
|
677
|
-
end
|
678
|
-
alias :exec_update :exec_delete
|
679
|
-
|
680
|
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
681
|
-
unless pk
|
682
|
-
# Extract the table from the insert sql. Yuck.
|
683
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
684
|
-
pk = primary_key(table_ref) if table_ref
|
685
|
-
end
|
686
|
-
|
687
|
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
688
|
-
|
689
|
-
[sql, binds]
|
690
|
-
end
|
691
|
-
|
692
|
-
# Executes an UPDATE query and returns the number of affected tuples.
|
693
|
-
def update_sql(sql, name = nil)
|
694
|
-
super.cmd_tuples
|
695
|
-
end
|
696
|
-
|
697
|
-
# Begins a transaction.
|
698
|
-
def begin_db_transaction
|
699
|
-
execute "BEGIN"
|
700
|
-
end
|
701
|
-
|
702
|
-
# Commits a transaction.
|
703
|
-
def commit_db_transaction
|
704
|
-
execute "COMMIT"
|
705
|
-
end
|
706
|
-
|
707
|
-
# Aborts a transaction.
|
708
|
-
def rollback_db_transaction
|
709
|
-
execute "ROLLBACK"
|
710
|
-
end
|
711
|
-
|
712
|
-
def outside_transaction?
|
713
|
-
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
714
|
-
end
|
715
|
-
|
716
|
-
def create_savepoint
|
717
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
718
|
-
end
|
719
|
-
|
720
|
-
def rollback_to_savepoint
|
721
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
722
|
-
end
|
723
|
-
|
724
|
-
def release_savepoint
|
725
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
726
|
-
end
|
727
|
-
|
728
|
-
# SCHEMA STATEMENTS ========================================
|
729
|
-
|
730
|
-
# Drops the database specified on the +name+ attribute
|
731
|
-
# and creates it again using the provided +options+.
|
732
|
-
def recreate_database(name, options = {}) #:nodoc:
|
733
|
-
drop_database(name)
|
734
|
-
create_database(name, options)
|
735
|
-
end
|
736
|
-
|
737
|
-
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
738
|
-
# <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
739
|
-
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
740
|
-
#
|
741
|
-
# Example:
|
742
|
-
# create_database config[:database], config
|
743
|
-
# create_database 'foo_development', :encoding => 'unicode'
|
744
|
-
def create_database(name, options = {})
|
745
|
-
options = options.reverse_merge(:encoding => "utf8")
|
746
|
-
|
747
|
-
option_string = options.symbolize_keys.sum do |key, value|
|
748
|
-
case key
|
749
|
-
when :owner
|
750
|
-
" OWNER = \"#{value}\""
|
751
|
-
when :template
|
752
|
-
" TEMPLATE = \"#{value}\""
|
753
|
-
when :encoding
|
754
|
-
" ENCODING = '#{value}'"
|
755
|
-
when :tablespace
|
756
|
-
" TABLESPACE = \"#{value}\""
|
757
|
-
when :connection_limit
|
758
|
-
" CONNECTION LIMIT = #{value}"
|
759
|
-
else
|
760
|
-
""
|
761
|
-
end
|
762
|
-
end
|
763
|
-
|
764
|
-
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
765
|
-
end
|
766
|
-
|
767
|
-
# Drops a PostgreSQL database.
|
768
|
-
#
|
769
|
-
# Example:
|
770
|
-
# drop_database 'matt_development'
|
771
|
-
def drop_database(name) #:nodoc:
|
772
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
773
|
-
end
|
774
|
-
|
775
|
-
# Returns the list of all tables in the schema search path or a specified schema.
|
776
|
-
def tables(name = nil)
|
777
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
778
|
-
SELECT tablename
|
779
|
-
FROM pg_tables
|
780
|
-
WHERE schemaname = ANY (current_schemas(false))
|
781
|
-
SQL
|
782
|
-
end
|
783
|
-
|
784
|
-
# Returns true if table exists.
|
785
|
-
# If the schema is not specified as part of +name+ then it will only find tables within
|
786
|
-
# the current schema search path (regardless of permissions to access tables in other schemas)
|
787
|
-
def table_exists?(name)
|
788
|
-
schema, table = Utils.extract_schema_and_table(name.to_s)
|
789
|
-
return false unless table
|
790
|
-
|
791
|
-
binds = [[nil, table]]
|
792
|
-
binds << [nil, schema] if schema
|
793
|
-
|
794
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
795
|
-
SELECT COUNT(*)
|
796
|
-
FROM pg_class c
|
797
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
798
|
-
WHERE c.relkind in ('v','r')
|
799
|
-
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
|
800
|
-
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
801
|
-
SQL
|
802
|
-
end
|
803
|
-
|
804
|
-
# Returns true if schema exists.
|
805
|
-
def schema_exists?(name)
|
806
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
807
|
-
SELECT COUNT(*)
|
808
|
-
FROM pg_namespace
|
809
|
-
WHERE nspname = '#{name}'
|
810
|
-
SQL
|
811
|
-
end
|
812
|
-
|
813
|
-
# Returns an array of indexes for the given table.
|
814
|
-
def indexes(table_name, name = nil)
|
815
|
-
result = query(<<-SQL, 'SCHEMA')
|
816
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
817
|
-
FROM pg_class t
|
818
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
819
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
820
|
-
WHERE i.relkind = 'i'
|
821
|
-
AND d.indisprimary = 'f'
|
822
|
-
AND t.relname = '#{table_name}'
|
823
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
824
|
-
ORDER BY i.relname
|
825
|
-
SQL
|
826
|
-
|
827
|
-
|
828
|
-
result.map do |row|
|
829
|
-
index_name = row[0]
|
830
|
-
unique = row[1] == 't'
|
831
|
-
indkey = row[2].split(" ")
|
832
|
-
inddef = row[3]
|
833
|
-
oid = row[4]
|
834
|
-
|
835
|
-
columns = Hash[query(<<-SQL, "SCHEMA")]
|
836
|
-
SELECT a.attnum, a.attname
|
837
|
-
FROM pg_attribute a
|
838
|
-
WHERE a.attrelid = #{oid}
|
839
|
-
AND a.attnum IN (#{indkey.join(",")})
|
840
|
-
SQL
|
841
|
-
|
842
|
-
column_names = columns.values_at(*indkey).compact
|
843
|
-
|
844
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
845
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
846
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
847
|
-
|
848
|
-
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
849
|
-
end.compact
|
850
|
-
end
|
851
|
-
|
852
|
-
# Returns the list of all column definitions for a table.
|
853
|
-
def columns(table_name, name = nil)
|
854
|
-
# Limit, precision, and scale are all handled by the superclass.
|
855
|
-
column_definitions(table_name).collect do |column_name, type, default, notnull|
|
856
|
-
PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
|
857
|
-
end
|
858
|
-
end
|
859
|
-
|
860
|
-
# Returns the current database name.
|
861
|
-
def current_database
|
862
|
-
query('select current_database()', 'SCHEMA')[0][0]
|
863
|
-
end
|
864
|
-
|
865
|
-
# Returns the current schema name.
|
866
|
-
def current_schema
|
867
|
-
query('SELECT current_schema', 'SCHEMA')[0][0]
|
868
|
-
end
|
869
|
-
|
870
|
-
# Returns the current database encoding format.
|
871
|
-
def encoding
|
872
|
-
query(<<-end_sql, 'SCHEMA')[0][0]
|
873
|
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
874
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
875
|
-
end_sql
|
876
|
-
end
|
877
|
-
|
878
|
-
# Sets the schema search path to a string of comma-separated schema names.
|
879
|
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
880
|
-
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
881
|
-
#
|
882
|
-
# This should be not be called manually but set in database.yml.
|
883
|
-
def schema_search_path=(schema_csv)
|
884
|
-
if schema_csv
|
885
|
-
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
|
886
|
-
@schema_search_path = schema_csv
|
887
|
-
end
|
888
|
-
end
|
889
|
-
|
890
|
-
# Returns the active schema search path.
|
891
|
-
def schema_search_path
|
892
|
-
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
|
893
|
-
end
|
894
|
-
|
895
|
-
# Returns the current client message level.
|
896
|
-
def client_min_messages
|
897
|
-
query('SHOW client_min_messages', 'SCHEMA')[0][0]
|
898
|
-
end
|
899
|
-
|
900
|
-
# Set the client message level.
|
901
|
-
def client_min_messages=(level)
|
902
|
-
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
|
903
|
-
end
|
904
|
-
|
905
|
-
# Returns the sequence name for a table's primary key or some other specified key.
|
906
|
-
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
907
|
-
serial_sequence(table_name, pk || 'id').split('.').last
|
908
|
-
rescue ActiveRecord::StatementInvalid
|
909
|
-
"#{table_name}_#{pk || 'id'}_seq"
|
910
|
-
end
|
911
|
-
|
912
|
-
def serial_sequence(table, column)
|
913
|
-
result = exec_query(<<-eosql, 'SCHEMA')
|
914
|
-
SELECT pg_get_serial_sequence('#{table}', '#{column}')
|
915
|
-
eosql
|
916
|
-
result.rows.first.first
|
917
|
-
end
|
918
|
-
|
919
|
-
# Resets the sequence of a table's primary key to the maximum value.
|
920
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
921
|
-
unless pk and sequence
|
922
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
923
|
-
|
924
|
-
pk ||= default_pk
|
925
|
-
sequence ||= default_sequence
|
926
|
-
end
|
927
|
-
|
928
|
-
if @logger && pk && !sequence
|
929
|
-
@logger.warn "#{table} has primary key #{pk} with no default sequence"
|
930
|
-
end
|
931
|
-
|
932
|
-
if pk && sequence
|
933
|
-
quoted_sequence = quote_table_name(sequence)
|
934
|
-
|
935
|
-
select_value <<-end_sql, 'SCHEMA'
|
936
|
-
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
937
|
-
end_sql
|
938
|
-
end
|
939
|
-
end
|
940
|
-
|
941
|
-
# Returns a table's primary key and belonging sequence.
|
942
|
-
def pk_and_sequence_for(table) #:nodoc:
|
943
|
-
# First try looking for a sequence with a dependency on the
|
944
|
-
# given table's primary key.
|
945
|
-
result = query(<<-end_sql, 'SCHEMA')[0]
|
946
|
-
SELECT attr.attname, seq.relname
|
947
|
-
FROM pg_class seq,
|
948
|
-
pg_attribute attr,
|
949
|
-
pg_depend dep,
|
950
|
-
pg_namespace name,
|
951
|
-
pg_constraint cons
|
952
|
-
WHERE seq.oid = dep.objid
|
953
|
-
AND seq.relkind = 'S'
|
954
|
-
AND attr.attrelid = dep.refobjid
|
955
|
-
AND attr.attnum = dep.refobjsubid
|
956
|
-
AND attr.attrelid = cons.conrelid
|
957
|
-
AND attr.attnum = cons.conkey[1]
|
958
|
-
AND cons.contype = 'p'
|
959
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
960
|
-
end_sql
|
961
|
-
|
962
|
-
if result.nil? or result.empty?
|
963
|
-
result = query(<<-end_sql, 'SCHEMA')[0]
|
964
|
-
SELECT attr.attname,
|
965
|
-
CASE
|
966
|
-
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
967
|
-
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
968
|
-
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
969
|
-
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
970
|
-
END
|
971
|
-
FROM pg_class t
|
972
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
973
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
974
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
975
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
976
|
-
AND cons.contype = 'p'
|
977
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
|
978
|
-
end_sql
|
979
|
-
end
|
980
|
-
|
981
|
-
[result.first, result.last]
|
982
|
-
rescue
|
983
|
-
nil
|
984
|
-
end
|
985
|
-
|
986
|
-
# Returns just a table's primary key
|
987
|
-
def primary_key(table)
|
988
|
-
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
|
989
|
-
SELECT attr.attname
|
990
|
-
FROM pg_attribute attr
|
991
|
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
992
|
-
WHERE cons.contype = 'p'
|
993
|
-
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
|
994
|
-
end_sql
|
995
|
-
|
996
|
-
row && row.first
|
997
|
-
end
|
998
|
-
|
999
|
-
# Renames a table.
|
1000
|
-
# Also renames a table's primary key sequence if the sequence name matches the
|
1001
|
-
# Active Record default.
|
1002
|
-
#
|
1003
|
-
# Example:
|
1004
|
-
# rename_table('octopuses', 'octopi')
|
1005
|
-
def rename_table(name, new_name)
|
1006
|
-
clear_cache!
|
1007
|
-
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
1008
|
-
pk, seq = pk_and_sequence_for(new_name)
|
1009
|
-
if seq == "#{name}_#{pk}_seq"
|
1010
|
-
new_seq = "#{new_name}_#{pk}_seq"
|
1011
|
-
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
1012
|
-
end
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
# Adds a new column to the named table.
|
1016
|
-
# See TableDefinition#column for details of the options you can use.
|
1017
|
-
def add_column(table_name, column_name, type, options = {})
|
1018
|
-
clear_cache!
|
1019
|
-
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1020
|
-
add_column_options!(add_column_sql, options)
|
1021
|
-
|
1022
|
-
execute add_column_sql
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
# Changes the column of a table.
|
1026
|
-
def change_column(table_name, column_name, type, options = {})
|
1027
|
-
clear_cache!
|
1028
|
-
quoted_table_name = quote_table_name(table_name)
|
1029
|
-
|
1030
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1031
|
-
|
1032
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
1033
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
1034
|
-
end
|
1035
|
-
|
1036
|
-
# Changes the default value of a table column.
|
1037
|
-
def change_column_default(table_name, column_name, default)
|
1038
|
-
clear_cache!
|
1039
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
1040
|
-
end
|
1041
|
-
|
1042
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
1043
|
-
clear_cache!
|
1044
|
-
unless null || default.nil?
|
1045
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
1046
|
-
end
|
1047
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
# Renames a column in a table.
|
1051
|
-
def rename_column(table_name, column_name, new_column_name)
|
1052
|
-
clear_cache!
|
1053
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1054
|
-
end
|
1055
|
-
|
1056
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
1057
|
-
execute "DROP INDEX #{quote_table_name(index_name)}"
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
def rename_index(table_name, old_name, new_name)
|
1061
|
-
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
1062
|
-
end
|
1063
|
-
|
1064
|
-
def index_name_length
|
1065
|
-
63
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
# Maps logical Rails types to PostgreSQL-specific data types.
|
1069
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
1070
|
-
case type.to_s
|
1071
|
-
when 'binary'
|
1072
|
-
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
1073
|
-
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
1074
|
-
case limit
|
1075
|
-
when nil, 0..0x3fffffff; super(type)
|
1076
|
-
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
1077
|
-
end
|
1078
|
-
when 'text'
|
1079
|
-
# PostgreSQL doesn't support limits on text columns.
|
1080
|
-
# The hard limit is 1Gb, according to section 8.3 in the manual.
|
1081
|
-
case limit
|
1082
|
-
when nil, 0..0x3fffffff; super(type)
|
1083
|
-
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
1084
|
-
end
|
1085
|
-
when 'integer'
|
1086
|
-
return 'integer' unless limit
|
1087
|
-
|
1088
|
-
case limit
|
1089
|
-
when 1, 2; 'smallint'
|
1090
|
-
when 3, 4; 'integer'
|
1091
|
-
when 5..8; 'bigint'
|
1092
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
1093
|
-
end
|
1094
|
-
else
|
1095
|
-
super
|
1096
|
-
end
|
1097
|
-
end
|
1098
|
-
|
1099
|
-
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
1100
|
-
#
|
1101
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
1102
|
-
# requires that the ORDER BY include the distinct column.
|
1103
|
-
#
|
1104
|
-
# distinct("posts.id", "posts.created_at desc")
|
1105
|
-
def distinct(columns, orders) #:nodoc:
|
1106
|
-
return "DISTINCT #{columns}" if orders.empty?
|
1107
|
-
|
1108
|
-
# Construct a clean list of column names from the ORDER BY clause, removing
|
1109
|
-
# any ASC/DESC modifiers
|
1110
|
-
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
|
1111
|
-
order_columns.delete_if { |c| c.blank? }
|
1112
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
1113
|
-
|
1114
|
-
"DISTINCT #{columns}, #{order_columns * ', '}"
|
1115
|
-
end
|
1116
|
-
|
1117
695
|
module Utils
|
1118
696
|
extend self
|
1119
697
|
|
@@ -1133,7 +711,20 @@ module ActiveRecord
|
|
1133
711
|
end
|
1134
712
|
end
|
1135
713
|
|
714
|
+
def use_insert_returning?
|
715
|
+
@use_insert_returning
|
716
|
+
end
|
717
|
+
|
718
|
+
def valid_type?(type)
|
719
|
+
!native_database_types[type].nil?
|
720
|
+
end
|
721
|
+
|
722
|
+
def update_table_definition(table_name, base) #:nodoc:
|
723
|
+
Table.new(table_name, base)
|
724
|
+
end
|
725
|
+
|
1136
726
|
protected
|
727
|
+
|
1137
728
|
# Returns the version of the connected PostgreSQL server.
|
1138
729
|
def postgresql_version
|
1139
730
|
@connection.server_version
|
@@ -1157,39 +748,81 @@ module ActiveRecord
|
|
1157
748
|
end
|
1158
749
|
|
1159
750
|
private
|
1160
|
-
|
751
|
+
|
752
|
+
def get_oid_type(oid, fmod, column_name)
|
753
|
+
OID::TYPE_MAP.fetch(oid, fmod) {
|
754
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
755
|
+
OID::TYPE_MAP[oid] = OID::Identity.new
|
756
|
+
}
|
757
|
+
end
|
758
|
+
|
759
|
+
def reload_type_map
|
760
|
+
OID::TYPE_MAP.clear
|
761
|
+
initialize_type_map
|
762
|
+
end
|
763
|
+
|
764
|
+
def initialize_type_map
|
765
|
+
result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
|
766
|
+
leaves, nodes = result.partition { |row| row['typelem'] == '0' }
|
767
|
+
|
768
|
+
# populate the leaf nodes
|
769
|
+
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
|
770
|
+
OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
|
771
|
+
end
|
772
|
+
|
773
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
|
774
|
+
|
775
|
+
# populate composite types
|
776
|
+
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
777
|
+
if OID.registered_type? row['typname']
|
778
|
+
# this composite type is explicitly registered
|
779
|
+
vector = OID::NAMES[row['typname']]
|
780
|
+
else
|
781
|
+
# use the default for composite types
|
782
|
+
vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
|
783
|
+
end
|
784
|
+
|
785
|
+
OID::TYPE_MAP[row['oid'].to_i] = vector
|
786
|
+
end
|
787
|
+
|
788
|
+
# populate array types
|
789
|
+
arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
790
|
+
array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
|
791
|
+
OID::TYPE_MAP[row['oid'].to_i] = array
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
1161
796
|
|
1162
797
|
def exec_no_cache(sql, binds)
|
1163
798
|
@connection.async_exec(sql, [])
|
1164
799
|
end
|
1165
800
|
|
1166
801
|
def exec_cache(sql, binds)
|
802
|
+
stmt_key = prepare_statement sql
|
803
|
+
|
804
|
+
# Clear the queue
|
805
|
+
@connection.get_last_result
|
806
|
+
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
807
|
+
type_cast(val, col)
|
808
|
+
})
|
809
|
+
@connection.block
|
810
|
+
@connection.get_last_result
|
811
|
+
rescue PGError => e
|
812
|
+
# Get the PG code for the failure. Annoyingly, the code for
|
813
|
+
# prepared statements whose return value may have changed is
|
814
|
+
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
815
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1167
816
|
begin
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
rescue PGError => e
|
1178
|
-
# Get the PG code for the failure. Annoyingly, the code for
|
1179
|
-
# prepared statements whose return value may have changed is
|
1180
|
-
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
1181
|
-
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1182
|
-
begin
|
1183
|
-
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
1184
|
-
rescue
|
1185
|
-
raise e
|
1186
|
-
end
|
1187
|
-
if FEATURE_NOT_SUPPORTED == code
|
1188
|
-
@statements.delete sql_key(sql)
|
1189
|
-
retry
|
1190
|
-
else
|
1191
|
-
raise e
|
1192
|
-
end
|
817
|
+
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
818
|
+
rescue
|
819
|
+
raise e
|
820
|
+
end
|
821
|
+
if FEATURE_NOT_SUPPORTED == code
|
822
|
+
@statements.delete sql_key(sql)
|
823
|
+
retry
|
824
|
+
else
|
825
|
+
raise e
|
1193
826
|
end
|
1194
827
|
end
|
1195
828
|
|
@@ -1219,7 +852,7 @@ module ActiveRecord
|
|
1219
852
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1220
853
|
# connected server's characteristics.
|
1221
854
|
def connect
|
1222
|
-
@connection = PGconn.connect(
|
855
|
+
@connection = PGconn.connect(@connection_parameters)
|
1223
856
|
|
1224
857
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1225
858
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
@@ -1235,7 +868,7 @@ module ActiveRecord
|
|
1235
868
|
if @config[:encoding]
|
1236
869
|
@connection.set_client_encoding(@config[:encoding])
|
1237
870
|
end
|
1238
|
-
self.client_min_messages = @config[:min_messages]
|
871
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
1239
872
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1240
873
|
|
1241
874
|
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
@@ -1243,31 +876,43 @@ module ActiveRecord
|
|
1243
876
|
|
1244
877
|
# If using Active Record's time zone support configure the connection to return
|
1245
878
|
# TIMESTAMP WITH ZONE types in UTC.
|
879
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
1246
880
|
if ActiveRecord::Base.default_timezone == :utc
|
1247
881
|
execute("SET time zone 'UTC'", 'SCHEMA')
|
1248
882
|
elsif @local_tz
|
1249
883
|
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1250
884
|
end
|
885
|
+
|
886
|
+
# SET statements from :variables config hash
|
887
|
+
# http://www.postgresql.org/docs/8.3/static/sql-set.html
|
888
|
+
variables = @config[:variables] || {}
|
889
|
+
variables.map do |k, v|
|
890
|
+
if v == ':default' || v == :default
|
891
|
+
# Sets the value to the global or compile default
|
892
|
+
execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
|
893
|
+
elsif !v.nil?
|
894
|
+
execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
|
895
|
+
end
|
896
|
+
end
|
1251
897
|
end
|
1252
898
|
|
1253
899
|
# Returns the current ID of a table's sequence.
|
1254
900
|
def last_insert_id(sequence_name) #:nodoc:
|
1255
|
-
|
1256
|
-
|
901
|
+
Integer(last_insert_id_value(sequence_name))
|
902
|
+
end
|
903
|
+
|
904
|
+
def last_insert_id_value(sequence_name)
|
905
|
+
last_insert_id_result(sequence_name).rows.first.first
|
906
|
+
end
|
907
|
+
|
908
|
+
def last_insert_id_result(sequence_name) #:nodoc:
|
909
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1257
910
|
end
|
1258
911
|
|
1259
912
|
# Executes a SELECT query and returns the results, performing any data type
|
1260
913
|
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
1261
914
|
def select(sql, name = nil, binds = [])
|
1262
|
-
exec_query(sql, name, binds)
|
1263
|
-
end
|
1264
|
-
|
1265
|
-
def select_raw(sql, name = nil)
|
1266
|
-
res = execute(sql, name)
|
1267
|
-
results = result_as_array(res)
|
1268
|
-
fields = res.fields
|
1269
|
-
res.clear
|
1270
|
-
return fields, results
|
915
|
+
exec_query(sql, name, binds)
|
1271
916
|
end
|
1272
917
|
|
1273
918
|
# Returns the list of a table's column names, data types, and default values.
|
@@ -1290,13 +935,13 @@ module ActiveRecord
|
|
1290
935
|
# - ::regclass is a function that gives the id for a table name
|
1291
936
|
def column_definitions(table_name) #:nodoc:
|
1292
937
|
exec_query(<<-end_sql, 'SCHEMA').rows
|
1293
|
-
|
938
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1294
939
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
940
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
941
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
942
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
943
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
944
|
+
ORDER BY a.attnum
|
1300
945
|
end_sql
|
1301
946
|
end
|
1302
947
|
|
@@ -1315,8 +960,8 @@ module ActiveRecord
|
|
1315
960
|
$1.strip if $1
|
1316
961
|
end
|
1317
962
|
|
1318
|
-
def
|
1319
|
-
TableDefinition.new
|
963
|
+
def create_table_definition(name, temporary, options)
|
964
|
+
TableDefinition.new native_database_types, name, temporary, options
|
1320
965
|
end
|
1321
966
|
end
|
1322
967
|
end
|