activerecord 3.2.22.5 → 4.2.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- 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/active_record/test_case.rb +0 -73
- 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,242 +1,142 @@
|
|
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
|
+
|
4
|
+
require 'active_record/connection_adapters/postgresql/utils'
|
5
|
+
require 'active_record/connection_adapters/postgresql/column'
|
6
|
+
require 'active_record/connection_adapters/postgresql/oid'
|
7
|
+
require 'active_record/connection_adapters/postgresql/quoting'
|
8
|
+
require 'active_record/connection_adapters/postgresql/referential_integrity'
|
9
|
+
require 'active_record/connection_adapters/postgresql/schema_definitions'
|
10
|
+
require 'active_record/connection_adapters/postgresql/schema_statements'
|
11
|
+
require 'active_record/connection_adapters/postgresql/database_statements'
|
12
|
+
|
4
13
|
require 'arel/visitors/bind_visitor'
|
5
14
|
|
6
15
|
# Make sure we're using pg high enough for PGResult#values
|
7
|
-
gem 'pg', '~> 0.
|
16
|
+
gem 'pg', '~> 0.15'
|
8
17
|
require 'pg'
|
9
18
|
|
19
|
+
require 'ipaddr'
|
20
|
+
|
10
21
|
module ActiveRecord
|
11
|
-
|
22
|
+
module ConnectionHandling # :nodoc:
|
23
|
+
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
|
24
|
+
:client_encoding, :options, :application_name, :fallback_application_name,
|
25
|
+
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
|
26
|
+
:tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
|
27
|
+
:sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
|
28
|
+
|
12
29
|
# 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
|
30
|
+
def postgresql_connection(config)
|
31
|
+
conn_params = config.symbolize_keys
|
32
|
+
|
33
|
+
conn_params.delete_if { |_, v| v.nil? }
|
34
|
+
|
35
|
+
# Map ActiveRecords param names to PGs.
|
36
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
37
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
38
|
+
|
39
|
+
# Forward only valid config params to PGconn.connect.
|
40
|
+
conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
|
25
41
|
|
26
42
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
27
43
|
# so just pass a nil connection object for the time being.
|
28
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger,
|
44
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
29
45
|
end
|
30
46
|
end
|
31
47
|
|
32
48
|
module ConnectionAdapters
|
33
|
-
# PostgreSQL
|
34
|
-
class PostgreSQLColumn < Column #:nodoc:
|
35
|
-
# Instantiates a new PostgreSQL column definition in a table.
|
36
|
-
def initialize(name, default, sql_type = nil, null = true)
|
37
|
-
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
38
|
-
end
|
39
|
-
|
40
|
-
# :stopdoc:
|
41
|
-
class << self
|
42
|
-
attr_accessor :money_precision
|
43
|
-
def string_to_time(string)
|
44
|
-
return string unless String === string
|
45
|
-
|
46
|
-
case string
|
47
|
-
when 'infinity' then 1.0 / 0.0
|
48
|
-
when '-infinity' then -1.0 / 0.0
|
49
|
-
else
|
50
|
-
super
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
# :startdoc:
|
55
|
-
|
56
|
-
private
|
57
|
-
def extract_limit(sql_type)
|
58
|
-
case sql_type
|
59
|
-
when /^bigint/i; 8
|
60
|
-
when /^smallint/i; 2
|
61
|
-
else super
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Extracts the scale from PostgreSQL-specific data types.
|
66
|
-
def extract_scale(sql_type)
|
67
|
-
# Money type has a fixed scale of 2.
|
68
|
-
sql_type =~ /^money/ ? 2 : super
|
69
|
-
end
|
70
|
-
|
71
|
-
# Extracts the precision from PostgreSQL-specific data types.
|
72
|
-
def extract_precision(sql_type)
|
73
|
-
if sql_type == 'money'
|
74
|
-
self.class.money_precision
|
75
|
-
else
|
76
|
-
super
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Maps PostgreSQL-specific data types to logical Rails types.
|
81
|
-
def simplified_type(field_type)
|
82
|
-
case field_type
|
83
|
-
# Numeric and monetary types
|
84
|
-
when /^(?:real|double precision)$/
|
85
|
-
:float
|
86
|
-
# Monetary types
|
87
|
-
when 'money'
|
88
|
-
:decimal
|
89
|
-
# Character types
|
90
|
-
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
91
|
-
:string
|
92
|
-
# Binary data types
|
93
|
-
when 'bytea'
|
94
|
-
:binary
|
95
|
-
# Date/time types
|
96
|
-
when /^timestamp with(?:out)? time zone$/
|
97
|
-
:datetime
|
98
|
-
when 'interval'
|
99
|
-
:string
|
100
|
-
# Geometric types
|
101
|
-
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
102
|
-
:string
|
103
|
-
# Network address types
|
104
|
-
when /^(?:cidr|inet|macaddr)$/
|
105
|
-
:string
|
106
|
-
# Bit strings
|
107
|
-
when /^bit(?: varying)?(?:\(\d+\))?$/
|
108
|
-
:string
|
109
|
-
# XML type
|
110
|
-
when 'xml'
|
111
|
-
:xml
|
112
|
-
# tsvector type
|
113
|
-
when 'tsvector'
|
114
|
-
:tsvector
|
115
|
-
# Arrays
|
116
|
-
when /^\D+\[\]$/
|
117
|
-
:string
|
118
|
-
# Object identifier types
|
119
|
-
when 'oid'
|
120
|
-
:integer
|
121
|
-
# UUID type
|
122
|
-
when 'uuid'
|
123
|
-
:string
|
124
|
-
# Small and big integer types
|
125
|
-
when /^(?:small|big)int$/
|
126
|
-
:integer
|
127
|
-
# Pass through all types that are not specific to PostgreSQL.
|
128
|
-
else
|
129
|
-
super
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# Extracts the value from a PostgreSQL column default definition.
|
134
|
-
def self.extract_value_from_default(default)
|
135
|
-
case default
|
136
|
-
# This is a performance optimization for Ruby 1.9.2 in development.
|
137
|
-
# If the value is nil, we return nil straight away without checking
|
138
|
-
# the regular expressions. If we check each regular expression,
|
139
|
-
# Regexp#=== will call NilClass#to_str, which will trigger
|
140
|
-
# method_missing (defined by whiny nil in ActiveSupport) which
|
141
|
-
# makes this method very very slow.
|
142
|
-
when NilClass
|
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
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
190
|
-
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
49
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
191
50
|
#
|
192
51
|
# Options:
|
193
52
|
#
|
194
|
-
# * <tt>:host</tt> - Defaults to
|
53
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
54
|
+
# the default is to connect to localhost.
|
195
55
|
# * <tt>:port</tt> - Defaults to 5432.
|
196
|
-
# * <tt>:username</tt> - Defaults to
|
197
|
-
# * <tt>:password</tt> -
|
198
|
-
# * <tt>:database</tt> -
|
56
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
57
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
58
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
199
59
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
60
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
61
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
62
|
# <encoding></tt> call on the connection.
|
203
63
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
64
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
65
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
66
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
67
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
68
|
+
# defaults to true.
|
69
|
+
#
|
70
|
+
# Any further options are used as connection parameters to libpq. See
|
71
|
+
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
|
72
|
+
# list of parameters.
|
73
|
+
#
|
74
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
75
|
+
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
|
205
76
|
class PostgreSQLAdapter < AbstractAdapter
|
206
|
-
|
207
|
-
def xml(*args)
|
208
|
-
options = args.extract_options!
|
209
|
-
column(args[0], 'xml', options)
|
210
|
-
end
|
211
|
-
|
212
|
-
def tsvector(*args)
|
213
|
-
options = args.extract_options!
|
214
|
-
column(args[0], 'tsvector', options)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
ADAPTER_NAME = 'PostgreSQL'
|
77
|
+
ADAPTER_NAME = 'PostgreSQL'.freeze
|
219
78
|
|
220
79
|
NATIVE_DATABASE_TYPES = {
|
221
|
-
:
|
222
|
-
:
|
223
|
-
:
|
224
|
-
:
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
80
|
+
primary_key: "serial primary key",
|
81
|
+
bigserial: "bigserial",
|
82
|
+
string: { name: "character varying" },
|
83
|
+
text: { name: "text" },
|
84
|
+
integer: { name: "integer" },
|
85
|
+
float: { name: "float" },
|
86
|
+
decimal: { name: "decimal" },
|
87
|
+
datetime: { name: "timestamp" },
|
88
|
+
time: { name: "time" },
|
89
|
+
date: { name: "date" },
|
90
|
+
daterange: { name: "daterange" },
|
91
|
+
numrange: { name: "numrange" },
|
92
|
+
tsrange: { name: "tsrange" },
|
93
|
+
tstzrange: { name: "tstzrange" },
|
94
|
+
int4range: { name: "int4range" },
|
95
|
+
int8range: { name: "int8range" },
|
96
|
+
binary: { name: "bytea" },
|
97
|
+
boolean: { name: "boolean" },
|
98
|
+
bigint: { name: "bigint" },
|
99
|
+
xml: { name: "xml" },
|
100
|
+
tsvector: { name: "tsvector" },
|
101
|
+
hstore: { name: "hstore" },
|
102
|
+
inet: { name: "inet" },
|
103
|
+
cidr: { name: "cidr" },
|
104
|
+
macaddr: { name: "macaddr" },
|
105
|
+
uuid: { name: "uuid" },
|
106
|
+
json: { name: "json" },
|
107
|
+
jsonb: { name: "jsonb" },
|
108
|
+
ltree: { name: "ltree" },
|
109
|
+
citext: { name: "citext" },
|
110
|
+
point: { name: "point" },
|
111
|
+
bit: { name: "bit" },
|
112
|
+
bit_varying: { name: "bit varying" },
|
113
|
+
money: { name: "money" },
|
235
114
|
}
|
236
115
|
|
237
|
-
|
238
|
-
|
239
|
-
|
116
|
+
OID = PostgreSQL::OID #:nodoc:
|
117
|
+
|
118
|
+
include PostgreSQL::Quoting
|
119
|
+
include PostgreSQL::ReferentialIntegrity
|
120
|
+
include PostgreSQL::SchemaStatements
|
121
|
+
include PostgreSQL::DatabaseStatements
|
122
|
+
include Savepoints
|
123
|
+
|
124
|
+
def schema_creation # :nodoc:
|
125
|
+
PostgreSQL::SchemaCreation.new self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Adds +:array+ option to the default set provided by the
|
129
|
+
# AbstractAdapter
|
130
|
+
def prepare_column_options(column, types) # :nodoc:
|
131
|
+
spec = super
|
132
|
+
spec[:array] = 'true' if column.respond_to?(:array) && column.array
|
133
|
+
spec[:default] = "\"#{column.default_function}\"" if column.default_function
|
134
|
+
spec
|
135
|
+
end
|
136
|
+
|
137
|
+
# Adds +:array+ as a valid migration key
|
138
|
+
def migration_keys
|
139
|
+
super + [:array]
|
240
140
|
end
|
241
141
|
|
242
142
|
# Returns +true+, since this connection adapter supports prepared statement
|
@@ -249,6 +149,26 @@ module ActiveRecord
|
|
249
149
|
true
|
250
150
|
end
|
251
151
|
|
152
|
+
def supports_partial_index?
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
def supports_transaction_isolation?
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
def supports_foreign_keys?
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
def supports_views?
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
def index_algorithms
|
169
|
+
{ concurrently: 'CONCURRENTLY' }
|
170
|
+
end
|
171
|
+
|
252
172
|
class StatementPool < ConnectionAdapters::StatementPool
|
253
173
|
def initialize(connection, max)
|
254
174
|
super
|
@@ -286,33 +206,31 @@ module ActiveRecord
|
|
286
206
|
end
|
287
207
|
|
288
208
|
private
|
289
|
-
def cache
|
290
|
-
@cache[$$]
|
291
|
-
end
|
292
209
|
|
293
|
-
|
294
|
-
|
295
|
-
|
210
|
+
def cache
|
211
|
+
@cache[Process.pid]
|
212
|
+
end
|
296
213
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
false
|
301
|
-
end
|
302
|
-
end
|
214
|
+
def dealloc(key)
|
215
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
216
|
+
end
|
303
217
|
|
304
|
-
|
305
|
-
|
218
|
+
def connection_active?
|
219
|
+
@connection.status == PGconn::CONNECTION_OK
|
220
|
+
rescue PGError
|
221
|
+
false
|
222
|
+
end
|
306
223
|
end
|
307
224
|
|
308
225
|
# Initializes and connects a PostgreSQL adapter.
|
309
226
|
def initialize(connection, logger, connection_parameters, config)
|
310
227
|
super(connection, logger)
|
311
228
|
|
312
|
-
|
313
|
-
|
229
|
+
@visitor = Arel::Visitors::PostgreSQL.new self
|
230
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
231
|
+
@prepared_statements = true
|
314
232
|
else
|
315
|
-
@
|
233
|
+
@prepared_statements = false
|
316
234
|
end
|
317
235
|
|
318
236
|
@connection_parameters, @config = connection_parameters, config
|
@@ -323,13 +241,16 @@ module ActiveRecord
|
|
323
241
|
|
324
242
|
connect
|
325
243
|
@statements = StatementPool.new @connection,
|
326
|
-
config.fetch(:statement_limit) { 1000 }
|
244
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
|
327
245
|
|
328
246
|
if postgresql_version < 80200
|
329
247
|
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
330
248
|
end
|
331
249
|
|
250
|
+
@type_map = Type::HashLookupTypeMap.new
|
251
|
+
initialize_type_map(type_map)
|
332
252
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
253
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
333
254
|
end
|
334
255
|
|
335
256
|
# Clears the prepared statements cache.
|
@@ -337,6 +258,10 @@ module ActiveRecord
|
|
337
258
|
@statements.clear
|
338
259
|
end
|
339
260
|
|
261
|
+
def truncate(table_name, name = nil)
|
262
|
+
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
263
|
+
end
|
264
|
+
|
340
265
|
# Is this connection alive and ready for queries?
|
341
266
|
def active?
|
342
267
|
@connection.query 'SELECT 1'
|
@@ -347,21 +272,25 @@ module ActiveRecord
|
|
347
272
|
|
348
273
|
# Close then reopen the connection.
|
349
274
|
def reconnect!
|
350
|
-
|
275
|
+
super
|
351
276
|
@connection.reset
|
352
|
-
@open_transactions = 0
|
353
277
|
configure_connection
|
354
278
|
end
|
355
279
|
|
356
280
|
def reset!
|
357
281
|
clear_cache!
|
358
|
-
|
282
|
+
reset_transaction
|
283
|
+
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
|
284
|
+
@connection.query 'ROLLBACK'
|
285
|
+
end
|
286
|
+
@connection.query 'DISCARD ALL'
|
287
|
+
configure_connection
|
359
288
|
end
|
360
289
|
|
361
290
|
# Disconnects from the database if already connected. Otherwise, this
|
362
291
|
# method does nothing.
|
363
292
|
def disconnect!
|
364
|
-
|
293
|
+
super
|
365
294
|
@connection.close rescue nil
|
366
295
|
end
|
367
296
|
|
@@ -379,817 +308,316 @@ module ActiveRecord
|
|
379
308
|
true
|
380
309
|
end
|
381
310
|
|
382
|
-
# Enable standard-conforming strings if available.
|
383
311
|
def set_standard_conforming_strings
|
384
|
-
|
385
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
386
|
-
ensure
|
387
|
-
self.client_min_messages = old
|
388
|
-
end
|
389
|
-
|
390
|
-
def supports_insert_with_returning?
|
391
|
-
true
|
312
|
+
execute('SET standard_conforming_strings = on', 'SCHEMA')
|
392
313
|
end
|
393
314
|
|
394
315
|
def supports_ddl_transactions?
|
395
316
|
true
|
396
317
|
end
|
397
318
|
|
398
|
-
# Returns true, since this connection adapter supports savepoints.
|
399
|
-
def supports_savepoints?
|
400
|
-
true
|
401
|
-
end
|
402
|
-
|
403
|
-
# Returns true.
|
404
319
|
def supports_explain?
|
405
320
|
true
|
406
321
|
end
|
407
322
|
|
408
|
-
# Returns
|
409
|
-
def
|
410
|
-
|
323
|
+
# Returns true if pg > 9.1
|
324
|
+
def supports_extensions?
|
325
|
+
postgresql_version >= 90100
|
411
326
|
end
|
412
327
|
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
def escape_bytea(value)
|
417
|
-
@connection.escape_bytea(value) if value
|
328
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
329
|
+
def supports_ranges?
|
330
|
+
postgresql_version >= 90200
|
418
331
|
end
|
419
332
|
|
420
|
-
|
421
|
-
|
422
|
-
# on escaped binary output from database drive.
|
423
|
-
def unescape_bytea(value)
|
424
|
-
@connection.unescape_bytea(value) if value
|
333
|
+
def supports_materialized_views?
|
334
|
+
postgresql_version >= 90300
|
425
335
|
end
|
426
336
|
|
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
|
454
|
-
end
|
455
|
-
|
456
|
-
def type_cast(value, column)
|
457
|
-
return super unless column
|
458
|
-
|
459
|
-
case value
|
460
|
-
when String
|
461
|
-
return super unless 'bytea' == column.sql_type
|
462
|
-
{ :value => value, :format => 1 }
|
463
|
-
else
|
464
|
-
super
|
465
|
-
end
|
337
|
+
def enable_extension(name)
|
338
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
339
|
+
reload_type_map
|
340
|
+
}
|
466
341
|
end
|
467
342
|
|
468
|
-
|
469
|
-
|
470
|
-
|
343
|
+
def disable_extension(name)
|
344
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
345
|
+
reload_type_map
|
346
|
+
}
|
471
347
|
end
|
472
348
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
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)
|
486
|
-
else
|
487
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
488
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
349
|
+
def extension_enabled?(name)
|
350
|
+
if supports_extensions?
|
351
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
|
352
|
+
'SCHEMA'
|
353
|
+
res.cast_values.first
|
489
354
|
end
|
490
355
|
end
|
491
356
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
end
|
496
|
-
|
497
|
-
# Quote date/time values for use in SQL input. Includes microseconds
|
498
|
-
# if the value is a Time responding to usec.
|
499
|
-
def quoted_date(value) #:nodoc:
|
500
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
501
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
357
|
+
def extensions
|
358
|
+
if supports_extensions?
|
359
|
+
exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
|
502
360
|
else
|
503
361
|
super
|
504
362
|
end
|
505
363
|
end
|
506
364
|
|
365
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
366
|
+
def table_alias_length
|
367
|
+
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
|
368
|
+
end
|
369
|
+
|
507
370
|
# Set the authorized user for this session
|
508
371
|
def session_auth=(user)
|
509
372
|
clear_cache!
|
510
373
|
exec_query "SET SESSION AUTHORIZATION #{user}"
|
511
374
|
end
|
512
375
|
|
513
|
-
|
514
|
-
|
515
|
-
def supports_disable_referential_integrity? #:nodoc:
|
516
|
-
true
|
376
|
+
def use_insert_returning?
|
377
|
+
@use_insert_returning
|
517
378
|
end
|
518
379
|
|
519
|
-
def
|
520
|
-
|
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
|
380
|
+
def valid_type?(type)
|
381
|
+
!native_database_types[type].nil?
|
528
382
|
end
|
529
383
|
|
530
|
-
|
531
|
-
|
532
|
-
def explain(arel, binds = [])
|
533
|
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
534
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
384
|
+
def update_table_definition(table_name, base) #:nodoc:
|
385
|
+
PostgreSQL::Table.new(table_name, base)
|
535
386
|
end
|
536
387
|
|
537
|
-
|
538
|
-
|
539
|
-
|
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
|
388
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
389
|
+
oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
|
390
|
+
super(oid)
|
572
391
|
end
|
573
392
|
|
574
|
-
|
575
|
-
|
576
|
-
def select_rows(sql, name = nil)
|
577
|
-
select_raw(sql, name).last
|
393
|
+
def column_name_for_operation(operation, node) # :nodoc:
|
394
|
+
OPERATION_ALIASES.fetch(operation) { operation.downcase }
|
578
395
|
end
|
579
396
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
pk = primary_key(table_ref) if table_ref
|
586
|
-
end
|
397
|
+
OPERATION_ALIASES = { # :nodoc:
|
398
|
+
"maximum" => "max",
|
399
|
+
"minimum" => "min",
|
400
|
+
"average" => "avg",
|
401
|
+
}
|
587
402
|
|
588
|
-
|
589
|
-
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
590
|
-
else
|
591
|
-
super
|
592
|
-
end
|
593
|
-
end
|
594
|
-
alias :create :insert
|
403
|
+
protected
|
595
404
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
ftypes = Array.new(res.nfields) do |i|
|
600
|
-
[i, res.ftype(i)]
|
405
|
+
# Returns the version of the connected PostgreSQL server.
|
406
|
+
def postgresql_version
|
407
|
+
@connection.server_version
|
601
408
|
end
|
602
409
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
}
|
607
|
-
|
608
|
-
typehash = ftypes.group_by { |_, type| type }
|
609
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
610
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
410
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
411
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
412
|
+
UNIQUE_VIOLATION = "23505"
|
611
413
|
|
612
|
-
|
613
|
-
|
614
|
-
binaries.each do |index, _|
|
615
|
-
row[index] = unescape_bytea(row[index])
|
616
|
-
end
|
414
|
+
def translate_exception(exception, message)
|
415
|
+
return exception unless exception.respond_to?(:result)
|
617
416
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
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
|
417
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
418
|
+
when UNIQUE_VIOLATION
|
419
|
+
RecordNotUnique.new(message, exception)
|
420
|
+
when FOREIGN_KEY_VIOLATION
|
421
|
+
InvalidForeignKey.new(message, exception)
|
422
|
+
else
|
423
|
+
super
|
634
424
|
end
|
635
425
|
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
426
|
|
654
|
-
|
655
|
-
Arel::Nodes::BindParam.new "$#{index + 1}"
|
656
|
-
end
|
427
|
+
private
|
657
428
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
429
|
+
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
|
430
|
+
if !type_map.key?(oid)
|
431
|
+
load_additional_types(type_map, [oid])
|
432
|
+
end
|
662
433
|
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
434
|
+
type_map.fetch(oid, fmod, sql_type) {
|
435
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
436
|
+
Type::Value.new.tap do |cast_type|
|
437
|
+
type_map.register_type(oid, cast_type)
|
438
|
+
end
|
439
|
+
}
|
440
|
+
end
|
441
|
+
|
442
|
+
def initialize_type_map(m) # :nodoc:
|
443
|
+
register_class_with_limit m, 'int2', OID::Integer
|
444
|
+
register_class_with_limit m, 'int4', OID::Integer
|
445
|
+
register_class_with_limit m, 'int8', OID::Integer
|
446
|
+
m.alias_type 'oid', 'int2'
|
447
|
+
m.register_type 'float4', OID::Float.new
|
448
|
+
m.alias_type 'float8', 'float4'
|
449
|
+
m.register_type 'text', Type::Text.new
|
450
|
+
register_class_with_limit m, 'varchar', Type::String
|
451
|
+
m.alias_type 'char', 'varchar'
|
452
|
+
m.alias_type 'name', 'varchar'
|
453
|
+
m.alias_type 'bpchar', 'varchar'
|
454
|
+
m.register_type 'bool', Type::Boolean.new
|
455
|
+
register_class_with_limit m, 'bit', OID::Bit
|
456
|
+
register_class_with_limit m, 'varbit', OID::BitVarying
|
457
|
+
m.alias_type 'timestamptz', 'timestamp'
|
458
|
+
m.register_type 'date', OID::Date.new
|
459
|
+
m.register_type 'time', OID::Time.new
|
460
|
+
|
461
|
+
m.register_type 'money', OID::Money.new
|
462
|
+
m.register_type 'bytea', OID::Bytea.new
|
463
|
+
m.register_type 'point', OID::Point.new
|
464
|
+
m.register_type 'hstore', OID::Hstore.new
|
465
|
+
m.register_type 'json', OID::Json.new
|
466
|
+
m.register_type 'jsonb', OID::Jsonb.new
|
467
|
+
m.register_type 'cidr', OID::Cidr.new
|
468
|
+
m.register_type 'inet', OID::Inet.new
|
469
|
+
m.register_type 'uuid', OID::Uuid.new
|
470
|
+
m.register_type 'xml', OID::Xml.new
|
471
|
+
m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
|
472
|
+
m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
|
473
|
+
m.register_type 'citext', OID::SpecializedString.new(:citext)
|
474
|
+
m.register_type 'ltree', OID::SpecializedString.new(:ltree)
|
475
|
+
|
476
|
+
# FIXME: why are we keeping these types as strings?
|
477
|
+
m.alias_type 'interval', 'varchar'
|
478
|
+
m.alias_type 'path', 'varchar'
|
479
|
+
m.alias_type 'line', 'varchar'
|
480
|
+
m.alias_type 'polygon', 'varchar'
|
481
|
+
m.alias_type 'circle', 'varchar'
|
482
|
+
m.alias_type 'lseg', 'varchar'
|
483
|
+
m.alias_type 'box', 'varchar'
|
484
|
+
|
485
|
+
m.register_type 'timestamp' do |_, _, sql_type|
|
486
|
+
precision = extract_precision(sql_type)
|
487
|
+
OID::DateTime.new(precision: precision)
|
488
|
+
end
|
668
489
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
490
|
+
m.register_type 'numeric' do |_, fmod, sql_type|
|
491
|
+
precision = extract_precision(sql_type)
|
492
|
+
scale = extract_scale(sql_type)
|
493
|
+
|
494
|
+
# The type for the numeric depends on the width of the field,
|
495
|
+
# so we'll do something special here.
|
496
|
+
#
|
497
|
+
# When dealing with decimal columns:
|
498
|
+
#
|
499
|
+
# places after decimal = fmod - 4 & 0xffff
|
500
|
+
# places before decimal = (fmod - 4) >> 16 & 0xffff
|
501
|
+
if fmod && (fmod - 4 & 0xffff).zero?
|
502
|
+
# FIXME: Remove this class, and the second argument to
|
503
|
+
# lookups on PG
|
504
|
+
Type::DecimalWithoutScale.new(precision: precision)
|
505
|
+
else
|
506
|
+
OID::Decimal.new(precision: precision, scale: scale)
|
507
|
+
end
|
508
|
+
end
|
679
509
|
|
680
|
-
|
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
|
510
|
+
load_additional_types(m)
|
685
511
|
end
|
686
512
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
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}"
|
513
|
+
def extract_limit(sql_type) # :nodoc:
|
514
|
+
case sql_type
|
515
|
+
when /^bigint/i, /^int8/i
|
516
|
+
8
|
517
|
+
when /^smallint/i
|
518
|
+
2
|
759
519
|
else
|
760
|
-
|
520
|
+
super
|
761
521
|
end
|
762
522
|
end
|
763
523
|
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
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
|
524
|
+
# Extracts the value from a PostgreSQL column default definition.
|
525
|
+
def extract_value_from_default(oid, default) # :nodoc:
|
526
|
+
case default
|
527
|
+
# Quoted types
|
528
|
+
when /\A[\(B]?'(.*)'::/m
|
529
|
+
$1.gsub(/''/, "'")
|
530
|
+
# Boolean types
|
531
|
+
when 'true', 'false'
|
532
|
+
default
|
533
|
+
# Numeric types
|
534
|
+
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
535
|
+
$1
|
536
|
+
# Object identifier types
|
537
|
+
when /\A-?\d+\z/
|
538
|
+
$1
|
539
|
+
else
|
540
|
+
# Anything else is blank, some user type, or some function
|
541
|
+
# and we can't know the value of that, so return nil.
|
542
|
+
nil
|
543
|
+
end
|
979
544
|
end
|
980
545
|
|
981
|
-
|
982
|
-
|
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)}"
|
546
|
+
def extract_default_function(default_value, default) # :nodoc:
|
547
|
+
default if has_default_function?(default_value, default)
|
1012
548
|
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
549
|
|
1022
|
-
|
1023
|
-
|
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")
|
550
|
+
def has_default_function?(default_value, default) # :nodoc:
|
551
|
+
!default_value && (%r{\w+\(.*\)} === default)
|
1046
552
|
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
553
|
|
1056
|
-
|
1057
|
-
|
1058
|
-
end
|
554
|
+
def load_additional_types(type_map, oids = nil) # :nodoc:
|
555
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
1059
556
|
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
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}.")
|
557
|
+
if supports_ranges?
|
558
|
+
query = <<-SQL
|
559
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
560
|
+
FROM pg_type as t
|
561
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
562
|
+
SQL
|
563
|
+
else
|
564
|
+
query = <<-SQL
|
565
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
|
566
|
+
FROM pg_type as t
|
567
|
+
SQL
|
1077
568
|
end
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
569
|
+
|
570
|
+
if oids
|
571
|
+
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
|
572
|
+
else
|
573
|
+
query += initializer.query_conditions_for_initial_load(type_map)
|
1084
574
|
end
|
1085
|
-
|
1086
|
-
|
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.")
|
575
|
+
|
576
|
+
execute_and_clear(query, 'SCHEMA', []) do |records|
|
577
|
+
initializer.run(records)
|
1093
578
|
end
|
1094
|
-
else
|
1095
|
-
super
|
1096
579
|
end
|
1097
|
-
end
|
1098
580
|
|
1099
|
-
|
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
|
581
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
1116
582
|
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
|
1124
|
-
#
|
1125
|
-
# * <tt>table_name</tt>
|
1126
|
-
# * <tt>"table.name"</tt>
|
1127
|
-
# * <tt>schema_name.table_name</tt>
|
1128
|
-
# * <tt>schema_name."table.name"</tt>
|
1129
|
-
# * <tt>"schema.name"."table name"</tt>
|
1130
|
-
def extract_schema_and_table(name)
|
1131
|
-
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
1132
|
-
[schema, table]
|
583
|
+
def execute_and_clear(sql, name, binds)
|
584
|
+
result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
|
585
|
+
exec_cache(sql, name, binds)
|
586
|
+
ret = yield result
|
587
|
+
result.clear
|
588
|
+
ret
|
1133
589
|
end
|
1134
|
-
end
|
1135
590
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
def postgresql_version
|
1139
|
-
@connection.server_version
|
591
|
+
def exec_no_cache(sql, name, binds)
|
592
|
+
log(sql, name, binds) { @connection.async_exec(sql, []) }
|
1140
593
|
end
|
1141
594
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
return exception unless exception.respond_to?(:result)
|
595
|
+
def exec_cache(sql, name, binds)
|
596
|
+
stmt_key = prepare_statement(sql)
|
597
|
+
type_casted_binds = binds.map { |col, val|
|
598
|
+
[col, type_cast(val, col)]
|
599
|
+
}
|
1148
600
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
RecordNotUnique.new(message, exception)
|
1152
|
-
when FOREIGN_KEY_VIOLATION
|
1153
|
-
InvalidForeignKey.new(message, exception)
|
1154
|
-
else
|
1155
|
-
super
|
601
|
+
log(sql, name, type_casted_binds, stmt_key) do
|
602
|
+
@connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
|
1156
603
|
end
|
1157
|
-
|
604
|
+
rescue ActiveRecord::StatementInvalid => e
|
605
|
+
pgerror = e.original_exception
|
1158
606
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
@connection.async_exec(sql, [])
|
1164
|
-
end
|
1165
|
-
|
1166
|
-
def exec_cache(sql, binds)
|
607
|
+
# Get the PG code for the failure. Annoyingly, the code for
|
608
|
+
# prepared statements whose return value may have changed is
|
609
|
+
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
610
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1167
611
|
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
|
612
|
+
code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
613
|
+
rescue
|
614
|
+
raise e
|
615
|
+
end
|
616
|
+
if FEATURE_NOT_SUPPORTED == code
|
617
|
+
@statements.delete sql_key(sql)
|
618
|
+
retry
|
619
|
+
else
|
620
|
+
raise e
|
1193
621
|
end
|
1194
622
|
end
|
1195
623
|
|
@@ -1205,28 +633,35 @@ module ActiveRecord
|
|
1205
633
|
sql_key = sql_key(sql)
|
1206
634
|
unless @statements.key? sql_key
|
1207
635
|
nextkey = @statements.next_key
|
1208
|
-
|
636
|
+
begin
|
637
|
+
@connection.prepare nextkey, sql
|
638
|
+
rescue => e
|
639
|
+
raise translate_exception_class(e, sql)
|
640
|
+
end
|
641
|
+
# Clear the queue
|
642
|
+
@connection.get_last_result
|
1209
643
|
@statements[sql_key] = nextkey
|
1210
644
|
end
|
1211
645
|
@statements[sql_key]
|
1212
646
|
end
|
1213
647
|
|
1214
|
-
# The internal PostgreSQL identifier of the money data type.
|
1215
|
-
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
1216
|
-
# The internal PostgreSQL identifier of the BYTEA data type.
|
1217
|
-
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
|
1218
|
-
|
1219
648
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1220
649
|
# connected server's characteristics.
|
1221
650
|
def connect
|
1222
|
-
@connection = PGconn.connect(
|
651
|
+
@connection = PGconn.connect(@connection_parameters)
|
1223
652
|
|
1224
653
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1225
654
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
1226
655
|
# should know about this but can't detect it there, so deal with it here.
|
1227
|
-
|
656
|
+
OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
|
1228
657
|
|
1229
658
|
configure_connection
|
659
|
+
rescue ::PG::Error => error
|
660
|
+
if error.message.include?("does not exist")
|
661
|
+
raise ActiveRecord::NoDatabaseError.new(error.message, error)
|
662
|
+
else
|
663
|
+
raise
|
664
|
+
end
|
1230
665
|
end
|
1231
666
|
|
1232
667
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -1235,39 +670,45 @@ module ActiveRecord
|
|
1235
670
|
if @config[:encoding]
|
1236
671
|
@connection.set_client_encoding(@config[:encoding])
|
1237
672
|
end
|
1238
|
-
self.client_min_messages = @config[:min_messages]
|
673
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
1239
674
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1240
675
|
|
1241
|
-
# Use standard-conforming strings
|
676
|
+
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
1242
677
|
set_standard_conforming_strings
|
1243
678
|
|
1244
679
|
# If using Active Record's time zone support configure the connection to return
|
1245
680
|
# TIMESTAMP WITH ZONE types in UTC.
|
681
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
1246
682
|
if ActiveRecord::Base.default_timezone == :utc
|
1247
683
|
execute("SET time zone 'UTC'", 'SCHEMA')
|
1248
684
|
elsif @local_tz
|
1249
685
|
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1250
686
|
end
|
687
|
+
|
688
|
+
# SET statements from :variables config hash
|
689
|
+
# http://www.postgresql.org/docs/8.3/static/sql-set.html
|
690
|
+
variables = @config[:variables] || {}
|
691
|
+
variables.map do |k, v|
|
692
|
+
if v == ':default' || v == :default
|
693
|
+
# Sets the value to the global or compile default
|
694
|
+
execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
|
695
|
+
elsif !v.nil?
|
696
|
+
execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
|
697
|
+
end
|
698
|
+
end
|
1251
699
|
end
|
1252
700
|
|
1253
701
|
# Returns the current ID of a table's sequence.
|
1254
702
|
def last_insert_id(sequence_name) #:nodoc:
|
1255
|
-
|
1256
|
-
Integer(r.rows.first.first)
|
703
|
+
Integer(last_insert_id_value(sequence_name))
|
1257
704
|
end
|
1258
705
|
|
1259
|
-
|
1260
|
-
|
1261
|
-
def select(sql, name = nil, binds = [])
|
1262
|
-
exec_query(sql, name, binds).to_a
|
706
|
+
def last_insert_id_value(sequence_name)
|
707
|
+
last_insert_id_result(sequence_name).rows.first.first
|
1263
708
|
end
|
1264
709
|
|
1265
|
-
def
|
1266
|
-
|
1267
|
-
results = result_as_array(res)
|
1268
|
-
fields = res.fields
|
1269
|
-
res.clear
|
1270
|
-
return fields, results
|
710
|
+
def last_insert_id_result(sequence_name) #:nodoc:
|
711
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1271
712
|
end
|
1272
713
|
|
1273
714
|
# Returns the list of a table's column names, data types, and default values.
|
@@ -1288,35 +729,25 @@ module ActiveRecord
|
|
1288
729
|
# Query implementation notes:
|
1289
730
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1290
731
|
# - ::regclass is a function that gives the id for a table name
|
1291
|
-
def column_definitions(table_name)
|
732
|
+
def column_definitions(table_name) # :nodoc:
|
1292
733
|
exec_query(<<-end_sql, 'SCHEMA').rows
|
1293
|
-
|
734
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1294
735
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
736
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
737
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
738
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
739
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
740
|
+
ORDER BY a.attnum
|
1300
741
|
end_sql
|
1301
742
|
end
|
1302
743
|
|
1303
|
-
def
|
1304
|
-
|
1305
|
-
|
1306
|
-
if match_data
|
1307
|
-
rest = name[match_data[0].length, name.length]
|
1308
|
-
rest = rest[1, rest.length] if rest.start_with? "."
|
1309
|
-
[match_data[1], (rest.length > 0 ? rest : nil)]
|
1310
|
-
end
|
1311
|
-
end
|
1312
|
-
|
1313
|
-
def extract_table_ref_from_insert_sql(sql)
|
1314
|
-
sql[/into\s+([^\(]*).*values\s*\(/i]
|
744
|
+
def extract_table_ref_from_insert_sql(sql) # :nodoc:
|
745
|
+
sql[/into\s+([^\(]*).*values\s*\(/im]
|
1315
746
|
$1.strip if $1
|
1316
747
|
end
|
1317
748
|
|
1318
|
-
def
|
1319
|
-
TableDefinition.new
|
749
|
+
def create_table_definition(name, temporary, options, as = nil) # :nodoc:
|
750
|
+
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
|
1320
751
|
end
|
1321
752
|
end
|
1322
753
|
end
|