activerecord 3.1.10 → 4.2.11
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 +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- 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 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- 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 +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- 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 +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- 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 +279 -232
- data/lib/active_record/base.rb +84 -1969
- 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 +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- 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 +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- 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 +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- 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 +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- 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 +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- 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 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- 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 +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- 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 +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -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 +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- 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/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- 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 -16
@@ -1,246 +1,142 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'active_support/core_ext/kernel/requires'
|
3
|
-
require 'active_support/core_ext/object/blank'
|
4
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
|
+
|
5
13
|
require 'arel/visitors/bind_visitor'
|
6
14
|
|
7
15
|
# Make sure we're using pg high enough for PGResult#values
|
8
|
-
gem 'pg', '~> 0.
|
16
|
+
gem 'pg', '~> 0.15'
|
9
17
|
require 'pg'
|
10
18
|
|
19
|
+
require 'ipaddr'
|
20
|
+
|
11
21
|
module ActiveRecord
|
12
|
-
|
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
|
+
|
13
29
|
# Establishes a connection to the database that's used by all Active Record objects
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
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) }
|
26
41
|
|
27
42
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
28
43
|
# so just pass a nil connection object for the time being.
|
29
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger,
|
44
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
33
48
|
module ConnectionAdapters
|
34
|
-
# PostgreSQL
|
35
|
-
class PostgreSQLColumn < Column #:nodoc:
|
36
|
-
# Instantiates a new PostgreSQL column definition in a table.
|
37
|
-
def initialize(name, default, sql_type = nil, null = true)
|
38
|
-
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
39
|
-
end
|
40
|
-
|
41
|
-
# :stopdoc:
|
42
|
-
class << self
|
43
|
-
attr_accessor :money_precision
|
44
|
-
def string_to_time(string)
|
45
|
-
return string unless String === string
|
46
|
-
|
47
|
-
case string
|
48
|
-
when 'infinity' then 1.0 / 0.0
|
49
|
-
when '-infinity' then -1.0 / 0.0
|
50
|
-
else
|
51
|
-
super
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
# :startdoc:
|
56
|
-
|
57
|
-
private
|
58
|
-
def extract_limit(sql_type)
|
59
|
-
case sql_type
|
60
|
-
when /^bigint/i; 8
|
61
|
-
when /^smallint/i; 2
|
62
|
-
else super
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Extracts the scale from PostgreSQL-specific data types.
|
67
|
-
def extract_scale(sql_type)
|
68
|
-
# Money type has a fixed scale of 2.
|
69
|
-
sql_type =~ /^money/ ? 2 : super
|
70
|
-
end
|
71
|
-
|
72
|
-
# Extracts the precision from PostgreSQL-specific data types.
|
73
|
-
def extract_precision(sql_type)
|
74
|
-
if sql_type == 'money'
|
75
|
-
self.class.money_precision
|
76
|
-
else
|
77
|
-
super
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Maps PostgreSQL-specific data types to logical Rails types.
|
82
|
-
def simplified_type(field_type)
|
83
|
-
case field_type
|
84
|
-
# Numeric and monetary types
|
85
|
-
when /^(?:real|double precision)$/
|
86
|
-
:float
|
87
|
-
# Monetary types
|
88
|
-
when 'money'
|
89
|
-
:decimal
|
90
|
-
# Character types
|
91
|
-
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
92
|
-
:string
|
93
|
-
# Binary data types
|
94
|
-
when 'bytea'
|
95
|
-
:binary
|
96
|
-
# Date/time types
|
97
|
-
when /^timestamp with(?:out)? time zone$/
|
98
|
-
:datetime
|
99
|
-
when 'interval'
|
100
|
-
:string
|
101
|
-
# Geometric types
|
102
|
-
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
103
|
-
:string
|
104
|
-
# Network address types
|
105
|
-
when /^(?:cidr|inet|macaddr)$/
|
106
|
-
:string
|
107
|
-
# Bit strings
|
108
|
-
when /^bit(?: varying)?(?:\(\d+\))?$/
|
109
|
-
:string
|
110
|
-
# XML type
|
111
|
-
when 'xml'
|
112
|
-
:xml
|
113
|
-
# tsvector type
|
114
|
-
when 'tsvector'
|
115
|
-
:tsvector
|
116
|
-
# Arrays
|
117
|
-
when /^\D+\[\]$/
|
118
|
-
:string
|
119
|
-
# Object identifier types
|
120
|
-
when 'oid'
|
121
|
-
:integer
|
122
|
-
# UUID type
|
123
|
-
when 'uuid'
|
124
|
-
:string
|
125
|
-
# Small and big integer types
|
126
|
-
when /^(?:small|big)int$/
|
127
|
-
:integer
|
128
|
-
# Pass through all types that are not specific to PostgreSQL.
|
129
|
-
else
|
130
|
-
super
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Extracts the value from a PostgreSQL column default definition.
|
135
|
-
def self.extract_value_from_default(default)
|
136
|
-
case default
|
137
|
-
# This is a performance optimization for Ruby 1.9.2 in development.
|
138
|
-
# If the value is nil, we return nil straight away without checking
|
139
|
-
# the regular expressions. If we check each regular expression,
|
140
|
-
# Regexp#=== will call NilClass#to_str, which will trigger
|
141
|
-
# method_missing (defined by whiny nil in ActiveSupport) which
|
142
|
-
# makes this method very very slow.
|
143
|
-
when NilClass
|
144
|
-
nil
|
145
|
-
# Numeric types
|
146
|
-
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
147
|
-
$1
|
148
|
-
# Character types
|
149
|
-
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
|
150
|
-
$1
|
151
|
-
# Character types (8.1 formatting)
|
152
|
-
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
153
|
-
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
154
|
-
# Binary data types
|
155
|
-
when /\A'(.*)'::bytea\z/m
|
156
|
-
$1
|
157
|
-
# Date/time types
|
158
|
-
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
159
|
-
$1
|
160
|
-
when /\A'(.*)'::interval\z/
|
161
|
-
$1
|
162
|
-
# Boolean type
|
163
|
-
when 'true'
|
164
|
-
true
|
165
|
-
when 'false'
|
166
|
-
false
|
167
|
-
# Geometric types
|
168
|
-
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
169
|
-
$1
|
170
|
-
# Network address types
|
171
|
-
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
172
|
-
$1
|
173
|
-
# Bit string types
|
174
|
-
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
175
|
-
$1
|
176
|
-
# XML type
|
177
|
-
when /\A'(.*)'::xml\z/m
|
178
|
-
$1
|
179
|
-
# Arrays
|
180
|
-
when /\A'(.*)'::"?\D+"?\[\]\z/
|
181
|
-
$1
|
182
|
-
# Object identifier types
|
183
|
-
when /\A-?\d+\z/
|
184
|
-
$1
|
185
|
-
else
|
186
|
-
# Anything else is blank, some user type, or some function
|
187
|
-
# and we can't know the value of that, so return nil.
|
188
|
-
nil
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
194
|
-
# 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.
|
195
50
|
#
|
196
51
|
# Options:
|
197
52
|
#
|
198
|
-
# * <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.
|
199
55
|
# * <tt>:port</tt> - Defaults to 5432.
|
200
|
-
# * <tt>:username</tt> - Defaults to
|
201
|
-
# * <tt>:password</tt> -
|
202
|
-
# * <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.
|
203
59
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
204
|
-
# as a string of comma-separated schema names.
|
60
|
+
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
205
61
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
206
62
|
# <encoding></tt> call on the connection.
|
207
63
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
208
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 .
|
209
76
|
class PostgreSQLAdapter < AbstractAdapter
|
210
|
-
|
211
|
-
def xml(*args)
|
212
|
-
options = args.extract_options!
|
213
|
-
column(args[0], 'xml', options)
|
214
|
-
end
|
215
|
-
|
216
|
-
def tsvector(*args)
|
217
|
-
options = args.extract_options!
|
218
|
-
column(args[0], 'tsvector', options)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
ADAPTER_NAME = 'PostgreSQL'
|
77
|
+
ADAPTER_NAME = 'PostgreSQL'.freeze
|
223
78
|
|
224
79
|
NATIVE_DATABASE_TYPES = {
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
235
|
-
:
|
236
|
-
:
|
237
|
-
:
|
238
|
-
:
|
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" },
|
239
114
|
}
|
240
115
|
|
241
|
-
|
242
|
-
|
243
|
-
|
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]
|
244
140
|
end
|
245
141
|
|
246
142
|
# Returns +true+, since this connection adapter supports prepared statement
|
@@ -249,6 +145,30 @@ module ActiveRecord
|
|
249
145
|
true
|
250
146
|
end
|
251
147
|
|
148
|
+
def supports_index_sort_order?
|
149
|
+
true
|
150
|
+
end
|
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,30 +206,32 @@ 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
|
-
|
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
|
232
|
+
else
|
233
|
+
@prepared_statements = false
|
234
|
+
end
|
313
235
|
|
314
236
|
@connection_parameters, @config = connection_parameters, config
|
315
237
|
|
@@ -319,22 +241,16 @@ module ActiveRecord
|
|
319
241
|
|
320
242
|
connect
|
321
243
|
@statements = StatementPool.new @connection,
|
322
|
-
config.fetch(:statement_limit) { 1000 }
|
244
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
|
323
245
|
|
324
246
|
if postgresql_version < 80200
|
325
247
|
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
|
326
248
|
end
|
327
249
|
|
250
|
+
@type_map = Type::HashLookupTypeMap.new
|
251
|
+
initialize_type_map(type_map)
|
328
252
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
329
|
-
|
330
|
-
|
331
|
-
def self.visitor_for(pool) # :nodoc:
|
332
|
-
config = pool.spec.config
|
333
|
-
if config.fetch(:prepared_statements) { true }
|
334
|
-
Arel::Visitors::PostgreSQL.new pool
|
335
|
-
else
|
336
|
-
BindSubstitution.new pool
|
337
|
-
end
|
253
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
338
254
|
end
|
339
255
|
|
340
256
|
# Clears the prepared statements cache.
|
@@ -342,29 +258,39 @@ module ActiveRecord
|
|
342
258
|
@statements.clear
|
343
259
|
end
|
344
260
|
|
261
|
+
def truncate(table_name, name = nil)
|
262
|
+
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
263
|
+
end
|
264
|
+
|
345
265
|
# Is this connection alive and ready for queries?
|
346
266
|
def active?
|
347
|
-
@connection.
|
267
|
+
@connection.query 'SELECT 1'
|
268
|
+
true
|
348
269
|
rescue PGError
|
349
270
|
false
|
350
271
|
end
|
351
272
|
|
352
273
|
# Close then reopen the connection.
|
353
274
|
def reconnect!
|
354
|
-
|
275
|
+
super
|
355
276
|
@connection.reset
|
356
277
|
configure_connection
|
357
278
|
end
|
358
279
|
|
359
280
|
def reset!
|
360
281
|
clear_cache!
|
361
|
-
|
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
|
362
288
|
end
|
363
289
|
|
364
290
|
# Disconnects from the database if already connected. Otherwise, this
|
365
291
|
# method does nothing.
|
366
292
|
def disconnect!
|
367
|
-
|
293
|
+
super
|
368
294
|
@connection.close rescue nil
|
369
295
|
end
|
370
296
|
|
@@ -382,708 +308,316 @@ module ActiveRecord
|
|
382
308
|
true
|
383
309
|
end
|
384
310
|
|
385
|
-
# Enable standard-conforming strings if available.
|
386
311
|
def set_standard_conforming_strings
|
387
|
-
|
388
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
389
|
-
ensure
|
390
|
-
self.client_min_messages = old
|
391
|
-
end
|
392
|
-
|
393
|
-
def supports_insert_with_returning?
|
394
|
-
true
|
312
|
+
execute('SET standard_conforming_strings = on', 'SCHEMA')
|
395
313
|
end
|
396
314
|
|
397
315
|
def supports_ddl_transactions?
|
398
316
|
true
|
399
317
|
end
|
400
318
|
|
401
|
-
|
402
|
-
def supports_savepoints?
|
319
|
+
def supports_explain?
|
403
320
|
true
|
404
321
|
end
|
405
322
|
|
406
|
-
# Returns
|
407
|
-
def
|
408
|
-
|
323
|
+
# Returns true if pg > 9.1
|
324
|
+
def supports_extensions?
|
325
|
+
postgresql_version >= 90100
|
409
326
|
end
|
410
327
|
|
411
|
-
#
|
412
|
-
|
413
|
-
|
414
|
-
def escape_bytea(value)
|
415
|
-
@connection.escape_bytea(value) if value
|
328
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
329
|
+
def supports_ranges?
|
330
|
+
postgresql_version >= 90200
|
416
331
|
end
|
417
332
|
|
418
|
-
|
419
|
-
|
420
|
-
# on escaped binary output from database drive.
|
421
|
-
def unescape_bytea(value)
|
422
|
-
@connection.unescape_bytea(value) if value
|
423
|
-
end
|
424
|
-
|
425
|
-
# Quotes PostgreSQL-specific data types for SQL input.
|
426
|
-
def quote(value, column = nil) #:nodoc:
|
427
|
-
return super unless column
|
428
|
-
|
429
|
-
case value
|
430
|
-
when Float
|
431
|
-
return super unless value.infinite? && column.type == :datetime
|
432
|
-
"'#{value.to_s.downcase}'"
|
433
|
-
when Numeric
|
434
|
-
return super unless column.sql_type == 'money'
|
435
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
436
|
-
"'#{value}'"
|
437
|
-
when String
|
438
|
-
case column.sql_type
|
439
|
-
when 'bytea' then "'#{escape_bytea(value)}'"
|
440
|
-
when 'xml' then "xml '#{quote_string(value)}'"
|
441
|
-
when /^bit/
|
442
|
-
case value
|
443
|
-
when /^[01]*$/ then "B'#{value}'" # Bit-string notation
|
444
|
-
when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
|
445
|
-
end
|
446
|
-
else
|
447
|
-
super
|
448
|
-
end
|
449
|
-
else
|
450
|
-
super
|
451
|
-
end
|
333
|
+
def supports_materialized_views?
|
334
|
+
postgresql_version >= 90300
|
452
335
|
end
|
453
336
|
|
454
|
-
def
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
when String
|
459
|
-
return super unless 'bytea' == column.sql_type
|
460
|
-
{ :value => value, :format => 1 }
|
461
|
-
else
|
462
|
-
super
|
463
|
-
end
|
337
|
+
def enable_extension(name)
|
338
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
339
|
+
reload_type_map
|
340
|
+
}
|
464
341
|
end
|
465
342
|
|
466
|
-
|
467
|
-
|
468
|
-
|
343
|
+
def disable_extension(name)
|
344
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
345
|
+
reload_type_map
|
346
|
+
}
|
469
347
|
end
|
470
348
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
# - schema_name."table.name"
|
477
|
-
# - "schema.name".table_name
|
478
|
-
# - "schema.name"."table.name"
|
479
|
-
def quote_table_name(name)
|
480
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
481
|
-
|
482
|
-
unless name_part
|
483
|
-
quote_column_name(schema)
|
484
|
-
else
|
485
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
486
|
-
"#{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
|
487
354
|
end
|
488
355
|
end
|
489
356
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
end
|
494
|
-
|
495
|
-
# Quote date/time values for use in SQL input. Includes microseconds
|
496
|
-
# if the value is a Time responding to usec.
|
497
|
-
def quoted_date(value) #:nodoc:
|
498
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
499
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
357
|
+
def extensions
|
358
|
+
if supports_extensions?
|
359
|
+
exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
|
500
360
|
else
|
501
361
|
super
|
502
362
|
end
|
503
363
|
end
|
504
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
|
+
|
505
370
|
# Set the authorized user for this session
|
506
371
|
def session_auth=(user)
|
507
372
|
clear_cache!
|
508
373
|
exec_query "SET SESSION AUTHORIZATION #{user}"
|
509
374
|
end
|
510
375
|
|
511
|
-
|
512
|
-
|
513
|
-
def supports_disable_referential_integrity? #:nodoc:
|
514
|
-
true
|
515
|
-
end
|
516
|
-
|
517
|
-
def disable_referential_integrity #:nodoc:
|
518
|
-
if supports_disable_referential_integrity? then
|
519
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
520
|
-
end
|
521
|
-
yield
|
522
|
-
ensure
|
523
|
-
if supports_disable_referential_integrity? then
|
524
|
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
525
|
-
end
|
526
|
-
end
|
527
|
-
|
528
|
-
# DATABASE STATEMENTS ======================================
|
529
|
-
|
530
|
-
# Executes a SELECT query and returns an array of rows. Each row is an
|
531
|
-
# array of field values.
|
532
|
-
def select_rows(sql, name = nil)
|
533
|
-
select_raw(sql, name).last
|
534
|
-
end
|
535
|
-
|
536
|
-
# Executes an INSERT query and returns the new record's ID
|
537
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
538
|
-
# Extract the table from the insert sql. Yuck.
|
539
|
-
_, table = extract_schema_and_table(sql.split(" ", 4)[2])
|
540
|
-
|
541
|
-
pk ||= primary_key(table)
|
542
|
-
|
543
|
-
if pk
|
544
|
-
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
545
|
-
else
|
546
|
-
super
|
547
|
-
end
|
548
|
-
end
|
549
|
-
alias :create :insert
|
550
|
-
|
551
|
-
# create a 2D array representing the result set
|
552
|
-
def result_as_array(res) #:nodoc:
|
553
|
-
# check if we have any binary column and if they need escaping
|
554
|
-
ftypes = Array.new(res.nfields) do |i|
|
555
|
-
[i, res.ftype(i)]
|
556
|
-
end
|
557
|
-
|
558
|
-
rows = res.values
|
559
|
-
return rows unless ftypes.any? { |_, x|
|
560
|
-
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
561
|
-
}
|
562
|
-
|
563
|
-
typehash = ftypes.group_by { |_, type| type }
|
564
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
565
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
566
|
-
|
567
|
-
rows.each do |row|
|
568
|
-
# unescape string passed BYTEA field (OID == 17)
|
569
|
-
binaries.each do |index, _|
|
570
|
-
row[index] = unescape_bytea(row[index])
|
571
|
-
end
|
572
|
-
|
573
|
-
# If this is a money type column and there are any currency symbols,
|
574
|
-
# then strip them off. Indeed it would be prettier to do this in
|
575
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
576
|
-
# fields that call value_before_type_cast.
|
577
|
-
monies.each do |index, _|
|
578
|
-
data = row[index]
|
579
|
-
# Because money output is formatted according to the locale, there are two
|
580
|
-
# cases to consider (note the decimal separators):
|
581
|
-
# (1) $12,345,678.12
|
582
|
-
# (2) $12.345.678,12
|
583
|
-
case data
|
584
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
585
|
-
data.gsub!(/[^-\d.]/, '')
|
586
|
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
587
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
588
|
-
end
|
589
|
-
end
|
590
|
-
end
|
376
|
+
def use_insert_returning?
|
377
|
+
@use_insert_returning
|
591
378
|
end
|
592
379
|
|
593
|
-
|
594
|
-
|
595
|
-
def query(sql, name = nil) #:nodoc:
|
596
|
-
log(sql, name) do
|
597
|
-
result_as_array @connection.async_exec(sql)
|
598
|
-
end
|
380
|
+
def valid_type?(type)
|
381
|
+
!native_database_types[type].nil?
|
599
382
|
end
|
600
383
|
|
601
|
-
|
602
|
-
|
603
|
-
def execute(sql, name = nil)
|
604
|
-
log(sql, name) do
|
605
|
-
@connection.async_exec(sql)
|
606
|
-
end
|
384
|
+
def update_table_definition(table_name, base) #:nodoc:
|
385
|
+
PostgreSQL::Table.new(table_name, base)
|
607
386
|
end
|
608
387
|
|
609
|
-
def
|
610
|
-
|
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)
|
611
391
|
end
|
612
392
|
|
613
|
-
def
|
614
|
-
|
615
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
616
|
-
exec_cache(sql, binds)
|
617
|
-
|
618
|
-
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
|
619
|
-
result.clear
|
620
|
-
return ret
|
621
|
-
end
|
393
|
+
def column_name_for_operation(operation, node) # :nodoc:
|
394
|
+
OPERATION_ALIASES.fetch(operation) { operation.downcase }
|
622
395
|
end
|
623
396
|
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
result.clear
|
630
|
-
affected
|
631
|
-
end
|
632
|
-
end
|
633
|
-
alias :exec_update :exec_delete
|
397
|
+
OPERATION_ALIASES = { # :nodoc:
|
398
|
+
"maximum" => "max",
|
399
|
+
"minimum" => "min",
|
400
|
+
"average" => "avg",
|
401
|
+
}
|
634
402
|
|
635
|
-
|
636
|
-
unless pk
|
637
|
-
_, table = extract_schema_and_table(sql.split(" ", 4)[2])
|
403
|
+
protected
|
638
404
|
|
639
|
-
|
405
|
+
# Returns the version of the connected PostgreSQL server.
|
406
|
+
def postgresql_version
|
407
|
+
@connection.server_version
|
640
408
|
end
|
641
409
|
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
end
|
646
|
-
|
647
|
-
# Executes an UPDATE query and returns the number of affected tuples.
|
648
|
-
def update_sql(sql, name = nil)
|
649
|
-
super.cmd_tuples
|
650
|
-
end
|
651
|
-
|
652
|
-
# Begins a transaction.
|
653
|
-
def begin_db_transaction
|
654
|
-
execute "BEGIN"
|
655
|
-
end
|
656
|
-
|
657
|
-
# Commits a transaction.
|
658
|
-
def commit_db_transaction
|
659
|
-
execute "COMMIT"
|
660
|
-
end
|
661
|
-
|
662
|
-
# Aborts a transaction.
|
663
|
-
def rollback_db_transaction
|
664
|
-
execute "ROLLBACK"
|
665
|
-
end
|
666
|
-
|
667
|
-
def outside_transaction?
|
668
|
-
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
669
|
-
end
|
670
|
-
|
671
|
-
def create_savepoint
|
672
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
673
|
-
end
|
674
|
-
|
675
|
-
def rollback_to_savepoint
|
676
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
677
|
-
end
|
678
|
-
|
679
|
-
def release_savepoint
|
680
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
681
|
-
end
|
410
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
411
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
412
|
+
UNIQUE_VIOLATION = "23505"
|
682
413
|
|
683
|
-
|
684
|
-
|
685
|
-
def recreate_database(name) #:nodoc:
|
686
|
-
drop_database(name)
|
687
|
-
create_database(name)
|
688
|
-
end
|
414
|
+
def translate_exception(exception, message)
|
415
|
+
return exception unless exception.respond_to?(:result)
|
689
416
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
# create_database config[:database], config
|
696
|
-
# create_database 'foo_development', :encoding => 'unicode'
|
697
|
-
def create_database(name, options = {})
|
698
|
-
options = options.reverse_merge(:encoding => "utf8")
|
699
|
-
|
700
|
-
option_string = options.symbolize_keys.sum do |key, value|
|
701
|
-
case key
|
702
|
-
when :owner
|
703
|
-
" OWNER = \"#{value}\""
|
704
|
-
when :template
|
705
|
-
" TEMPLATE = \"#{value}\""
|
706
|
-
when :encoding
|
707
|
-
" ENCODING = '#{value}'"
|
708
|
-
when :tablespace
|
709
|
-
" TABLESPACE = \"#{value}\""
|
710
|
-
when :connection_limit
|
711
|
-
" CONNECTION LIMIT = #{value}"
|
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)
|
712
422
|
else
|
713
|
-
|
423
|
+
super
|
714
424
|
end
|
715
425
|
end
|
716
426
|
|
717
|
-
|
718
|
-
end
|
719
|
-
|
720
|
-
# Drops a PostgreSQL database.
|
721
|
-
#
|
722
|
-
# Example:
|
723
|
-
# drop_database 'matt_development'
|
724
|
-
def drop_database(name) #:nodoc:
|
725
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
726
|
-
end
|
727
|
-
|
728
|
-
# Returns the list of all tables in the schema search path or a specified schema.
|
729
|
-
def tables(name = nil)
|
730
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
731
|
-
SELECT tablename
|
732
|
-
FROM pg_tables
|
733
|
-
WHERE schemaname = ANY (current_schemas(false))
|
734
|
-
SQL
|
735
|
-
end
|
736
|
-
|
737
|
-
def table_exists?(name)
|
738
|
-
schema, table = extract_schema_and_table(name.to_s)
|
739
|
-
return false unless table # Abstract classes is having nil table name
|
427
|
+
private
|
740
428
|
|
741
|
-
|
742
|
-
|
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
|
743
433
|
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
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
|
751
489
|
|
752
|
-
|
753
|
-
|
754
|
-
|
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
|
755
509
|
|
756
|
-
|
757
|
-
table = schema
|
758
|
-
schema = nil
|
510
|
+
load_additional_types(m)
|
759
511
|
end
|
760
512
|
|
761
|
-
|
762
|
-
|
763
|
-
|
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
|
519
|
+
else
|
520
|
+
super
|
521
|
+
end
|
764
522
|
end
|
765
|
-
[schema, table]
|
766
|
-
end
|
767
|
-
|
768
|
-
# Returns an array of indexes for the given table.
|
769
|
-
def indexes(table_name, name = nil)
|
770
|
-
result = query(<<-SQL, name)
|
771
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
|
772
|
-
FROM pg_class t
|
773
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
774
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
775
|
-
WHERE i.relkind = 'i'
|
776
|
-
AND d.indisprimary = 'f'
|
777
|
-
AND t.relname = '#{table_name}'
|
778
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
779
|
-
ORDER BY i.relname
|
780
|
-
SQL
|
781
|
-
|
782
|
-
|
783
|
-
result.map do |row|
|
784
|
-
index_name = row[0]
|
785
|
-
unique = row[1] == 't'
|
786
|
-
indkey = row[2].split(" ")
|
787
|
-
oid = row[3]
|
788
|
-
|
789
|
-
columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
|
790
|
-
SELECT a.attnum, a.attname
|
791
|
-
FROM pg_attribute a
|
792
|
-
WHERE a.attrelid = #{oid}
|
793
|
-
AND a.attnum IN (#{indkey.join(",")})
|
794
|
-
SQL
|
795
|
-
|
796
|
-
column_names = columns.values_at(*indkey).compact
|
797
|
-
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
|
798
|
-
end.compact
|
799
|
-
end
|
800
523
|
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
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
|
806
544
|
end
|
807
|
-
end
|
808
|
-
|
809
|
-
# Returns the current database name.
|
810
|
-
def current_database
|
811
|
-
query('select current_database()')[0][0]
|
812
|
-
end
|
813
545
|
|
814
|
-
|
815
|
-
|
816
|
-
query(<<-end_sql)[0][0]
|
817
|
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
818
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
819
|
-
end_sql
|
820
|
-
end
|
821
|
-
|
822
|
-
# Sets the schema search path to a string of comma-separated schema names.
|
823
|
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
824
|
-
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
825
|
-
#
|
826
|
-
# This should be not be called manually but set in database.yml.
|
827
|
-
def schema_search_path=(schema_csv)
|
828
|
-
if schema_csv
|
829
|
-
execute "SET search_path TO #{schema_csv}"
|
830
|
-
@schema_search_path = schema_csv
|
546
|
+
def extract_default_function(default_value, default) # :nodoc:
|
547
|
+
default if has_default_function?(default_value, default)
|
831
548
|
end
|
832
|
-
end
|
833
|
-
|
834
|
-
# Returns the active schema search path.
|
835
|
-
def schema_search_path
|
836
|
-
@schema_search_path ||= query('SHOW search_path')[0][0]
|
837
|
-
end
|
838
|
-
|
839
|
-
# Returns the current client message level.
|
840
|
-
def client_min_messages
|
841
|
-
query('SHOW client_min_messages', 'SCHEMA')[0][0]
|
842
|
-
end
|
843
|
-
|
844
|
-
# Set the client message level.
|
845
|
-
def client_min_messages=(level)
|
846
|
-
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
|
847
|
-
end
|
848
|
-
|
849
|
-
# Returns the sequence name for a table's primary key or some other specified key.
|
850
|
-
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
851
|
-
serial_sequence(table_name, pk || 'id').split('.').last
|
852
|
-
rescue ActiveRecord::StatementInvalid
|
853
|
-
"#{table_name}_#{pk || 'id'}_seq"
|
854
|
-
end
|
855
|
-
|
856
|
-
def serial_sequence(table, column)
|
857
|
-
result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
|
858
|
-
SELECT pg_get_serial_sequence($1, $2)
|
859
|
-
eosql
|
860
|
-
result.rows.first.first
|
861
|
-
end
|
862
549
|
|
863
|
-
|
864
|
-
|
865
|
-
unless pk and sequence
|
866
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
867
|
-
|
868
|
-
pk ||= default_pk
|
869
|
-
sequence ||= default_sequence
|
550
|
+
def has_default_function?(default_value, default) # :nodoc:
|
551
|
+
!default_value && (%r{\w+\(.*\)} === default)
|
870
552
|
end
|
871
553
|
|
872
|
-
|
873
|
-
|
874
|
-
end
|
554
|
+
def load_additional_types(type_map, oids = nil) # :nodoc:
|
555
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
875
556
|
|
876
|
-
|
877
|
-
|
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
|
568
|
+
end
|
878
569
|
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
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)
|
574
|
+
end
|
884
575
|
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
# given table's primary key.
|
889
|
-
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
890
|
-
SELECT attr.attname, seq.relname
|
891
|
-
FROM pg_class seq,
|
892
|
-
pg_attribute attr,
|
893
|
-
pg_depend dep,
|
894
|
-
pg_namespace name,
|
895
|
-
pg_constraint cons
|
896
|
-
WHERE seq.oid = dep.objid
|
897
|
-
AND seq.relkind = 'S'
|
898
|
-
AND attr.attrelid = dep.refobjid
|
899
|
-
AND attr.attnum = dep.refobjsubid
|
900
|
-
AND attr.attrelid = cons.conrelid
|
901
|
-
AND attr.attnum = cons.conkey[1]
|
902
|
-
AND cons.contype = 'p'
|
903
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
904
|
-
end_sql
|
905
|
-
|
906
|
-
if result.nil? or result.empty?
|
907
|
-
# If that fails, try parsing the primary key's default value.
|
908
|
-
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
909
|
-
# the 8.1+ nextval('foo'::regclass).
|
910
|
-
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
911
|
-
SELECT attr.attname,
|
912
|
-
CASE
|
913
|
-
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
914
|
-
substr(split_part(def.adsrc, '''', 2),
|
915
|
-
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
916
|
-
ELSE split_part(def.adsrc, '''', 2)
|
917
|
-
END
|
918
|
-
FROM pg_class t
|
919
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
920
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
921
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
922
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
923
|
-
AND cons.contype = 'p'
|
924
|
-
AND def.adsrc ~* 'nextval'
|
925
|
-
end_sql
|
576
|
+
execute_and_clear(query, 'SCHEMA', []) do |records|
|
577
|
+
initializer.run(records)
|
578
|
+
end
|
926
579
|
end
|
927
580
|
|
928
|
-
|
929
|
-
rescue
|
930
|
-
nil
|
931
|
-
end
|
932
|
-
|
933
|
-
# Returns just a table's primary key
|
934
|
-
def primary_key(table)
|
935
|
-
row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
|
936
|
-
SELECT DISTINCT(attr.attname)
|
937
|
-
FROM pg_attribute attr
|
938
|
-
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
|
939
|
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
940
|
-
WHERE cons.contype = 'p'
|
941
|
-
AND dep.refobjid = $1::regclass
|
942
|
-
end_sql
|
943
|
-
|
944
|
-
row && row.first
|
945
|
-
end
|
946
|
-
|
947
|
-
# Renames a table.
|
948
|
-
#
|
949
|
-
# Example:
|
950
|
-
# rename_table('octopuses', 'octopi')
|
951
|
-
def rename_table(name, new_name)
|
952
|
-
clear_cache!
|
953
|
-
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
954
|
-
end
|
955
|
-
|
956
|
-
# Adds a new column to the named table.
|
957
|
-
# See TableDefinition#column for details of the options you can use.
|
958
|
-
def add_column(table_name, column_name, type, options = {})
|
959
|
-
clear_cache!
|
960
|
-
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])}"
|
961
|
-
add_column_options!(add_column_sql, options)
|
962
|
-
|
963
|
-
execute add_column_sql
|
964
|
-
end
|
965
|
-
|
966
|
-
# Changes the column of a table.
|
967
|
-
def change_column(table_name, column_name, type, options = {})
|
968
|
-
clear_cache!
|
969
|
-
quoted_table_name = quote_table_name(table_name)
|
970
|
-
|
971
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
972
|
-
|
973
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
974
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
975
|
-
end
|
976
|
-
|
977
|
-
# Changes the default value of a table column.
|
978
|
-
def change_column_default(table_name, column_name, default)
|
979
|
-
clear_cache!
|
980
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
981
|
-
end
|
581
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
982
582
|
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
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
|
987
589
|
end
|
988
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
989
|
-
end
|
990
|
-
|
991
|
-
# Renames a column in a table.
|
992
|
-
def rename_column(table_name, column_name, new_column_name)
|
993
|
-
clear_cache!
|
994
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
995
|
-
end
|
996
|
-
|
997
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
998
|
-
execute "DROP INDEX #{quote_table_name(index_name)}"
|
999
|
-
end
|
1000
|
-
|
1001
|
-
def rename_index(table_name, old_name, new_name)
|
1002
|
-
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
1003
|
-
end
|
1004
590
|
|
1005
|
-
|
1006
|
-
|
1007
|
-
end
|
1008
|
-
|
1009
|
-
# Maps logical Rails types to PostgreSQL-specific data types.
|
1010
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
1011
|
-
return super unless type.to_s == 'integer'
|
1012
|
-
return 'integer' unless limit
|
1013
|
-
|
1014
|
-
case limit
|
1015
|
-
when 1, 2; 'smallint'
|
1016
|
-
when 3, 4; 'integer'
|
1017
|
-
when 5..8; 'bigint'
|
1018
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
591
|
+
def exec_no_cache(sql, name, binds)
|
592
|
+
log(sql, name, binds) { @connection.async_exec(sql, []) }
|
1019
593
|
end
|
1020
|
-
end
|
1021
|
-
|
1022
|
-
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
1023
|
-
#
|
1024
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
1025
|
-
# requires that the ORDER BY include the distinct column.
|
1026
|
-
#
|
1027
|
-
# distinct("posts.id", "posts.created_at desc")
|
1028
|
-
def distinct(columns, orders) #:nodoc:
|
1029
|
-
return "DISTINCT #{columns}" if orders.empty?
|
1030
|
-
|
1031
|
-
# Construct a clean list of column names from the ORDER BY clause, removing
|
1032
|
-
# any ASC/DESC modifiers
|
1033
|
-
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
|
1034
|
-
order_columns.delete_if { |c| c.blank? }
|
1035
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
1036
|
-
|
1037
|
-
"DISTINCT #{columns}, #{order_columns * ', '}"
|
1038
|
-
end
|
1039
594
|
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
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
|
+
}
|
1045
600
|
|
1046
|
-
|
1047
|
-
|
1048
|
-
when /duplicate key value violates unique constraint/
|
1049
|
-
RecordNotUnique.new(message, exception)
|
1050
|
-
when /violates foreign key constraint/
|
1051
|
-
InvalidForeignKey.new(message, exception)
|
1052
|
-
else
|
1053
|
-
super
|
601
|
+
log(sql, name, type_casted_binds, stmt_key) do
|
602
|
+
@connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
|
1054
603
|
end
|
1055
|
-
|
1056
|
-
|
1057
|
-
private
|
1058
|
-
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
1059
|
-
|
1060
|
-
def exec_no_cache(sql, binds)
|
1061
|
-
@connection.async_exec(sql)
|
1062
|
-
end
|
604
|
+
rescue ActiveRecord::StatementInvalid => e
|
605
|
+
pgerror = e.original_exception
|
1063
606
|
|
1064
|
-
|
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
|
1065
611
|
begin
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
rescue PGError => e
|
1076
|
-
# Get the PG code for the failure. Annoyingly, the code for
|
1077
|
-
# prepared statements whose return value may have changed is
|
1078
|
-
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
1079
|
-
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1080
|
-
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
1081
|
-
if FEATURE_NOT_SUPPORTED == code
|
1082
|
-
@statements.delete sql_key(sql)
|
1083
|
-
retry
|
1084
|
-
else
|
1085
|
-
raise e
|
1086
|
-
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
|
1087
621
|
end
|
1088
622
|
end
|
1089
623
|
|
@@ -1099,28 +633,35 @@ module ActiveRecord
|
|
1099
633
|
sql_key = sql_key(sql)
|
1100
634
|
unless @statements.key? sql_key
|
1101
635
|
nextkey = @statements.next_key
|
1102
|
-
|
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
|
1103
643
|
@statements[sql_key] = nextkey
|
1104
644
|
end
|
1105
645
|
@statements[sql_key]
|
1106
646
|
end
|
1107
647
|
|
1108
|
-
# The internal PostgreSQL identifier of the money data type.
|
1109
|
-
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
1110
|
-
# The internal PostgreSQL identifier of the BYTEA data type.
|
1111
|
-
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
|
1112
|
-
|
1113
648
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1114
649
|
# connected server's characteristics.
|
1115
650
|
def connect
|
1116
|
-
@connection = PGconn.connect(
|
651
|
+
@connection = PGconn.connect(@connection_parameters)
|
1117
652
|
|
1118
653
|
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1119
654
|
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
1120
655
|
# should know about this but can't detect it there, so deal with it here.
|
1121
|
-
|
656
|
+
OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
|
1122
657
|
|
1123
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
|
1124
665
|
end
|
1125
666
|
|
1126
667
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -1129,39 +670,45 @@ module ActiveRecord
|
|
1129
670
|
if @config[:encoding]
|
1130
671
|
@connection.set_client_encoding(@config[:encoding])
|
1131
672
|
end
|
1132
|
-
self.client_min_messages = @config[:min_messages]
|
673
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
1133
674
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1134
675
|
|
1135
|
-
# Use standard-conforming strings
|
676
|
+
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
1136
677
|
set_standard_conforming_strings
|
1137
678
|
|
1138
679
|
# If using Active Record's time zone support configure the connection to return
|
1139
680
|
# TIMESTAMP WITH ZONE types in UTC.
|
681
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
1140
682
|
if ActiveRecord::Base.default_timezone == :utc
|
1141
683
|
execute("SET time zone 'UTC'", 'SCHEMA')
|
1142
684
|
elsif @local_tz
|
1143
685
|
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1144
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
|
1145
699
|
end
|
1146
700
|
|
1147
701
|
# Returns the current ID of a table's sequence.
|
1148
702
|
def last_insert_id(sequence_name) #:nodoc:
|
1149
|
-
|
1150
|
-
Integer(r.rows.first.first)
|
703
|
+
Integer(last_insert_id_value(sequence_name))
|
1151
704
|
end
|
1152
705
|
|
1153
|
-
|
1154
|
-
|
1155
|
-
def select(sql, name = nil, binds = [])
|
1156
|
-
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
|
1157
708
|
end
|
1158
709
|
|
1159
|
-
def
|
1160
|
-
|
1161
|
-
results = result_as_array(res)
|
1162
|
-
fields = res.fields
|
1163
|
-
res.clear
|
1164
|
-
return fields, results
|
710
|
+
def last_insert_id_result(sequence_name) #:nodoc:
|
711
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1165
712
|
end
|
1166
713
|
|
1167
714
|
# Returns the list of a table's column names, data types, and default values.
|
@@ -1182,30 +729,26 @@ module ActiveRecord
|
|
1182
729
|
# Query implementation notes:
|
1183
730
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1184
731
|
# - ::regclass is a function that gives the id for a table name
|
1185
|
-
def column_definitions(table_name)
|
732
|
+
def column_definitions(table_name) # :nodoc:
|
1186
733
|
exec_query(<<-end_sql, 'SCHEMA').rows
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
734
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
735
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
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
|
1193
741
|
end_sql
|
1194
742
|
end
|
1195
743
|
|
1196
|
-
def
|
1197
|
-
|
1198
|
-
|
1199
|
-
if match_data
|
1200
|
-
rest = name[match_data[0].length, name.length]
|
1201
|
-
rest = rest[1, rest.length] if rest.start_with? "."
|
1202
|
-
[match_data[1], (rest.length > 0 ? rest : nil)]
|
1203
|
-
end
|
744
|
+
def extract_table_ref_from_insert_sql(sql) # :nodoc:
|
745
|
+
sql[/into\s+([^\(]*).*values\s*\(/im]
|
746
|
+
$1.strip if $1
|
1204
747
|
end
|
1205
748
|
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
749
|
+
def create_table_definition(name, temporary, options, as = nil) # :nodoc:
|
750
|
+
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
|
751
|
+
end
|
1209
752
|
end
|
1210
753
|
end
|
1211
754
|
end
|