activerecord 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- 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 +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- 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 +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- 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/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- 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 +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -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/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- 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,148 @@
|
|
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
|
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) # :nodoc:
|
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:
|
48
|
+
attr_accessor :array
|
35
49
|
# Instantiates a new PostgreSQL column definition in a table.
|
36
|
-
def initialize(name, default, sql_type = nil, null = true)
|
37
|
-
|
50
|
+
def initialize(name, default, oid_type, sql_type = nil, null = true)
|
51
|
+
@oid_type = oid_type
|
52
|
+
if sql_type =~ /\[\]$/
|
53
|
+
@array = true
|
54
|
+
super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
|
55
|
+
else
|
56
|
+
@array = false
|
57
|
+
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
58
|
+
end
|
38
59
|
end
|
39
60
|
|
40
61
|
# :stopdoc:
|
41
62
|
class << self
|
63
|
+
include ConnectionAdapters::PostgreSQLColumn::Cast
|
64
|
+
include ConnectionAdapters::PostgreSQLColumn::ArrayParser
|
42
65
|
attr_accessor :money_precision
|
43
|
-
|
44
|
-
|
66
|
+
end
|
67
|
+
# :startdoc:
|
45
68
|
|
46
|
-
|
47
|
-
|
48
|
-
|
69
|
+
# Extracts the value from a PostgreSQL column default definition.
|
70
|
+
def self.extract_value_from_default(default)
|
71
|
+
# This is a performance optimization for Ruby 1.9.2 in development.
|
72
|
+
# If the value is nil, we return nil straight away without checking
|
73
|
+
# the regular expressions. If we check each regular expression,
|
74
|
+
# Regexp#=== will call NilClass#to_str, which will trigger
|
75
|
+
# method_missing (defined by whiny nil in ActiveSupport) which
|
76
|
+
# makes this method very very slow.
|
77
|
+
return default unless default
|
78
|
+
|
79
|
+
case default
|
80
|
+
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
|
81
|
+
$1
|
82
|
+
# Numeric types
|
83
|
+
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
84
|
+
$1
|
85
|
+
# Character types
|
86
|
+
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
87
|
+
$1
|
88
|
+
# Binary data types
|
89
|
+
when /\A'(.*)'::bytea\z/m
|
90
|
+
$1
|
91
|
+
# Date/time types
|
92
|
+
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
93
|
+
$1
|
94
|
+
when /\A'(.*)'::interval\z/
|
95
|
+
$1
|
96
|
+
# Boolean type
|
97
|
+
when 'true'
|
98
|
+
true
|
99
|
+
when 'false'
|
100
|
+
false
|
101
|
+
# Geometric types
|
102
|
+
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
103
|
+
$1
|
104
|
+
# Network address types
|
105
|
+
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
106
|
+
$1
|
107
|
+
# Bit string types
|
108
|
+
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
109
|
+
$1
|
110
|
+
# XML type
|
111
|
+
when /\A'(.*)'::xml\z/m
|
112
|
+
$1
|
113
|
+
# Arrays
|
114
|
+
when /\A'(.*)'::"?\D+"?\[\]\z/
|
115
|
+
$1
|
116
|
+
# Hstore
|
117
|
+
when /\A'(.*)'::hstore\z/
|
118
|
+
$1
|
119
|
+
# JSON
|
120
|
+
when /\A'(.*)'::json\z/
|
121
|
+
$1
|
122
|
+
# Object identifier types
|
123
|
+
when /\A-?\d+\z/
|
124
|
+
$1
|
49
125
|
else
|
50
|
-
|
51
|
-
|
126
|
+
# Anything else is blank, some user type, or some function
|
127
|
+
# and we can't know the value of that, so return nil.
|
128
|
+
nil
|
52
129
|
end
|
53
130
|
end
|
54
|
-
|
131
|
+
|
132
|
+
def type_cast(value)
|
133
|
+
return if value.nil?
|
134
|
+
return super if encoded?
|
135
|
+
|
136
|
+
@oid_type.type_cast value
|
137
|
+
end
|
55
138
|
|
56
139
|
private
|
140
|
+
|
57
141
|
def extract_limit(sql_type)
|
58
142
|
case sql_type
|
59
143
|
when /^bigint/i; 8
|
60
144
|
when /^smallint/i; 2
|
145
|
+
when /^timestamp/i; nil
|
61
146
|
else super
|
62
147
|
end
|
63
148
|
end
|
@@ -72,6 +157,8 @@ module ActiveRecord
|
|
72
157
|
def extract_precision(sql_type)
|
73
158
|
if sql_type == 'money'
|
74
159
|
self.class.money_precision
|
160
|
+
elsif sql_type =~ /timestamp/i
|
161
|
+
$1.to_i if sql_type =~ /\((\d+)\)/
|
75
162
|
else
|
76
163
|
super
|
77
164
|
end
|
@@ -80,129 +167,102 @@ module ActiveRecord
|
|
80
167
|
# Maps PostgreSQL-specific data types to logical Rails types.
|
81
168
|
def simplified_type(field_type)
|
82
169
|
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
|
170
|
+
# Numeric and monetary types
|
171
|
+
when /^(?:real|double precision)$/
|
172
|
+
:float
|
173
|
+
# Monetary types
|
174
|
+
when 'money'
|
175
|
+
:decimal
|
176
|
+
when 'hstore'
|
177
|
+
:hstore
|
178
|
+
when 'ltree'
|
179
|
+
:ltree
|
180
|
+
# Network address types
|
181
|
+
when 'inet'
|
182
|
+
:inet
|
183
|
+
when 'cidr'
|
184
|
+
:cidr
|
185
|
+
when 'macaddr'
|
186
|
+
:macaddr
|
187
|
+
# Character types
|
188
|
+
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
189
|
+
:string
|
190
|
+
# Binary data types
|
191
|
+
when 'bytea'
|
192
|
+
:binary
|
193
|
+
# Date/time types
|
194
|
+
when /^timestamp with(?:out)? time zone$/
|
195
|
+
:datetime
|
196
|
+
when /^interval(?:|\(\d+\))$/
|
197
|
+
:string
|
198
|
+
# Geometric types
|
199
|
+
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
200
|
+
:string
|
201
|
+
# Bit strings
|
202
|
+
when /^bit(?: varying)?(?:\(\d+\))?$/
|
203
|
+
:string
|
204
|
+
# XML type
|
205
|
+
when 'xml'
|
206
|
+
:xml
|
207
|
+
# tsvector type
|
208
|
+
when 'tsvector'
|
209
|
+
:tsvector
|
210
|
+
# Arrays
|
211
|
+
when /^\D+\[\]$/
|
212
|
+
:string
|
213
|
+
# Object identifier types
|
214
|
+
when 'oid'
|
215
|
+
:integer
|
216
|
+
# UUID type
|
217
|
+
when 'uuid'
|
218
|
+
:uuid
|
219
|
+
# JSON type
|
220
|
+
when 'json'
|
221
|
+
:json
|
222
|
+
# Small and big integer types
|
223
|
+
when /^(?:small|big)int$/
|
224
|
+
:integer
|
225
|
+
when /(num|date|tstz|ts|int4|int8)range$/
|
226
|
+
field_type.to_sym
|
227
|
+
# Pass through all types that are not specific to PostgreSQL.
|
228
|
+
else
|
229
|
+
super
|
185
230
|
end
|
186
231
|
end
|
187
232
|
end
|
188
233
|
|
189
|
-
# The PostgreSQL adapter works
|
190
|
-
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
234
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
191
235
|
#
|
192
236
|
# Options:
|
193
237
|
#
|
194
|
-
# * <tt>:host</tt> - Defaults to
|
238
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
239
|
+
# the default is to connect to localhost.
|
195
240
|
# * <tt>:port</tt> - Defaults to 5432.
|
196
|
-
# * <tt>:username</tt> - Defaults to
|
197
|
-
# * <tt>:password</tt> -
|
198
|
-
# * <tt>:database</tt> -
|
241
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
242
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
243
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
199
244
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
245
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
246
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
247
|
# <encoding></tt> call on the connection.
|
203
248
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
249
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
250
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
251
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
252
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
253
|
+
# defaults to true.
|
254
|
+
#
|
255
|
+
# Any further options are used as connection parameters to libpq. See
|
256
|
+
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
|
257
|
+
# list of parameters.
|
258
|
+
#
|
259
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
260
|
+
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
|
205
261
|
class PostgreSQLAdapter < AbstractAdapter
|
262
|
+
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
|
263
|
+
attr_accessor :array
|
264
|
+
end
|
265
|
+
|
206
266
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
207
267
|
def xml(*args)
|
208
268
|
options = args.extract_options!
|
@@ -213,32 +273,132 @@ module ActiveRecord
|
|
213
273
|
options = args.extract_options!
|
214
274
|
column(args[0], 'tsvector', options)
|
215
275
|
end
|
276
|
+
|
277
|
+
def int4range(name, options = {})
|
278
|
+
column(name, 'int4range', options)
|
279
|
+
end
|
280
|
+
|
281
|
+
def int8range(name, options = {})
|
282
|
+
column(name, 'int8range', options)
|
283
|
+
end
|
284
|
+
|
285
|
+
def tsrange(name, options = {})
|
286
|
+
column(name, 'tsrange', options)
|
287
|
+
end
|
288
|
+
|
289
|
+
def tstzrange(name, options = {})
|
290
|
+
column(name, 'tstzrange', options)
|
291
|
+
end
|
292
|
+
|
293
|
+
def numrange(name, options = {})
|
294
|
+
column(name, 'numrange', options)
|
295
|
+
end
|
296
|
+
|
297
|
+
def daterange(name, options = {})
|
298
|
+
column(name, 'daterange', options)
|
299
|
+
end
|
300
|
+
|
301
|
+
def hstore(name, options = {})
|
302
|
+
column(name, 'hstore', options)
|
303
|
+
end
|
304
|
+
|
305
|
+
def ltree(name, options = {})
|
306
|
+
column(name, 'ltree', options)
|
307
|
+
end
|
308
|
+
|
309
|
+
def inet(name, options = {})
|
310
|
+
column(name, 'inet', options)
|
311
|
+
end
|
312
|
+
|
313
|
+
def cidr(name, options = {})
|
314
|
+
column(name, 'cidr', options)
|
315
|
+
end
|
316
|
+
|
317
|
+
def macaddr(name, options = {})
|
318
|
+
column(name, 'macaddr', options)
|
319
|
+
end
|
320
|
+
|
321
|
+
def uuid(name, options = {})
|
322
|
+
column(name, 'uuid', options)
|
323
|
+
end
|
324
|
+
|
325
|
+
def json(name, options = {})
|
326
|
+
column(name, 'json', options)
|
327
|
+
end
|
328
|
+
|
329
|
+
def column(name, type = nil, options = {})
|
330
|
+
super
|
331
|
+
column = self[name]
|
332
|
+
column.array = options[:array]
|
333
|
+
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
def new_column_definition(base, name, type)
|
340
|
+
definition = ColumnDefinition.new base, name, type
|
341
|
+
@columns << definition
|
342
|
+
@columns_hash[name] = definition
|
343
|
+
definition
|
344
|
+
end
|
216
345
|
end
|
217
346
|
|
218
347
|
ADAPTER_NAME = 'PostgreSQL'
|
219
348
|
|
220
349
|
NATIVE_DATABASE_TYPES = {
|
221
|
-
:
|
222
|
-
:
|
223
|
-
:
|
224
|
-
:
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
350
|
+
primary_key: "serial primary key",
|
351
|
+
string: { name: "character varying", limit: 255 },
|
352
|
+
text: { name: "text" },
|
353
|
+
integer: { name: "integer" },
|
354
|
+
float: { name: "float" },
|
355
|
+
decimal: { name: "decimal" },
|
356
|
+
datetime: { name: "timestamp" },
|
357
|
+
timestamp: { name: "timestamp" },
|
358
|
+
time: { name: "time" },
|
359
|
+
date: { name: "date" },
|
360
|
+
daterange: { name: "daterange" },
|
361
|
+
numrange: { name: "numrange" },
|
362
|
+
tsrange: { name: "tsrange" },
|
363
|
+
tstzrange: { name: "tstzrange" },
|
364
|
+
int4range: { name: "int4range" },
|
365
|
+
int8range: { name: "int8range" },
|
366
|
+
binary: { name: "bytea" },
|
367
|
+
boolean: { name: "boolean" },
|
368
|
+
xml: { name: "xml" },
|
369
|
+
tsvector: { name: "tsvector" },
|
370
|
+
hstore: { name: "hstore" },
|
371
|
+
inet: { name: "inet" },
|
372
|
+
cidr: { name: "cidr" },
|
373
|
+
macaddr: { name: "macaddr" },
|
374
|
+
uuid: { name: "uuid" },
|
375
|
+
json: { name: "json" },
|
376
|
+
ltree: { name: "ltree" }
|
235
377
|
}
|
236
378
|
|
379
|
+
include Quoting
|
380
|
+
include ReferentialIntegrity
|
381
|
+
include SchemaStatements
|
382
|
+
include DatabaseStatements
|
383
|
+
|
237
384
|
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
238
385
|
def adapter_name
|
239
386
|
ADAPTER_NAME
|
240
387
|
end
|
241
388
|
|
389
|
+
# Adds `:array` option to the default set provided by the
|
390
|
+
# AbstractAdapter
|
391
|
+
def prepare_column_options(column, types)
|
392
|
+
spec = super
|
393
|
+
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
394
|
+
spec
|
395
|
+
end
|
396
|
+
|
397
|
+
# Adds `:array` as a valid migration key
|
398
|
+
def migration_keys
|
399
|
+
super + [:array]
|
400
|
+
end
|
401
|
+
|
242
402
|
# Returns +true+, since this connection adapter supports prepared statement
|
243
403
|
# caching.
|
244
404
|
def supports_statement_cache?
|
@@ -249,6 +409,14 @@ module ActiveRecord
|
|
249
409
|
true
|
250
410
|
end
|
251
411
|
|
412
|
+
def supports_partial_index?
|
413
|
+
true
|
414
|
+
end
|
415
|
+
|
416
|
+
def supports_transaction_isolation?
|
417
|
+
true
|
418
|
+
end
|
419
|
+
|
252
420
|
class StatementPool < ConnectionAdapters::StatementPool
|
253
421
|
def initialize(connection, max)
|
254
422
|
super
|
@@ -286,19 +454,20 @@ module ActiveRecord
|
|
286
454
|
end
|
287
455
|
|
288
456
|
private
|
289
|
-
def cache
|
290
|
-
@cache[$$]
|
291
|
-
end
|
292
457
|
|
293
|
-
|
294
|
-
|
295
|
-
|
458
|
+
def cache
|
459
|
+
@cache[Process.pid]
|
460
|
+
end
|
296
461
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
462
|
+
def dealloc(key)
|
463
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
464
|
+
end
|
465
|
+
|
466
|
+
def connection_active?
|
467
|
+
@connection.status == PGconn::CONNECTION_OK
|
468
|
+
rescue PGError
|
469
|
+
false
|
470
|
+
end
|
302
471
|
end
|
303
472
|
|
304
473
|
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
|
@@ -309,7 +478,7 @@ module ActiveRecord
|
|
309
478
|
def initialize(connection, logger, connection_parameters, config)
|
310
479
|
super(connection, logger)
|
311
480
|
|
312
|
-
if config.fetch(:prepared_statements) { true }
|
481
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
313
482
|
@visitor = Arel::Visitors::PostgreSQL.new self
|
314
483
|
else
|
315
484
|
@visitor = BindSubstitution.new self
|
@@ -323,13 +492,15 @@ module ActiveRecord
|
|
323
492
|
|
324
493
|
connect
|
325
494
|
@statements = StatementPool.new @connection,
|
326
|
-
config.fetch(:statement_limit) { 1000 }
|
495
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
|
327
496
|
|
328
497
|
if postgresql_version < 80200
|
329
498
|
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
330
499
|
end
|
331
500
|
|
501
|
+
initialize_type_map
|
332
502
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
503
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
333
504
|
end
|
334
505
|
|
335
506
|
# Clears the prepared statements cache.
|
@@ -347,9 +518,8 @@ module ActiveRecord
|
|
347
518
|
|
348
519
|
# Close then reopen the connection.
|
349
520
|
def reconnect!
|
350
|
-
|
521
|
+
super
|
351
522
|
@connection.reset
|
352
|
-
@open_transactions = 0
|
353
523
|
configure_connection
|
354
524
|
end
|
355
525
|
|
@@ -361,7 +531,7 @@ module ActiveRecord
|
|
361
531
|
# Disconnects from the database if already connected. Otherwise, this
|
362
532
|
# method does nothing.
|
363
533
|
def disconnect!
|
364
|
-
|
534
|
+
super
|
365
535
|
@connection.close rescue nil
|
366
536
|
end
|
367
537
|
|
@@ -405,103 +575,55 @@ module ActiveRecord
|
|
405
575
|
true
|
406
576
|
end
|
407
577
|
|
408
|
-
# Returns
|
409
|
-
def
|
410
|
-
|
578
|
+
# Returns true if pg > 9.2
|
579
|
+
def supports_extensions?
|
580
|
+
postgresql_version >= 90200
|
411
581
|
end
|
412
582
|
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
def escape_bytea(value)
|
417
|
-
@connection.escape_bytea(value) if value
|
583
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
584
|
+
def supports_ranges?
|
585
|
+
postgresql_version >= 90200
|
418
586
|
end
|
419
587
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
@connection.unescape_bytea(value) if value
|
588
|
+
def enable_extension(name)
|
589
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap {
|
590
|
+
reload_type_map
|
591
|
+
}
|
425
592
|
end
|
426
593
|
|
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
|
594
|
+
def disable_extension(name)
|
595
|
+
exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap {
|
596
|
+
reload_type_map
|
597
|
+
}
|
454
598
|
end
|
455
599
|
|
456
|
-
def
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
return super unless 'bytea' == column.sql_type
|
462
|
-
{ :value => value, :format => 1 }
|
463
|
-
else
|
464
|
-
super
|
600
|
+
def extension_enabled?(name)
|
601
|
+
if supports_extensions?
|
602
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
|
603
|
+
'SCHEMA'
|
604
|
+
res.column_types['exists'].type_cast res.rows.first.first
|
465
605
|
end
|
466
606
|
end
|
467
607
|
|
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)
|
608
|
+
def extensions
|
609
|
+
if supports_extensions?
|
610
|
+
res = exec_query "SELECT extname from pg_extension", "SCHEMA"
|
611
|
+
res.rows.map { |r| res.column_types['extname'].type_cast r.first }
|
486
612
|
else
|
487
|
-
|
488
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
613
|
+
super
|
489
614
|
end
|
490
615
|
end
|
491
616
|
|
492
|
-
#
|
493
|
-
def
|
494
|
-
|
617
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
618
|
+
def table_alias_length
|
619
|
+
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
|
495
620
|
end
|
496
621
|
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
501
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
502
|
-
else
|
503
|
-
super
|
622
|
+
def add_column_options!(sql, options)
|
623
|
+
if options[:array] || options[:column].try(:array)
|
624
|
+
sql << '[]'
|
504
625
|
end
|
626
|
+
super
|
505
627
|
end
|
506
628
|
|
507
629
|
# Set the authorized user for this session
|
@@ -510,610 +632,6 @@ module ActiveRecord
|
|
510
632
|
exec_query "SET SESSION AUTHORIZATION #{user}"
|
511
633
|
end
|
512
634
|
|
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
635
|
module Utils
|
1118
636
|
extend self
|
1119
637
|
|
@@ -1133,7 +651,12 @@ module ActiveRecord
|
|
1133
651
|
end
|
1134
652
|
end
|
1135
653
|
|
654
|
+
def use_insert_returning?
|
655
|
+
@use_insert_returning
|
656
|
+
end
|
657
|
+
|
1136
658
|
protected
|
659
|
+
|
1137
660
|
# Returns the version of the connected PostgreSQL server.
|
1138
661
|
def postgresql_version
|
1139
662
|
@connection.server_version
|
@@ -1144,8 +667,6 @@ module ActiveRecord
|
|
1144
667
|
UNIQUE_VIOLATION = "23505"
|
1145
668
|
|
1146
669
|
def translate_exception(exception, message)
|
1147
|
-
return exception unless exception.respond_to?(:result)
|
1148
|
-
|
1149
670
|
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
1150
671
|
when UNIQUE_VIOLATION
|
1151
672
|
RecordNotUnique.new(message, exception)
|
@@ -1157,39 +678,67 @@ module ActiveRecord
|
|
1157
678
|
end
|
1158
679
|
|
1159
680
|
private
|
681
|
+
|
682
|
+
def reload_type_map
|
683
|
+
OID::TYPE_MAP.clear
|
684
|
+
initialize_type_map
|
685
|
+
end
|
686
|
+
|
687
|
+
def initialize_type_map
|
688
|
+
result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
|
689
|
+
leaves, nodes = result.partition { |row| row['typelem'] == '0' }
|
690
|
+
|
691
|
+
# populate the leaf nodes
|
692
|
+
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
|
693
|
+
OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
|
694
|
+
end
|
695
|
+
|
696
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
|
697
|
+
|
698
|
+
# populate composite types
|
699
|
+
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
700
|
+
vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
|
701
|
+
OID::TYPE_MAP[row['oid'].to_i] = vector
|
702
|
+
end
|
703
|
+
|
704
|
+
# populate array types
|
705
|
+
arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
706
|
+
array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
|
707
|
+
OID::TYPE_MAP[row['oid'].to_i] = array
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
1160
711
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
1161
712
|
|
1162
713
|
def exec_no_cache(sql, binds)
|
1163
|
-
@connection.async_exec(sql
|
714
|
+
@connection.async_exec(sql)
|
1164
715
|
end
|
1165
716
|
|
1166
717
|
def exec_cache(sql, binds)
|
718
|
+
stmt_key = prepare_statement sql
|
719
|
+
|
720
|
+
# Clear the queue
|
721
|
+
@connection.get_last_result
|
722
|
+
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
723
|
+
type_cast(val, col)
|
724
|
+
})
|
725
|
+
@connection.block
|
726
|
+
@connection.get_last_result
|
727
|
+
rescue PGError => e
|
728
|
+
# Get the PG code for the failure. Annoyingly, the code for
|
729
|
+
# prepared statements whose return value may have changed is
|
730
|
+
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
731
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1167
732
|
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
|
733
|
+
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
734
|
+
rescue
|
735
|
+
raise e
|
736
|
+
end
|
737
|
+
if FEATURE_NOT_SUPPORTED == code
|
738
|
+
@statements.delete sql_key(sql)
|
739
|
+
retry
|
740
|
+
else
|
741
|
+
raise e
|
1193
742
|
end
|
1194
743
|
end
|
1195
744
|
|
@@ -1219,7 +768,7 @@ module ActiveRecord
|
|
1219
768
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1220
769
|
# connected server's characteristics.
|
1221
770
|
def connect
|
1222
|
-
@connection = PGconn.connect(
|
771
|
+
@connection = PGconn.connect(@connection_parameters)
|
1223
772
|
|
1224
773
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1225
774
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
@@ -1235,7 +784,7 @@ module ActiveRecord
|
|
1235
784
|
if @config[:encoding]
|
1236
785
|
@connection.set_client_encoding(@config[:encoding])
|
1237
786
|
end
|
1238
|
-
self.client_min_messages = @config[:min_messages]
|
787
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
1239
788
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1240
789
|
|
1241
790
|
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
@@ -1243,23 +792,43 @@ module ActiveRecord
|
|
1243
792
|
|
1244
793
|
# If using Active Record's time zone support configure the connection to return
|
1245
794
|
# TIMESTAMP WITH ZONE types in UTC.
|
795
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
1246
796
|
if ActiveRecord::Base.default_timezone == :utc
|
1247
797
|
execute("SET time zone 'UTC'", 'SCHEMA')
|
1248
798
|
elsif @local_tz
|
1249
799
|
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1250
800
|
end
|
801
|
+
|
802
|
+
# SET statements from :variables config hash
|
803
|
+
# http://www.postgresql.org/docs/8.3/static/sql-set.html
|
804
|
+
variables = @config[:variables] || {}
|
805
|
+
variables.map do |k, v|
|
806
|
+
if v == ':default' || v == :default
|
807
|
+
# Sets the value to the global or compile default
|
808
|
+
execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
|
809
|
+
elsif !v.nil?
|
810
|
+
execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
|
811
|
+
end
|
812
|
+
end
|
1251
813
|
end
|
1252
814
|
|
1253
815
|
# Returns the current ID of a table's sequence.
|
1254
816
|
def last_insert_id(sequence_name) #:nodoc:
|
1255
|
-
|
1256
|
-
|
817
|
+
Integer(last_insert_id_value(sequence_name))
|
818
|
+
end
|
819
|
+
|
820
|
+
def last_insert_id_value(sequence_name)
|
821
|
+
last_insert_id_result(sequence_name).rows.first.first
|
822
|
+
end
|
823
|
+
|
824
|
+
def last_insert_id_result(sequence_name) #:nodoc:
|
825
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1257
826
|
end
|
1258
827
|
|
1259
828
|
# Executes a SELECT query and returns the results, performing any data type
|
1260
829
|
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
1261
830
|
def select(sql, name = nil, binds = [])
|
1262
|
-
exec_query(sql, name, binds)
|
831
|
+
exec_query(sql, name, binds)
|
1263
832
|
end
|
1264
833
|
|
1265
834
|
def select_raw(sql, name = nil)
|
@@ -1290,13 +859,13 @@ module ActiveRecord
|
|
1290
859
|
# - ::regclass is a function that gives the id for a table name
|
1291
860
|
def column_definitions(table_name) #:nodoc:
|
1292
861
|
exec_query(<<-end_sql, 'SCHEMA').rows
|
1293
|
-
|
862
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1294
863
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
864
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
865
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
866
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
867
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
868
|
+
ORDER BY a.attnum
|
1300
869
|
end_sql
|
1301
870
|
end
|
1302
871
|
|