activerecord 3.0.0 → 4.0.0
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 +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -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 +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,56 +1,148 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require '
|
3
|
-
require '
|
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'
|
10
|
+
require 'arel/visitors/bind_visitor'
|
11
|
+
|
12
|
+
# Make sure we're using pg high enough for PGResult#values
|
13
|
+
gem 'pg', '~> 0.11'
|
14
|
+
require 'pg'
|
15
|
+
|
16
|
+
require 'ipaddr'
|
4
17
|
|
5
18
|
module ActiveRecord
|
6
|
-
|
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
|
+
|
7
26
|
# Establishes a connection to the database that's used by all Active Record objects
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
20
|
-
raise ArgumentError, "No database specified. Missing argument: database."
|
21
|
-
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) }
|
22
38
|
|
23
39
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
24
40
|
# so just pass a nil connection object for the time being.
|
25
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger,
|
41
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
26
42
|
end
|
27
43
|
end
|
28
44
|
|
29
45
|
module ConnectionAdapters
|
30
|
-
class TableDefinition
|
31
|
-
def xml(*args)
|
32
|
-
options = args.extract_options!
|
33
|
-
column(args[0], 'xml', options)
|
34
|
-
end
|
35
|
-
end
|
36
46
|
# PostgreSQL-specific extensions to column definitions in a table.
|
37
47
|
class PostgreSQLColumn < Column #:nodoc:
|
48
|
+
attr_accessor :array
|
38
49
|
# Instantiates a new PostgreSQL column definition in a table.
|
39
|
-
def initialize(name, default, sql_type = nil, null = true)
|
40
|
-
|
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
|
41
59
|
end
|
42
60
|
|
43
61
|
# :stopdoc:
|
44
62
|
class << self
|
63
|
+
include ConnectionAdapters::PostgreSQLColumn::Cast
|
64
|
+
include ConnectionAdapters::PostgreSQLColumn::ArrayParser
|
45
65
|
attr_accessor :money_precision
|
46
66
|
end
|
47
67
|
# :startdoc:
|
48
68
|
|
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*)?\)?(::bigint)?)\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
|
125
|
+
else
|
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
|
129
|
+
end
|
130
|
+
end
|
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
|
138
|
+
|
49
139
|
private
|
140
|
+
|
50
141
|
def extract_limit(sql_type)
|
51
142
|
case sql_type
|
52
143
|
when /^bigint/i; 8
|
53
144
|
when /^smallint/i; 2
|
145
|
+
when /^timestamp/i; nil
|
54
146
|
else super
|
55
147
|
end
|
56
148
|
end
|
@@ -65,6 +157,8 @@ module ActiveRecord
|
|
65
157
|
def extract_precision(sql_type)
|
66
158
|
if sql_type == 'money'
|
67
159
|
self.class.money_precision
|
160
|
+
elsif sql_type =~ /timestamp/i
|
161
|
+
$1.to_i if sql_type =~ /\((\d+)\)/
|
68
162
|
else
|
69
163
|
super
|
70
164
|
end
|
@@ -73,850 +167,653 @@ module ActiveRecord
|
|
73
167
|
# Maps PostgreSQL-specific data types to logical Rails types.
|
74
168
|
def simplified_type(field_type)
|
75
169
|
case field_type
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
when /\A'(.*)'::bytea\z/m
|
137
|
-
$1
|
138
|
-
# Date/time types
|
139
|
-
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
140
|
-
$1
|
141
|
-
when /\A'(.*)'::interval\z/
|
142
|
-
$1
|
143
|
-
# Boolean type
|
144
|
-
when 'true'
|
145
|
-
true
|
146
|
-
when 'false'
|
147
|
-
false
|
148
|
-
# Geometric types
|
149
|
-
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
150
|
-
$1
|
151
|
-
# Network address types
|
152
|
-
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
153
|
-
$1
|
154
|
-
# Bit string types
|
155
|
-
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
156
|
-
$1
|
157
|
-
# XML type
|
158
|
-
when /\A'(.*)'::xml\z/m
|
159
|
-
$1
|
160
|
-
# Arrays
|
161
|
-
when /\A'(.*)'::"?\D+"?\[\]\z/
|
162
|
-
$1
|
163
|
-
# Object identifier types
|
164
|
-
when /\A-?\d+\z/
|
165
|
-
$1
|
166
|
-
else
|
167
|
-
# Anything else is blank, some user type, or some function
|
168
|
-
# and we can't know the value of that, so return nil.
|
169
|
-
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
|
170
230
|
end
|
171
231
|
end
|
172
232
|
end
|
173
|
-
end
|
174
233
|
|
175
|
-
|
176
|
-
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
177
|
-
# 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.
|
178
235
|
#
|
179
236
|
# Options:
|
180
237
|
#
|
181
|
-
# * <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.
|
182
240
|
# * <tt>:port</tt> - Defaults to 5432.
|
183
|
-
# * <tt>:username</tt> - Defaults to
|
184
|
-
# * <tt>:password</tt> -
|
185
|
-
# * <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.
|
186
244
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
187
|
-
# as a string of comma-separated schema names.
|
245
|
+
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
188
246
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
189
247
|
# <encoding></tt> call on the connection.
|
190
248
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
191
249
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
192
|
-
# * <tt>:
|
193
|
-
#
|
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 .
|
194
261
|
class PostgreSQLAdapter < AbstractAdapter
|
195
|
-
|
262
|
+
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
|
263
|
+
attr_accessor :array
|
264
|
+
end
|
196
265
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
:float => { :name => "float" },
|
203
|
-
:decimal => { :name => "decimal" },
|
204
|
-
:datetime => { :name => "timestamp" },
|
205
|
-
:timestamp => { :name => "timestamp" },
|
206
|
-
:time => { :name => "time" },
|
207
|
-
:date => { :name => "date" },
|
208
|
-
:binary => { :name => "bytea" },
|
209
|
-
:boolean => { :name => "boolean" },
|
210
|
-
:xml => { :name => "xml" }
|
211
|
-
}
|
266
|
+
module ColumnMethods
|
267
|
+
def xml(*args)
|
268
|
+
options = args.extract_options!
|
269
|
+
column(args[0], 'xml', options)
|
270
|
+
end
|
212
271
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
272
|
+
def tsvector(*args)
|
273
|
+
options = args.extract_options!
|
274
|
+
column(args[0], 'tsvector', options)
|
275
|
+
end
|
217
276
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
@connection_parameters, @config = connection_parameters, config
|
277
|
+
def int4range(name, options = {})
|
278
|
+
column(name, 'int4range', options)
|
279
|
+
end
|
222
280
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
@postgresql_version = nil
|
281
|
+
def int8range(name, options = {})
|
282
|
+
column(name, 'int8range', options)
|
283
|
+
end
|
227
284
|
|
228
|
-
|
229
|
-
|
230
|
-
|
285
|
+
def tsrange(name, options = {})
|
286
|
+
column(name, 'tsrange', options)
|
287
|
+
end
|
231
288
|
|
232
|
-
|
233
|
-
|
234
|
-
if @connection.respond_to?(:status)
|
235
|
-
@connection.status == PGconn::CONNECTION_OK
|
236
|
-
else
|
237
|
-
# We're asking the driver, not Active Record, so use @connection.query instead of #query
|
238
|
-
@connection.query 'SELECT 1'
|
239
|
-
true
|
289
|
+
def tstzrange(name, options = {})
|
290
|
+
column(name, 'tstzrange', options)
|
240
291
|
end
|
241
|
-
# postgres-pr raises a NoMethodError when querying if no connection is available.
|
242
|
-
rescue PGError, NoMethodError
|
243
|
-
false
|
244
|
-
end
|
245
292
|
|
246
|
-
|
247
|
-
|
248
|
-
if @connection.respond_to?(:reset)
|
249
|
-
@connection.reset
|
250
|
-
configure_connection
|
251
|
-
else
|
252
|
-
disconnect!
|
253
|
-
connect
|
293
|
+
def numrange(name, options = {})
|
294
|
+
column(name, 'numrange', options)
|
254
295
|
end
|
255
|
-
end
|
256
296
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
297
|
+
def daterange(name, options = {})
|
298
|
+
column(name, 'daterange', options)
|
299
|
+
end
|
261
300
|
|
262
|
-
|
263
|
-
|
264
|
-
|
301
|
+
def hstore(name, options = {})
|
302
|
+
column(name, 'hstore', options)
|
303
|
+
end
|
265
304
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
end
|
305
|
+
def ltree(name, options = {})
|
306
|
+
column(name, 'ltree', options)
|
307
|
+
end
|
270
308
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
end
|
309
|
+
def inet(name, options = {})
|
310
|
+
column(name, 'inet', options)
|
311
|
+
end
|
275
312
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
execute('SET standard_conforming_strings = on') rescue nil
|
280
|
-
ensure
|
281
|
-
self.client_min_messages = old
|
282
|
-
end
|
313
|
+
def cidr(name, options = {})
|
314
|
+
column(name, 'cidr', options)
|
315
|
+
end
|
283
316
|
|
284
|
-
|
285
|
-
|
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
|
286
328
|
end
|
287
329
|
|
288
|
-
|
289
|
-
|
330
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
331
|
+
include ColumnMethods
|
332
|
+
|
333
|
+
# Defines the primary key field.
|
334
|
+
# Use of the native PostgreSQL UUID type is supported, and can be used
|
335
|
+
# by defining your tables as such:
|
336
|
+
#
|
337
|
+
# create_table :stuffs, id: :uuid do |t|
|
338
|
+
# t.string :content
|
339
|
+
# t.timestamps
|
340
|
+
# end
|
341
|
+
#
|
342
|
+
# By default, this will use the +uuid_generate_v4()+ function from the
|
343
|
+
# +uuid-ossp+ extension, which MUST be enabled on your databse. To enable
|
344
|
+
# the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
|
345
|
+
# migrations To use a UUID primary key without +uuid-ossp+ enabled, you can
|
346
|
+
# set the +:default+ option to nil:
|
347
|
+
#
|
348
|
+
# create_table :stuffs, id: false do |t|
|
349
|
+
# t.primary_key :id, :uuid, default: nil
|
350
|
+
# t.uuid :foo_id
|
351
|
+
# t.timestamps
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# You may also pass a different UUID generation function from +uuid-ossp+
|
355
|
+
# or another library.
|
356
|
+
#
|
357
|
+
# Note that setting the UUID primary key default value to +nil+
|
358
|
+
# will require you to assure that you always provide a UUID value
|
359
|
+
# before saving a record (as primary keys cannot be nil). This might be
|
360
|
+
# done via the SecureRandom.uuid method and a +before_save+ callback,
|
361
|
+
# for instance.
|
362
|
+
def primary_key(name, type = :primary_key, options = {})
|
363
|
+
return super unless type == :uuid
|
364
|
+
options[:default] = options.fetch(:default, 'uuid_generate_v4()')
|
365
|
+
options[:primary_key] = true
|
366
|
+
column name, type, options
|
367
|
+
end
|
368
|
+
|
369
|
+
def column(name, type = nil, options = {})
|
370
|
+
super
|
371
|
+
column = self[name]
|
372
|
+
column.array = options[:array]
|
373
|
+
|
374
|
+
self
|
375
|
+
end
|
376
|
+
|
377
|
+
def xml(options = {})
|
378
|
+
column(args[0], :text, options)
|
379
|
+
end
|
380
|
+
|
381
|
+
private
|
382
|
+
|
383
|
+
def create_column_definition(name, type)
|
384
|
+
ColumnDefinition.new name, type
|
385
|
+
end
|
290
386
|
end
|
291
387
|
|
292
|
-
|
293
|
-
|
388
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
389
|
+
include ColumnMethods
|
294
390
|
end
|
295
391
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
392
|
+
ADAPTER_NAME = 'PostgreSQL'
|
393
|
+
|
394
|
+
NATIVE_DATABASE_TYPES = {
|
395
|
+
primary_key: "serial primary key",
|
396
|
+
string: { name: "character varying", limit: 255 },
|
397
|
+
text: { name: "text" },
|
398
|
+
integer: { name: "integer" },
|
399
|
+
float: { name: "float" },
|
400
|
+
decimal: { name: "decimal" },
|
401
|
+
datetime: { name: "timestamp" },
|
402
|
+
timestamp: { name: "timestamp" },
|
403
|
+
time: { name: "time" },
|
404
|
+
date: { name: "date" },
|
405
|
+
daterange: { name: "daterange" },
|
406
|
+
numrange: { name: "numrange" },
|
407
|
+
tsrange: { name: "tsrange" },
|
408
|
+
tstzrange: { name: "tstzrange" },
|
409
|
+
int4range: { name: "int4range" },
|
410
|
+
int8range: { name: "int8range" },
|
411
|
+
binary: { name: "bytea" },
|
412
|
+
boolean: { name: "boolean" },
|
413
|
+
xml: { name: "xml" },
|
414
|
+
tsvector: { name: "tsvector" },
|
415
|
+
hstore: { name: "hstore" },
|
416
|
+
inet: { name: "inet" },
|
417
|
+
cidr: { name: "cidr" },
|
418
|
+
macaddr: { name: "macaddr" },
|
419
|
+
uuid: { name: "uuid" },
|
420
|
+
json: { name: "json" },
|
421
|
+
ltree: { name: "ltree" }
|
422
|
+
}
|
423
|
+
|
424
|
+
include Quoting
|
425
|
+
include ReferentialIntegrity
|
426
|
+
include SchemaStatements
|
427
|
+
include DatabaseStatements
|
428
|
+
|
429
|
+
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
430
|
+
def adapter_name
|
431
|
+
ADAPTER_NAME
|
300
432
|
end
|
301
433
|
|
302
|
-
#
|
434
|
+
# Adds `:array` option to the default set provided by the
|
435
|
+
# AbstractAdapter
|
436
|
+
def prepare_column_options(column, types)
|
437
|
+
spec = super
|
438
|
+
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
439
|
+
spec
|
440
|
+
end
|
303
441
|
|
304
|
-
#
|
305
|
-
def
|
306
|
-
|
442
|
+
# Adds `:array` as a valid migration key
|
443
|
+
def migration_keys
|
444
|
+
super + [:array]
|
307
445
|
end
|
308
446
|
|
309
|
-
#
|
310
|
-
#
|
311
|
-
|
312
|
-
|
313
|
-
@connection.unescape_bytea(value) if value
|
447
|
+
# Returns +true+, since this connection adapter supports prepared statement
|
448
|
+
# caching.
|
449
|
+
def supports_statement_cache?
|
450
|
+
true
|
314
451
|
end
|
315
452
|
|
316
|
-
|
317
|
-
|
318
|
-
return super unless column
|
319
|
-
|
320
|
-
if value.kind_of?(String) && column.type == :binary
|
321
|
-
"'#{escape_bytea(value)}'"
|
322
|
-
elsif value.kind_of?(String) && column.sql_type == 'xml'
|
323
|
-
"xml '#{quote_string(value)}'"
|
324
|
-
elsif value.kind_of?(Numeric) && column.sql_type == 'money'
|
325
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
326
|
-
"'#{value}'"
|
327
|
-
elsif value.kind_of?(String) && column.sql_type =~ /^bit/
|
328
|
-
case value
|
329
|
-
when /^[01]*$/
|
330
|
-
"B'#{value}'" # Bit-string notation
|
331
|
-
when /^[0-9A-F]*$/i
|
332
|
-
"X'#{value}'" # Hexadecimal notation
|
333
|
-
end
|
334
|
-
else
|
335
|
-
super
|
336
|
-
end
|
453
|
+
def supports_index_sort_order?
|
454
|
+
true
|
337
455
|
end
|
338
456
|
|
339
|
-
|
340
|
-
|
341
|
-
@connection.escape(s)
|
457
|
+
def supports_partial_index?
|
458
|
+
true
|
342
459
|
end
|
343
460
|
|
344
|
-
|
345
|
-
|
346
|
-
# - table_name
|
347
|
-
# - "table.name"
|
348
|
-
# - schema_name.table_name
|
349
|
-
# - schema_name."table.name"
|
350
|
-
# - "schema.name".table_name
|
351
|
-
# - "schema.name"."table.name"
|
352
|
-
def quote_table_name(name)
|
353
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
354
|
-
|
355
|
-
unless name_part
|
356
|
-
quote_column_name(schema)
|
357
|
-
else
|
358
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
359
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
360
|
-
end
|
461
|
+
def supports_transaction_isolation?
|
462
|
+
true
|
361
463
|
end
|
362
464
|
|
363
|
-
|
364
|
-
|
365
|
-
PGconn.quote_ident(name.to_s)
|
465
|
+
def index_algorithms
|
466
|
+
{ concurrently: 'CONCURRENTLY' }
|
366
467
|
end
|
367
468
|
|
368
|
-
|
369
|
-
|
370
|
-
def quoted_date(value) #:nodoc:
|
371
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
372
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
373
|
-
else
|
469
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
470
|
+
def initialize(connection, max)
|
374
471
|
super
|
472
|
+
@counter = 0
|
473
|
+
@cache = Hash.new { |h,pid| h[pid] = {} }
|
375
474
|
end
|
376
|
-
end
|
377
475
|
|
378
|
-
|
476
|
+
def each(&block); cache.each(&block); end
|
477
|
+
def key?(key); cache.key?(key); end
|
478
|
+
def [](key); cache[key]; end
|
479
|
+
def length; cache.length; end
|
379
480
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
rescue
|
384
|
-
return false
|
385
|
-
end
|
481
|
+
def next_key
|
482
|
+
"a#{@counter + 1}"
|
483
|
+
end
|
386
484
|
|
387
|
-
|
388
|
-
|
389
|
-
|
485
|
+
def []=(sql, key)
|
486
|
+
while @max <= cache.size
|
487
|
+
dealloc(cache.shift.last)
|
488
|
+
end
|
489
|
+
@counter += 1
|
490
|
+
cache[sql] = key
|
390
491
|
end
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
492
|
+
|
493
|
+
def clear
|
494
|
+
cache.each_value do |stmt_key|
|
495
|
+
dealloc stmt_key
|
496
|
+
end
|
497
|
+
cache.clear
|
395
498
|
end
|
396
|
-
end
|
397
499
|
|
398
|
-
|
500
|
+
def delete(sql_key)
|
501
|
+
dealloc cache[sql_key]
|
502
|
+
cache.delete sql_key
|
503
|
+
end
|
399
504
|
|
400
|
-
|
401
|
-
# array of field values.
|
402
|
-
def select_rows(sql, name = nil)
|
403
|
-
select_raw(sql, name).last
|
404
|
-
end
|
505
|
+
private
|
405
506
|
|
406
|
-
|
407
|
-
|
408
|
-
# Extract the table from the insert sql. Yuck.
|
409
|
-
table = sql.split(" ", 4)[2].gsub('"', '')
|
410
|
-
|
411
|
-
# Try an insert with 'returning id' if available (PG >= 8.2)
|
412
|
-
if supports_insert_with_returning?
|
413
|
-
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
414
|
-
if pk
|
415
|
-
id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
416
|
-
clear_query_cache
|
417
|
-
return id
|
507
|
+
def cache
|
508
|
+
@cache[Process.pid]
|
418
509
|
end
|
419
|
-
end
|
420
510
|
|
421
|
-
|
422
|
-
|
423
|
-
insert_id
|
424
|
-
else
|
425
|
-
# If neither pk nor sequence name is given, look them up.
|
426
|
-
unless pk || sequence_name
|
427
|
-
pk, sequence_name = *pk_and_sequence_for(table)
|
511
|
+
def dealloc(key)
|
512
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
428
513
|
end
|
429
514
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
515
|
+
def connection_active?
|
516
|
+
@connection.status == PGconn::CONNECTION_OK
|
517
|
+
rescue PGError
|
518
|
+
false
|
434
519
|
end
|
435
|
-
end
|
436
520
|
end
|
437
|
-
alias :create :insert
|
438
|
-
|
439
|
-
# create a 2D array representing the result set
|
440
|
-
def result_as_array(res) #:nodoc:
|
441
|
-
# check if we have any binary column and if they need escaping
|
442
|
-
unescape_col = []
|
443
|
-
res.nfields.times do |j|
|
444
|
-
unescape_col << res.ftype(j)
|
445
|
-
end
|
446
521
|
|
447
|
-
|
448
|
-
|
449
|
-
ary << []
|
450
|
-
res.nfields.times do |j|
|
451
|
-
data = res.getvalue(i,j)
|
452
|
-
case unescape_col[j]
|
453
|
-
|
454
|
-
# unescape string passed BYTEA field (OID == 17)
|
455
|
-
when BYTEA_COLUMN_TYPE_OID
|
456
|
-
data = unescape_bytea(data) if String === data
|
457
|
-
|
458
|
-
# If this is a money type column and there are any currency symbols,
|
459
|
-
# then strip them off. Indeed it would be prettier to do this in
|
460
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
461
|
-
# fields that call value_before_type_cast.
|
462
|
-
when MONEY_COLUMN_TYPE_OID
|
463
|
-
# Because money output is formatted according to the locale, there are two
|
464
|
-
# cases to consider (note the decimal separators):
|
465
|
-
# (1) $12,345,678.12
|
466
|
-
# (2) $12.345.678,12
|
467
|
-
case data
|
468
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
469
|
-
data.gsub!(/[^-\d\.]/, '')
|
470
|
-
when /^-?\D+[\d\.]+,\d{2}$/ # (2)
|
471
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
472
|
-
end
|
473
|
-
end
|
474
|
-
ary[i] << data
|
475
|
-
end
|
476
|
-
end
|
477
|
-
return ary
|
522
|
+
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
|
523
|
+
include Arel::Visitors::BindVisitor
|
478
524
|
end
|
479
525
|
|
526
|
+
# Initializes and connects a PostgreSQL adapter.
|
527
|
+
def initialize(connection, logger, connection_parameters, config)
|
528
|
+
super(connection, logger)
|
480
529
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
res = @connection.async_exec(sql)
|
486
|
-
else
|
487
|
-
res = @connection.exec(sql)
|
488
|
-
end
|
489
|
-
return result_as_array(res)
|
530
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
531
|
+
@visitor = Arel::Visitors::PostgreSQL.new self
|
532
|
+
else
|
533
|
+
@visitor = unprepared_visitor
|
490
534
|
end
|
491
|
-
end
|
492
535
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
536
|
+
@connection_parameters, @config = connection_parameters, config
|
537
|
+
|
538
|
+
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
539
|
+
@local_tz = nil
|
540
|
+
@table_alias_length = nil
|
541
|
+
|
542
|
+
connect
|
543
|
+
@statements = StatementPool.new @connection,
|
544
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
|
545
|
+
|
546
|
+
if postgresql_version < 80200
|
547
|
+
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
502
548
|
end
|
503
|
-
end
|
504
549
|
|
505
|
-
|
506
|
-
|
507
|
-
|
550
|
+
initialize_type_map
|
551
|
+
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
552
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
508
553
|
end
|
509
554
|
|
510
|
-
#
|
511
|
-
def
|
512
|
-
|
555
|
+
# Clears the prepared statements cache.
|
556
|
+
def clear_cache!
|
557
|
+
@statements.clear
|
513
558
|
end
|
514
559
|
|
515
|
-
#
|
516
|
-
def
|
517
|
-
|
560
|
+
# Is this connection alive and ready for queries?
|
561
|
+
def active?
|
562
|
+
@connection.connect_poll != PG::PGRES_POLLING_FAILED
|
563
|
+
rescue PGError
|
564
|
+
false
|
518
565
|
end
|
519
566
|
|
520
|
-
#
|
521
|
-
def
|
522
|
-
|
567
|
+
# Close then reopen the connection.
|
568
|
+
def reconnect!
|
569
|
+
super
|
570
|
+
@connection.reset
|
571
|
+
configure_connection
|
523
572
|
end
|
524
573
|
|
525
|
-
def
|
526
|
-
|
574
|
+
def reset!
|
575
|
+
clear_cache!
|
576
|
+
super
|
527
577
|
end
|
528
578
|
|
529
|
-
|
530
|
-
|
579
|
+
# Disconnects from the database if already connected. Otherwise, this
|
580
|
+
# method does nothing.
|
581
|
+
def disconnect!
|
582
|
+
super
|
583
|
+
@connection.close rescue nil
|
531
584
|
end
|
532
585
|
|
533
|
-
def
|
534
|
-
|
586
|
+
def native_database_types #:nodoc:
|
587
|
+
NATIVE_DATABASE_TYPES
|
535
588
|
end
|
536
589
|
|
537
|
-
|
538
|
-
|
590
|
+
# Returns true, since this connection adapter supports migrations.
|
591
|
+
def supports_migrations?
|
592
|
+
true
|
539
593
|
end
|
540
594
|
|
541
|
-
#
|
542
|
-
|
543
|
-
|
544
|
-
drop_database(name)
|
545
|
-
create_database(name)
|
595
|
+
# Does PostgreSQL support finding primary key on non-Active Record tables?
|
596
|
+
def supports_primary_key? #:nodoc:
|
597
|
+
true
|
546
598
|
end
|
547
599
|
|
548
|
-
#
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
# create_database 'foo_development', :encoding => 'unicode'
|
555
|
-
def create_database(name, options = {})
|
556
|
-
options = options.reverse_merge(:encoding => "utf8")
|
557
|
-
|
558
|
-
option_string = options.symbolize_keys.sum do |key, value|
|
559
|
-
case key
|
560
|
-
when :owner
|
561
|
-
" OWNER = \"#{value}\""
|
562
|
-
when :template
|
563
|
-
" TEMPLATE = \"#{value}\""
|
564
|
-
when :encoding
|
565
|
-
" ENCODING = '#{value}'"
|
566
|
-
when :tablespace
|
567
|
-
" TABLESPACE = \"#{value}\""
|
568
|
-
when :connection_limit
|
569
|
-
" CONNECTION LIMIT = #{value}"
|
570
|
-
else
|
571
|
-
""
|
572
|
-
end
|
573
|
-
end
|
574
|
-
|
575
|
-
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
|
600
|
+
# Enable standard-conforming strings if available.
|
601
|
+
def set_standard_conforming_strings
|
602
|
+
old, self.client_min_messages = client_min_messages, 'panic'
|
603
|
+
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
604
|
+
ensure
|
605
|
+
self.client_min_messages = old
|
576
606
|
end
|
577
607
|
|
578
|
-
|
579
|
-
|
580
|
-
# Example:
|
581
|
-
# drop_database 'matt_development'
|
582
|
-
def drop_database(name) #:nodoc:
|
583
|
-
if postgresql_version >= 80200
|
584
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
585
|
-
else
|
586
|
-
begin
|
587
|
-
execute "DROP DATABASE #{quote_table_name(name)}"
|
588
|
-
rescue ActiveRecord::StatementInvalid
|
589
|
-
@logger.warn "#{name} database doesn't exist." if @logger
|
590
|
-
end
|
591
|
-
end
|
608
|
+
def supports_insert_with_returning?
|
609
|
+
true
|
592
610
|
end
|
593
611
|
|
594
|
-
|
595
|
-
|
596
|
-
query(<<-SQL, name).map { |row| row[0] }
|
597
|
-
SELECT tablename
|
598
|
-
FROM pg_tables
|
599
|
-
WHERE schemaname = ANY (current_schemas(false))
|
600
|
-
SQL
|
612
|
+
def supports_ddl_transactions?
|
613
|
+
true
|
601
614
|
end
|
602
615
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
unless table # A table was provided without a schema
|
608
|
-
table = schema
|
609
|
-
schema = nil
|
610
|
-
end
|
611
|
-
|
612
|
-
if name =~ /^"/ # Handle quoted table names
|
613
|
-
table = name
|
614
|
-
schema = nil
|
615
|
-
end
|
616
|
-
|
617
|
-
query(<<-SQL).first[0].to_i > 0
|
618
|
-
SELECT COUNT(*)
|
619
|
-
FROM pg_tables
|
620
|
-
WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
|
621
|
-
#{schema ? "AND schemaname = '#{schema}'" : ''}
|
622
|
-
SQL
|
616
|
+
# Returns true, since this connection adapter supports savepoints.
|
617
|
+
def supports_savepoints?
|
618
|
+
true
|
623
619
|
end
|
624
620
|
|
625
|
-
# Returns
|
626
|
-
def
|
627
|
-
|
628
|
-
result = query(<<-SQL, name)
|
629
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
|
630
|
-
FROM pg_class t, pg_class i, pg_index d
|
631
|
-
WHERE i.relkind = 'i'
|
632
|
-
AND d.indexrelid = i.oid
|
633
|
-
AND d.indisprimary = 'f'
|
634
|
-
AND t.oid = d.indrelid
|
635
|
-
AND t.relname = '#{table_name}'
|
636
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
|
637
|
-
ORDER BY i.relname
|
638
|
-
SQL
|
639
|
-
|
640
|
-
|
641
|
-
result.map do |row|
|
642
|
-
index_name = row[0]
|
643
|
-
unique = row[1] == 't'
|
644
|
-
indkey = row[2].split(" ")
|
645
|
-
oid = row[3]
|
646
|
-
|
647
|
-
columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
|
648
|
-
SELECT a.attnum, a.attname
|
649
|
-
FROM pg_attribute a
|
650
|
-
WHERE a.attrelid = #{oid}
|
651
|
-
AND a.attnum IN (#{indkey.join(",")})
|
652
|
-
SQL
|
653
|
-
|
654
|
-
column_names = columns.values_at(*indkey).compact
|
655
|
-
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
|
656
|
-
end.compact
|
621
|
+
# Returns true.
|
622
|
+
def supports_explain?
|
623
|
+
true
|
657
624
|
end
|
658
625
|
|
659
|
-
# Returns
|
660
|
-
def
|
661
|
-
|
662
|
-
column_definitions(table_name).collect do |name, type, default, notnull|
|
663
|
-
PostgreSQLColumn.new(name, default, type, notnull == 'f')
|
664
|
-
end
|
626
|
+
# Returns true if pg > 9.2
|
627
|
+
def supports_extensions?
|
628
|
+
postgresql_version >= 90200
|
665
629
|
end
|
666
630
|
|
667
|
-
#
|
668
|
-
def
|
669
|
-
|
631
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
632
|
+
def supports_ranges?
|
633
|
+
postgresql_version >= 90200
|
670
634
|
end
|
671
635
|
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
677
|
-
end_sql
|
636
|
+
def enable_extension(name)
|
637
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
638
|
+
reload_type_map
|
639
|
+
}
|
678
640
|
end
|
679
641
|
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
# This should be not be called manually but set in database.yml.
|
685
|
-
def schema_search_path=(schema_csv)
|
686
|
-
if schema_csv
|
687
|
-
execute "SET search_path TO #{schema_csv}"
|
688
|
-
@schema_search_path = schema_csv
|
689
|
-
end
|
642
|
+
def disable_extension(name)
|
643
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
644
|
+
reload_type_map
|
645
|
+
}
|
690
646
|
end
|
691
647
|
|
692
|
-
|
693
|
-
|
694
|
-
|
648
|
+
def extension_enabled?(name)
|
649
|
+
if supports_extensions?
|
650
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
|
651
|
+
'SCHEMA'
|
652
|
+
res.column_types['exists'].type_cast res.rows.first.first
|
653
|
+
end
|
695
654
|
end
|
696
655
|
|
697
|
-
|
698
|
-
|
699
|
-
|
656
|
+
def extensions
|
657
|
+
if supports_extensions?
|
658
|
+
res = exec_query "SELECT extname from pg_extension", "SCHEMA"
|
659
|
+
res.rows.map { |r| res.column_types['extname'].type_cast r.first }
|
660
|
+
else
|
661
|
+
super
|
662
|
+
end
|
700
663
|
end
|
701
664
|
|
702
|
-
#
|
703
|
-
def
|
704
|
-
|
665
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
666
|
+
def table_alias_length
|
667
|
+
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
|
705
668
|
end
|
706
669
|
|
707
|
-
#
|
708
|
-
def
|
709
|
-
|
710
|
-
|
670
|
+
# Set the authorized user for this session
|
671
|
+
def session_auth=(user)
|
672
|
+
clear_cache!
|
673
|
+
exec_query "SET SESSION AUTHORIZATION #{user}"
|
711
674
|
end
|
712
675
|
|
713
|
-
|
714
|
-
|
715
|
-
unless pk and sequence
|
716
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
717
|
-
pk ||= default_pk
|
718
|
-
sequence ||= default_sequence
|
719
|
-
end
|
720
|
-
if pk
|
721
|
-
if sequence
|
722
|
-
quoted_sequence = quote_column_name(sequence)
|
676
|
+
module Utils
|
677
|
+
extend self
|
723
678
|
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
738
|
-
SELECT attr.attname, seq.relname
|
739
|
-
FROM pg_class seq,
|
740
|
-
pg_attribute attr,
|
741
|
-
pg_depend dep,
|
742
|
-
pg_namespace name,
|
743
|
-
pg_constraint cons
|
744
|
-
WHERE seq.oid = dep.objid
|
745
|
-
AND seq.relkind = 'S'
|
746
|
-
AND attr.attrelid = dep.refobjid
|
747
|
-
AND attr.attnum = dep.refobjsubid
|
748
|
-
AND attr.attrelid = cons.conrelid
|
749
|
-
AND attr.attnum = cons.conkey[1]
|
750
|
-
AND cons.contype = 'p'
|
751
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
752
|
-
end_sql
|
753
|
-
|
754
|
-
if result.nil? or result.empty?
|
755
|
-
# If that fails, try parsing the primary key's default value.
|
756
|
-
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
757
|
-
# the 8.1+ nextval('foo'::regclass).
|
758
|
-
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
759
|
-
SELECT attr.attname,
|
760
|
-
CASE
|
761
|
-
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
762
|
-
substr(split_part(def.adsrc, '''', 2),
|
763
|
-
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
764
|
-
ELSE split_part(def.adsrc, '''', 2)
|
765
|
-
END
|
766
|
-
FROM pg_class t
|
767
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
768
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
769
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
770
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
771
|
-
AND cons.contype = 'p'
|
772
|
-
AND def.adsrc ~* 'nextval'
|
773
|
-
end_sql
|
679
|
+
# Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
|
680
|
+
# +schema_name+ is nil if not specified in +name+.
|
681
|
+
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
|
682
|
+
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
|
683
|
+
#
|
684
|
+
# * <tt>table_name</tt>
|
685
|
+
# * <tt>"table.name"</tt>
|
686
|
+
# * <tt>schema_name.table_name</tt>
|
687
|
+
# * <tt>schema_name."table.name"</tt>
|
688
|
+
# * <tt>"schema.name"."table name"</tt>
|
689
|
+
def extract_schema_and_table(name)
|
690
|
+
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
691
|
+
[schema, table]
|
774
692
|
end
|
775
|
-
|
776
|
-
# [primary_key, sequence]
|
777
|
-
[result.first, result.last]
|
778
|
-
rescue
|
779
|
-
nil
|
780
693
|
end
|
781
694
|
|
782
|
-
|
783
|
-
|
784
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
785
|
-
pk_and_sequence && pk_and_sequence.first
|
695
|
+
def use_insert_returning?
|
696
|
+
@use_insert_returning
|
786
697
|
end
|
787
698
|
|
788
|
-
|
789
|
-
|
790
|
-
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
699
|
+
def valid_type?(type)
|
700
|
+
!native_database_types[type].nil?
|
791
701
|
end
|
792
702
|
|
793
|
-
|
794
|
-
# See TableDefinition#column for details of the options you can use.
|
795
|
-
def add_column(table_name, column_name, type, options = {})
|
796
|
-
default = options[:default]
|
797
|
-
notnull = options[:null] == false
|
703
|
+
protected
|
798
704
|
|
799
|
-
#
|
800
|
-
|
705
|
+
# Returns the version of the connected PostgreSQL server.
|
706
|
+
def postgresql_version
|
707
|
+
@connection.server_version
|
708
|
+
end
|
801
709
|
|
802
|
-
|
803
|
-
|
804
|
-
|
710
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
711
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
712
|
+
UNIQUE_VIOLATION = "23505"
|
805
713
|
|
806
|
-
|
807
|
-
|
808
|
-
quoted_table_name = quote_table_name(table_name)
|
714
|
+
def translate_exception(exception, message)
|
715
|
+
return exception unless exception.respond_to?(:result)
|
809
716
|
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
tmp_column_name = "#{column_name}_ar_tmp"
|
818
|
-
add_column(table_name, tmp_column_name, type, options)
|
819
|
-
execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
820
|
-
remove_column(table_name, column_name)
|
821
|
-
rename_column(table_name, tmp_column_name, column_name)
|
822
|
-
commit_db_transaction
|
823
|
-
rescue
|
824
|
-
rollback_db_transaction
|
717
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
718
|
+
when UNIQUE_VIOLATION
|
719
|
+
RecordNotUnique.new(message, exception)
|
720
|
+
when FOREIGN_KEY_VIOLATION
|
721
|
+
InvalidForeignKey.new(message, exception)
|
722
|
+
else
|
723
|
+
super
|
825
724
|
end
|
826
725
|
end
|
827
726
|
|
828
|
-
|
829
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
830
|
-
end
|
831
|
-
|
832
|
-
# Changes the default value of a table column.
|
833
|
-
def change_column_default(table_name, column_name, default)
|
834
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
835
|
-
end
|
727
|
+
private
|
836
728
|
|
837
|
-
|
838
|
-
|
839
|
-
|
729
|
+
def reload_type_map
|
730
|
+
OID::TYPE_MAP.clear
|
731
|
+
initialize_type_map
|
840
732
|
end
|
841
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
842
|
-
end
|
843
733
|
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
end
|
734
|
+
def initialize_type_map
|
735
|
+
result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
|
736
|
+
leaves, nodes = result.partition { |row| row['typelem'] == '0' }
|
848
737
|
|
849
|
-
|
850
|
-
|
851
|
-
|
738
|
+
# populate the leaf nodes
|
739
|
+
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
|
740
|
+
OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
|
741
|
+
end
|
852
742
|
|
853
|
-
|
854
|
-
63
|
855
|
-
end
|
743
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
|
856
744
|
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
745
|
+
# populate composite types
|
746
|
+
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
747
|
+
if OID.registered_type? row['typname']
|
748
|
+
# this composite type is explicitly registered
|
749
|
+
vector = OID::NAMES[row['typname']]
|
750
|
+
else
|
751
|
+
# use the default for composite types
|
752
|
+
vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
|
753
|
+
end
|
754
|
+
|
755
|
+
OID::TYPE_MAP[row['oid'].to_i] = vector
|
756
|
+
end
|
861
757
|
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
758
|
+
# populate array types
|
759
|
+
arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
|
760
|
+
array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
|
761
|
+
OID::TYPE_MAP[row['oid'].to_i] = array
|
762
|
+
end
|
867
763
|
end
|
868
|
-
end
|
869
764
|
|
870
|
-
|
871
|
-
#
|
872
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
873
|
-
# requires that the ORDER BY include the distinct column.
|
874
|
-
#
|
875
|
-
# distinct("posts.id", "posts.created_at desc")
|
876
|
-
def distinct(columns, order_by) #:nodoc:
|
877
|
-
return "DISTINCT #{columns}" if order_by.blank?
|
878
|
-
|
879
|
-
# Construct a clean list of column names from the ORDER BY clause, removing
|
880
|
-
# any ASC/DESC modifiers
|
881
|
-
order_columns = order_by.split(',').collect { |s| s.split.first }
|
882
|
-
order_columns.delete_if { |c| c.blank? }
|
883
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
884
|
-
|
885
|
-
# Return a DISTINCT ON() clause that's distinct on the columns we want but includes
|
886
|
-
# all the required columns for the ORDER BY to work properly.
|
887
|
-
sql = "DISTINCT ON (#{columns}) #{columns}, "
|
888
|
-
sql << order_columns * ', '
|
889
|
-
end
|
765
|
+
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
890
766
|
|
891
|
-
|
892
|
-
|
893
|
-
def postgresql_version
|
894
|
-
@postgresql_version ||=
|
895
|
-
if @connection.respond_to?(:server_version)
|
896
|
-
@connection.server_version
|
897
|
-
else
|
898
|
-
# Mimic PGconn.server_version behavior
|
899
|
-
begin
|
900
|
-
query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
|
901
|
-
($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
|
902
|
-
rescue
|
903
|
-
0
|
904
|
-
end
|
905
|
-
end
|
767
|
+
def exec_no_cache(sql, binds)
|
768
|
+
@connection.async_exec(sql)
|
906
769
|
end
|
907
770
|
|
908
|
-
def
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
771
|
+
def exec_cache(sql, binds)
|
772
|
+
stmt_key = prepare_statement sql
|
773
|
+
|
774
|
+
# Clear the queue
|
775
|
+
@connection.get_last_result
|
776
|
+
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
777
|
+
type_cast(val, col)
|
778
|
+
})
|
779
|
+
@connection.block
|
780
|
+
@connection.get_last_result
|
781
|
+
rescue PGError => e
|
782
|
+
# Get the PG code for the failure. Annoyingly, the code for
|
783
|
+
# prepared statements whose return value may have changed is
|
784
|
+
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
785
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
786
|
+
begin
|
787
|
+
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
788
|
+
rescue
|
789
|
+
raise e
|
790
|
+
end
|
791
|
+
if FEATURE_NOT_SUPPORTED == code
|
792
|
+
@statements.delete sql_key(sql)
|
793
|
+
retry
|
914
794
|
else
|
915
|
-
|
795
|
+
raise e
|
916
796
|
end
|
917
797
|
end
|
918
798
|
|
919
|
-
|
799
|
+
# Returns the statement identifier for the client side cache
|
800
|
+
# of statements
|
801
|
+
def sql_key(sql)
|
802
|
+
"#{schema_search_path}-#{sql}"
|
803
|
+
end
|
804
|
+
|
805
|
+
# Prepare the statement if it hasn't been prepared, return
|
806
|
+
# the statement key.
|
807
|
+
def prepare_statement(sql)
|
808
|
+
sql_key = sql_key(sql)
|
809
|
+
unless @statements.key? sql_key
|
810
|
+
nextkey = @statements.next_key
|
811
|
+
@connection.prepare nextkey, sql
|
812
|
+
@statements[sql_key] = nextkey
|
813
|
+
end
|
814
|
+
@statements[sql_key]
|
815
|
+
end
|
816
|
+
|
920
817
|
# The internal PostgreSQL identifier of the money data type.
|
921
818
|
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
922
819
|
# The internal PostgreSQL identifier of the BYTEA data type.
|
@@ -925,11 +822,7 @@ module ActiveRecord
|
|
925
822
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
926
823
|
# connected server's characteristics.
|
927
824
|
def connect
|
928
|
-
@connection = PGconn.connect(
|
929
|
-
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
930
|
-
|
931
|
-
# Ignore async_exec and async_query when using postgres-pr.
|
932
|
-
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
825
|
+
@connection = PGconn.connect(@connection_parameters)
|
933
826
|
|
934
827
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
935
828
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
@@ -943,13 +836,9 @@ module ActiveRecord
|
|
943
836
|
# This is called by #connect and should not be called manually.
|
944
837
|
def configure_connection
|
945
838
|
if @config[:encoding]
|
946
|
-
|
947
|
-
@connection.set_client_encoding(@config[:encoding])
|
948
|
-
else
|
949
|
-
execute("SET client_encoding TO '#{@config[:encoding]}'")
|
950
|
-
end
|
839
|
+
@connection.set_client_encoding(@config[:encoding])
|
951
840
|
end
|
952
|
-
self.client_min_messages = @config[:min_messages]
|
841
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
953
842
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
954
843
|
|
955
844
|
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
|
@@ -957,25 +846,43 @@ module ActiveRecord
|
|
957
846
|
|
958
847
|
# If using Active Record's time zone support configure the connection to return
|
959
848
|
# TIMESTAMP WITH ZONE types in UTC.
|
849
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
960
850
|
if ActiveRecord::Base.default_timezone == :utc
|
961
|
-
execute("SET time zone 'UTC'")
|
851
|
+
execute("SET time zone 'UTC'", 'SCHEMA')
|
962
852
|
elsif @local_tz
|
963
|
-
execute("SET time zone '#{@local_tz}'")
|
853
|
+
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
854
|
+
end
|
855
|
+
|
856
|
+
# SET statements from :variables config hash
|
857
|
+
# http://www.postgresql.org/docs/8.3/static/sql-set.html
|
858
|
+
variables = @config[:variables] || {}
|
859
|
+
variables.map do |k, v|
|
860
|
+
if v == ':default' || v == :default
|
861
|
+
# Sets the value to the global or compile default
|
862
|
+
execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
|
863
|
+
elsif !v.nil?
|
864
|
+
execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
|
865
|
+
end
|
964
866
|
end
|
965
867
|
end
|
966
868
|
|
967
869
|
# Returns the current ID of a table's sequence.
|
968
|
-
def last_insert_id(
|
969
|
-
Integer(
|
870
|
+
def last_insert_id(sequence_name) #:nodoc:
|
871
|
+
Integer(last_insert_id_value(sequence_name))
|
872
|
+
end
|
873
|
+
|
874
|
+
def last_insert_id_value(sequence_name)
|
875
|
+
last_insert_id_result(sequence_name).rows.first.first
|
876
|
+
end
|
877
|
+
|
878
|
+
def last_insert_id_result(sequence_name) #:nodoc:
|
879
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
970
880
|
end
|
971
881
|
|
972
882
|
# Executes a SELECT query and returns the results, performing any data type
|
973
883
|
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
974
|
-
def select(sql, name = nil)
|
975
|
-
|
976
|
-
rows.map do |row|
|
977
|
-
Hash[*fields.zip(row).flatten]
|
978
|
-
end
|
884
|
+
def select(sql, name = nil, binds = [])
|
885
|
+
exec_query(sql, name, binds)
|
979
886
|
end
|
980
887
|
|
981
888
|
def select_raw(sql, name = nil)
|
@@ -1005,26 +912,39 @@ module ActiveRecord
|
|
1005
912
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1006
913
|
# - ::regclass is a function that gives the id for a table name
|
1007
914
|
def column_definitions(table_name) #:nodoc:
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
915
|
+
exec_query(<<-end_sql, 'SCHEMA').rows
|
916
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
917
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
918
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
919
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
920
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
921
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
922
|
+
ORDER BY a.attnum
|
1015
923
|
end_sql
|
1016
924
|
end
|
1017
925
|
|
1018
926
|
def extract_pg_identifier_from_name(name)
|
1019
|
-
match_data = name
|
927
|
+
match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
|
1020
928
|
|
1021
929
|
if match_data
|
1022
|
-
rest = name[match_data[0].length
|
1023
|
-
rest = rest[1
|
930
|
+
rest = name[match_data[0].length, name.length]
|
931
|
+
rest = rest[1, rest.length] if rest.start_with? "."
|
1024
932
|
[match_data[1], (rest.length > 0 ? rest : nil)]
|
1025
933
|
end
|
1026
934
|
end
|
935
|
+
|
936
|
+
def extract_table_ref_from_insert_sql(sql)
|
937
|
+
sql[/into\s+([^\(]*).*values\s*\(/i]
|
938
|
+
$1.strip if $1
|
939
|
+
end
|
940
|
+
|
941
|
+
def create_table_definition(name, temporary, options)
|
942
|
+
TableDefinition.new native_database_types, name, temporary, options
|
943
|
+
end
|
944
|
+
|
945
|
+
def update_table_definition(table_name, base)
|
946
|
+
Table.new(table_name, base)
|
947
|
+
end
|
1027
948
|
end
|
1028
949
|
end
|
1029
950
|
end
|
1030
|
-
|