activerecord 3.2.19 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- 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 +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -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 +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -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/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -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 +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- 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/mysql_adapter.rb +0 -441
- 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/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,245 +1,132 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'active_record/connection_adapters/statement_pool'
|
4
|
-
require 'arel/visitors/bind_visitor'
|
5
|
-
|
6
|
-
# Make sure we're using pg high enough for PGResult#values
|
7
|
-
gem 'pg', '~> 0.11'
|
1
|
+
# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
|
2
|
+
gem 'pg', '~> 0.18'
|
8
3
|
require 'pg'
|
9
4
|
|
5
|
+
require "active_record/connection_adapters/abstract_adapter"
|
6
|
+
require "active_record/connection_adapters/postgresql/column"
|
7
|
+
require "active_record/connection_adapters/postgresql/database_statements"
|
8
|
+
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
|
9
|
+
require "active_record/connection_adapters/postgresql/oid"
|
10
|
+
require "active_record/connection_adapters/postgresql/quoting"
|
11
|
+
require "active_record/connection_adapters/postgresql/referential_integrity"
|
12
|
+
require "active_record/connection_adapters/postgresql/schema_definitions"
|
13
|
+
require "active_record/connection_adapters/postgresql/schema_dumper"
|
14
|
+
require "active_record/connection_adapters/postgresql/schema_statements"
|
15
|
+
require "active_record/connection_adapters/postgresql/type_metadata"
|
16
|
+
require "active_record/connection_adapters/postgresql/utils"
|
17
|
+
require "active_record/connection_adapters/statement_pool"
|
18
|
+
|
10
19
|
module ActiveRecord
|
11
|
-
|
20
|
+
module ConnectionHandling # :nodoc:
|
12
21
|
# 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
|
-
|
22
|
+
def postgresql_connection(config)
|
23
|
+
conn_params = config.symbolize_keys
|
24
|
+
|
25
|
+
conn_params.delete_if { |_, v| v.nil? }
|
26
|
+
|
27
|
+
# Map ActiveRecords param names to PGs.
|
28
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
29
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
30
|
+
|
31
|
+
# Forward only valid config params to PGconn.connect.
|
32
|
+
valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl]
|
33
|
+
conn_params.slice!(*valid_conn_param_keys)
|
25
34
|
|
26
35
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
27
36
|
# so just pass a nil connection object for the time being.
|
28
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger,
|
37
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
32
41
|
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.
|
42
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
191
43
|
#
|
192
44
|
# Options:
|
193
45
|
#
|
194
|
-
# * <tt>:host</tt> - Defaults to
|
46
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
47
|
+
# the default is to connect to localhost.
|
195
48
|
# * <tt>:port</tt> - Defaults to 5432.
|
196
|
-
# * <tt>:username</tt> - Defaults to
|
197
|
-
# * <tt>:password</tt> -
|
198
|
-
# * <tt>:database</tt> -
|
49
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
50
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
51
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
199
52
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
53
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
54
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
55
|
# <encoding></tt> call on the connection.
|
203
56
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
57
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
58
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
59
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
60
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
61
|
+
# defaults to true.
|
62
|
+
#
|
63
|
+
# Any further options are used as connection parameters to libpq. See
|
64
|
+
# http://www.postgresql.org/docs/current/static/libpq-connect.html for the
|
65
|
+
# list of parameters.
|
66
|
+
#
|
67
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
68
|
+
# See http://www.postgresql.org/docs/current/static/libpq-envars.html .
|
205
69
|
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'
|
70
|
+
ADAPTER_NAME = 'PostgreSQL'.freeze
|
219
71
|
|
220
72
|
NATIVE_DATABASE_TYPES = {
|
221
|
-
:
|
222
|
-
:
|
223
|
-
:
|
224
|
-
:
|
225
|
-
:
|
226
|
-
:
|
227
|
-
:
|
228
|
-
:
|
229
|
-
:
|
230
|
-
:
|
231
|
-
:
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
73
|
+
primary_key: "serial primary key",
|
74
|
+
string: { name: "character varying" },
|
75
|
+
text: { name: "text" },
|
76
|
+
integer: { name: "integer" },
|
77
|
+
float: { name: "float" },
|
78
|
+
decimal: { name: "decimal" },
|
79
|
+
datetime: { name: "timestamp" },
|
80
|
+
time: { name: "time" },
|
81
|
+
date: { name: "date" },
|
82
|
+
daterange: { name: "daterange" },
|
83
|
+
numrange: { name: "numrange" },
|
84
|
+
tsrange: { name: "tsrange" },
|
85
|
+
tstzrange: { name: "tstzrange" },
|
86
|
+
int4range: { name: "int4range" },
|
87
|
+
int8range: { name: "int8range" },
|
88
|
+
binary: { name: "bytea" },
|
89
|
+
boolean: { name: "boolean" },
|
90
|
+
xml: { name: "xml" },
|
91
|
+
tsvector: { name: "tsvector" },
|
92
|
+
hstore: { name: "hstore" },
|
93
|
+
inet: { name: "inet" },
|
94
|
+
cidr: { name: "cidr" },
|
95
|
+
macaddr: { name: "macaddr" },
|
96
|
+
uuid: { name: "uuid" },
|
97
|
+
json: { name: "json" },
|
98
|
+
jsonb: { name: "jsonb" },
|
99
|
+
ltree: { name: "ltree" },
|
100
|
+
citext: { name: "citext" },
|
101
|
+
point: { name: "point" },
|
102
|
+
line: { name: "line" },
|
103
|
+
lseg: { name: "lseg" },
|
104
|
+
box: { name: "box" },
|
105
|
+
path: { name: "path" },
|
106
|
+
polygon: { name: "polygon" },
|
107
|
+
circle: { name: "circle" },
|
108
|
+
bit: { name: "bit" },
|
109
|
+
bit_varying: { name: "bit varying" },
|
110
|
+
money: { name: "money" },
|
235
111
|
}
|
236
112
|
|
237
|
-
|
238
|
-
|
239
|
-
|
113
|
+
OID = PostgreSQL::OID #:nodoc:
|
114
|
+
|
115
|
+
include PostgreSQL::Quoting
|
116
|
+
include PostgreSQL::ReferentialIntegrity
|
117
|
+
include PostgreSQL::SchemaStatements
|
118
|
+
include PostgreSQL::DatabaseStatements
|
119
|
+
include PostgreSQL::ColumnDumper
|
120
|
+
|
121
|
+
def schema_creation # :nodoc:
|
122
|
+
PostgreSQL::SchemaCreation.new self
|
240
123
|
end
|
241
124
|
|
242
|
-
|
125
|
+
def arel_visitor # :nodoc:
|
126
|
+
Arel::Visitors::PostgreSQL.new(self)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns true, since this connection adapter supports prepared statement
|
243
130
|
# caching.
|
244
131
|
def supports_statement_cache?
|
245
132
|
true
|
@@ -249,87 +136,99 @@ module ActiveRecord
|
|
249
136
|
true
|
250
137
|
end
|
251
138
|
|
139
|
+
def supports_partial_index?
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
def supports_expression_index?
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def supports_transaction_isolation?
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
def supports_foreign_keys?
|
152
|
+
true
|
153
|
+
end
|
154
|
+
|
155
|
+
def supports_views?
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
def supports_datetime_with_precision?
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
def supports_json?
|
164
|
+
postgresql_version >= 90200
|
165
|
+
end
|
166
|
+
|
167
|
+
def supports_comments?
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def supports_savepoints?
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
def index_algorithms
|
176
|
+
{ concurrently: 'CONCURRENTLY' }
|
177
|
+
end
|
178
|
+
|
252
179
|
class StatementPool < ConnectionAdapters::StatementPool
|
253
180
|
def initialize(connection, max)
|
254
|
-
super
|
181
|
+
super(max)
|
182
|
+
@connection = connection
|
255
183
|
@counter = 0
|
256
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
257
184
|
end
|
258
185
|
|
259
|
-
def each(&block); cache.each(&block); end
|
260
|
-
def key?(key); cache.key?(key); end
|
261
|
-
def [](key); cache[key]; end
|
262
|
-
def length; cache.length; end
|
263
|
-
|
264
186
|
def next_key
|
265
187
|
"a#{@counter + 1}"
|
266
188
|
end
|
267
189
|
|
268
190
|
def []=(sql, key)
|
269
|
-
|
270
|
-
dealloc(cache.shift.last)
|
271
|
-
end
|
272
|
-
@counter += 1
|
273
|
-
cache[sql] = key
|
274
|
-
end
|
275
|
-
|
276
|
-
def clear
|
277
|
-
cache.each_value do |stmt_key|
|
278
|
-
dealloc stmt_key
|
279
|
-
end
|
280
|
-
cache.clear
|
281
|
-
end
|
282
|
-
|
283
|
-
def delete(sql_key)
|
284
|
-
dealloc cache[sql_key]
|
285
|
-
cache.delete sql_key
|
191
|
+
super.tap { @counter += 1 }
|
286
192
|
end
|
287
193
|
|
288
194
|
private
|
289
|
-
def cache
|
290
|
-
@cache[$$]
|
291
|
-
end
|
292
|
-
|
293
|
-
def dealloc(key)
|
294
|
-
@connection.query "DEALLOCATE #{key}" if connection_active?
|
295
|
-
end
|
296
195
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
false
|
301
|
-
end
|
302
|
-
end
|
196
|
+
def dealloc(key)
|
197
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
198
|
+
end
|
303
199
|
|
304
|
-
|
305
|
-
|
200
|
+
def connection_active?
|
201
|
+
@connection.status == PGconn::CONNECTION_OK
|
202
|
+
rescue PGError
|
203
|
+
false
|
204
|
+
end
|
306
205
|
end
|
307
206
|
|
308
207
|
# Initializes and connects a PostgreSQL adapter.
|
309
208
|
def initialize(connection, logger, connection_parameters, config)
|
310
|
-
super(connection, logger)
|
311
|
-
|
312
|
-
if config.fetch(:prepared_statements) { true }
|
313
|
-
@visitor = Arel::Visitors::PostgreSQL.new self
|
314
|
-
else
|
315
|
-
@visitor = BindSubstitution.new self
|
316
|
-
end
|
209
|
+
super(connection, logger, config)
|
317
210
|
|
318
|
-
@connection_parameters
|
211
|
+
@connection_parameters = connection_parameters
|
319
212
|
|
320
213
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
321
214
|
@local_tz = nil
|
322
215
|
@table_alias_length = nil
|
323
216
|
|
324
217
|
connect
|
218
|
+
add_pg_encoders
|
325
219
|
@statements = StatementPool.new @connection,
|
326
|
-
|
220
|
+
self.class.type_cast_config_to_integer(config[:statement_limit])
|
327
221
|
|
328
|
-
if postgresql_version <
|
329
|
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old
|
222
|
+
if postgresql_version < 90100
|
223
|
+
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
330
224
|
end
|
331
225
|
|
226
|
+
add_pg_decoders
|
227
|
+
|
228
|
+
@type_map = Type::HashLookupTypeMap.new
|
229
|
+
initialize_type_map(type_map)
|
332
230
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
231
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
333
232
|
end
|
334
233
|
|
335
234
|
# Clears the prepared statements cache.
|
@@ -337,6 +236,10 @@ module ActiveRecord
|
|
337
236
|
@statements.clear
|
338
237
|
end
|
339
238
|
|
239
|
+
def truncate(table_name, name = nil)
|
240
|
+
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
241
|
+
end
|
242
|
+
|
340
243
|
# Is this connection alive and ready for queries?
|
341
244
|
def active?
|
342
245
|
@connection.query 'SELECT 1'
|
@@ -347,21 +250,25 @@ module ActiveRecord
|
|
347
250
|
|
348
251
|
# Close then reopen the connection.
|
349
252
|
def reconnect!
|
350
|
-
|
253
|
+
super
|
351
254
|
@connection.reset
|
352
|
-
@open_transactions = 0
|
353
255
|
configure_connection
|
354
256
|
end
|
355
257
|
|
356
258
|
def reset!
|
357
259
|
clear_cache!
|
358
|
-
|
260
|
+
reset_transaction
|
261
|
+
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
|
262
|
+
@connection.query 'ROLLBACK'
|
263
|
+
end
|
264
|
+
@connection.query 'DISCARD ALL'
|
265
|
+
configure_connection
|
359
266
|
end
|
360
267
|
|
361
268
|
# Disconnects from the database if already connected. Otherwise, this
|
362
269
|
# method does nothing.
|
363
270
|
def disconnect!
|
364
|
-
|
271
|
+
super
|
365
272
|
@connection.close rescue nil
|
366
273
|
end
|
367
274
|
|
@@ -379,818 +286,359 @@ module ActiveRecord
|
|
379
286
|
true
|
380
287
|
end
|
381
288
|
|
382
|
-
# Enable standard-conforming strings if available.
|
383
289
|
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
|
290
|
+
execute('SET standard_conforming_strings = on', 'SCHEMA')
|
392
291
|
end
|
393
292
|
|
394
293
|
def supports_ddl_transactions?
|
395
294
|
true
|
396
295
|
end
|
397
296
|
|
398
|
-
|
399
|
-
def supports_savepoints?
|
297
|
+
def supports_advisory_locks?
|
400
298
|
true
|
401
299
|
end
|
402
300
|
|
403
|
-
# Returns true.
|
404
301
|
def supports_explain?
|
405
302
|
true
|
406
303
|
end
|
407
304
|
|
408
|
-
|
409
|
-
|
410
|
-
@table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
|
305
|
+
def supports_extensions?
|
306
|
+
true
|
411
307
|
end
|
412
308
|
|
413
|
-
#
|
414
|
-
|
415
|
-
|
416
|
-
def escape_bytea(value)
|
417
|
-
@connection.escape_bytea(value) if value
|
309
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
310
|
+
def supports_ranges?
|
311
|
+
postgresql_version >= 90200
|
418
312
|
end
|
419
313
|
|
420
|
-
|
421
|
-
|
422
|
-
# on escaped binary output from database drive.
|
423
|
-
def unescape_bytea(value)
|
424
|
-
@connection.unescape_bytea(value) if value
|
314
|
+
def supports_materialized_views?
|
315
|
+
postgresql_version >= 90300
|
425
316
|
end
|
426
317
|
|
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
|
318
|
+
def get_advisory_lock(lock_id) # :nodoc:
|
319
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
320
|
+
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
|
453
321
|
end
|
322
|
+
select_value("SELECT pg_try_advisory_lock(#{lock_id});")
|
454
323
|
end
|
455
324
|
|
456
|
-
def
|
457
|
-
|
458
|
-
|
459
|
-
case value
|
460
|
-
when String
|
461
|
-
return super unless 'bytea' == column.sql_type
|
462
|
-
{ :value => value, :format => 1 }
|
463
|
-
else
|
464
|
-
super
|
325
|
+
def release_advisory_lock(lock_id) # :nodoc:
|
326
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
327
|
+
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
|
465
328
|
end
|
329
|
+
select_value("SELECT pg_advisory_unlock(#{lock_id})")
|
466
330
|
end
|
467
331
|
|
468
|
-
|
469
|
-
|
470
|
-
|
332
|
+
def enable_extension(name)
|
333
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
334
|
+
reload_type_map
|
335
|
+
}
|
471
336
|
end
|
472
337
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
# - schema_name.table_name
|
478
|
-
# - schema_name."table.name"
|
479
|
-
# - "schema.name".table_name
|
480
|
-
# - "schema.name"."table.name"
|
481
|
-
def quote_table_name(name)
|
482
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
483
|
-
|
484
|
-
unless name_part
|
485
|
-
quote_column_name(schema)
|
486
|
-
else
|
487
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
488
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
489
|
-
end
|
338
|
+
def disable_extension(name)
|
339
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
340
|
+
reload_type_map
|
341
|
+
}
|
490
342
|
end
|
491
343
|
|
492
|
-
|
493
|
-
|
494
|
-
|
344
|
+
def extension_enabled?(name)
|
345
|
+
if supports_extensions?
|
346
|
+
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
|
347
|
+
'SCHEMA'
|
348
|
+
res.cast_values.first
|
349
|
+
end
|
495
350
|
end
|
496
351
|
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
501
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
352
|
+
def extensions
|
353
|
+
if supports_extensions?
|
354
|
+
exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
|
502
355
|
else
|
503
356
|
super
|
504
357
|
end
|
505
358
|
end
|
506
359
|
|
360
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
361
|
+
def table_alias_length
|
362
|
+
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
|
363
|
+
end
|
364
|
+
|
507
365
|
# Set the authorized user for this session
|
508
366
|
def session_auth=(user)
|
509
367
|
clear_cache!
|
510
368
|
exec_query "SET SESSION AUTHORIZATION #{user}"
|
511
369
|
end
|
512
370
|
|
513
|
-
|
514
|
-
|
515
|
-
def supports_disable_referential_integrity? #:nodoc:
|
516
|
-
true
|
371
|
+
def use_insert_returning?
|
372
|
+
@use_insert_returning
|
517
373
|
end
|
518
374
|
|
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
|
375
|
+
def valid_type?(type)
|
376
|
+
!native_database_types[type].nil?
|
528
377
|
end
|
529
378
|
|
530
|
-
|
531
|
-
|
532
|
-
def explain(arel, binds = [])
|
533
|
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
534
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
379
|
+
def update_table_definition(table_name, base) #:nodoc:
|
380
|
+
PostgreSQL::Table.new(table_name, base)
|
535
381
|
end
|
536
382
|
|
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
|
383
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
384
|
+
oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
|
385
|
+
super(oid)
|
572
386
|
end
|
573
387
|
|
574
|
-
|
575
|
-
|
576
|
-
def select_rows(sql, name = nil)
|
577
|
-
select_raw(sql, name).last
|
388
|
+
def column_name_for_operation(operation, node) # :nodoc:
|
389
|
+
OPERATION_ALIASES.fetch(operation) { operation.downcase }
|
578
390
|
end
|
579
391
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
pk = primary_key(table_ref) if table_ref
|
586
|
-
end
|
392
|
+
OPERATION_ALIASES = { # :nodoc:
|
393
|
+
"maximum" => "max",
|
394
|
+
"minimum" => "min",
|
395
|
+
"average" => "avg",
|
396
|
+
}
|
587
397
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
super
|
592
|
-
end
|
398
|
+
# Returns the version of the connected PostgreSQL server.
|
399
|
+
def postgresql_version
|
400
|
+
@connection.server_version
|
593
401
|
end
|
594
|
-
alias :create :insert
|
595
402
|
|
596
|
-
|
597
|
-
def result_as_array(res) #:nodoc:
|
598
|
-
# check if we have any binary column and if they need escaping
|
599
|
-
ftypes = Array.new(res.nfields) do |i|
|
600
|
-
[i, res.ftype(i)]
|
601
|
-
end
|
602
|
-
|
603
|
-
rows = res.values
|
604
|
-
return rows unless ftypes.any? { |_, x|
|
605
|
-
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
606
|
-
}
|
403
|
+
protected
|
607
404
|
|
608
|
-
|
609
|
-
|
610
|
-
|
405
|
+
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
406
|
+
VALUE_LIMIT_VIOLATION = "22001"
|
407
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
408
|
+
UNIQUE_VIOLATION = "23505"
|
611
409
|
|
612
|
-
|
613
|
-
|
614
|
-
binaries.each do |index, _|
|
615
|
-
row[index] = unescape_bytea(row[index])
|
616
|
-
end
|
410
|
+
def translate_exception(exception, message)
|
411
|
+
return exception unless exception.respond_to?(:result)
|
617
412
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
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
|
413
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
414
|
+
when UNIQUE_VIOLATION
|
415
|
+
RecordNotUnique.new(message)
|
416
|
+
when FOREIGN_KEY_VIOLATION
|
417
|
+
InvalidForeignKey.new(message)
|
418
|
+
when VALUE_LIMIT_VIOLATION
|
419
|
+
ValueTooLong.new(message)
|
420
|
+
else
|
421
|
+
super
|
634
422
|
end
|
635
423
|
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
424
|
|
646
|
-
|
647
|
-
# or raising a PGError exception otherwise.
|
648
|
-
def execute(sql, name = nil)
|
649
|
-
log(sql, name) do
|
650
|
-
@connection.async_exec(sql)
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
def substitute_at(column, index)
|
655
|
-
Arel::Nodes::BindParam.new "$#{index + 1}"
|
656
|
-
end
|
657
|
-
|
658
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
659
|
-
log(sql, name, binds) do
|
660
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
661
|
-
exec_cache(sql, binds)
|
425
|
+
private
|
662
426
|
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
end
|
427
|
+
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
|
428
|
+
if !type_map.key?(oid)
|
429
|
+
load_additional_types(type_map, [oid])
|
430
|
+
end
|
668
431
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
432
|
+
type_map.fetch(oid, fmod, sql_type) {
|
433
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
434
|
+
Type::Value.new.tap do |cast_type|
|
435
|
+
type_map.register_type(oid, cast_type)
|
436
|
+
end
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
def initialize_type_map(m) # :nodoc:
|
441
|
+
register_class_with_limit m, 'int2', Type::Integer
|
442
|
+
register_class_with_limit m, 'int4', Type::Integer
|
443
|
+
register_class_with_limit m, 'int8', Type::Integer
|
444
|
+
m.alias_type 'oid', 'int2'
|
445
|
+
m.register_type 'float4', Type::Float.new
|
446
|
+
m.alias_type 'float8', 'float4'
|
447
|
+
m.register_type 'text', Type::Text.new
|
448
|
+
register_class_with_limit m, 'varchar', Type::String
|
449
|
+
m.alias_type 'char', 'varchar'
|
450
|
+
m.alias_type 'name', 'varchar'
|
451
|
+
m.alias_type 'bpchar', 'varchar'
|
452
|
+
m.register_type 'bool', Type::Boolean.new
|
453
|
+
register_class_with_limit m, 'bit', OID::Bit
|
454
|
+
register_class_with_limit m, 'varbit', OID::BitVarying
|
455
|
+
m.alias_type 'timestamptz', 'timestamp'
|
456
|
+
m.register_type 'date', Type::Date.new
|
457
|
+
|
458
|
+
m.register_type 'money', OID::Money.new
|
459
|
+
m.register_type 'bytea', OID::Bytea.new
|
460
|
+
m.register_type 'point', OID::Point.new
|
461
|
+
m.register_type 'hstore', OID::Hstore.new
|
462
|
+
m.register_type 'json', OID::Json.new
|
463
|
+
m.register_type 'jsonb', OID::Jsonb.new
|
464
|
+
m.register_type 'cidr', OID::Cidr.new
|
465
|
+
m.register_type 'inet', OID::Inet.new
|
466
|
+
m.register_type 'uuid', OID::Uuid.new
|
467
|
+
m.register_type 'xml', OID::Xml.new
|
468
|
+
m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
|
469
|
+
m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
|
470
|
+
m.register_type 'citext', OID::SpecializedString.new(:citext)
|
471
|
+
m.register_type 'ltree', OID::SpecializedString.new(:ltree)
|
472
|
+
m.register_type 'line', OID::SpecializedString.new(:line)
|
473
|
+
m.register_type 'lseg', OID::SpecializedString.new(:lseg)
|
474
|
+
m.register_type 'box', OID::SpecializedString.new(:box)
|
475
|
+
m.register_type 'path', OID::SpecializedString.new(:path)
|
476
|
+
m.register_type 'polygon', OID::SpecializedString.new(:polygon)
|
477
|
+
m.register_type 'circle', OID::SpecializedString.new(:circle)
|
478
|
+
|
479
|
+
# FIXME: why are we keeping these types as strings?
|
480
|
+
m.alias_type 'interval', 'varchar'
|
481
|
+
|
482
|
+
register_class_with_precision m, 'time', Type::Time
|
483
|
+
register_class_with_precision m, 'timestamp', OID::DateTime
|
484
|
+
|
485
|
+
m.register_type 'numeric' do |_, fmod, sql_type|
|
486
|
+
precision = extract_precision(sql_type)
|
487
|
+
scale = extract_scale(sql_type)
|
488
|
+
|
489
|
+
# The type for the numeric depends on the width of the field,
|
490
|
+
# so we'll do something special here.
|
491
|
+
#
|
492
|
+
# When dealing with decimal columns:
|
493
|
+
#
|
494
|
+
# places after decimal = fmod - 4 & 0xffff
|
495
|
+
# places before decimal = (fmod - 4) >> 16 & 0xffff
|
496
|
+
if fmod && (fmod - 4 & 0xffff).zero?
|
497
|
+
# FIXME: Remove this class, and the second argument to
|
498
|
+
# lookups on PG
|
499
|
+
Type::DecimalWithoutScale.new(precision: precision)
|
500
|
+
else
|
501
|
+
OID::Decimal.new(precision: precision, scale: scale)
|
502
|
+
end
|
503
|
+
end
|
679
504
|
|
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
|
505
|
+
load_additional_types(m)
|
685
506
|
end
|
686
507
|
|
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}"
|
508
|
+
def extract_limit(sql_type) # :nodoc:
|
509
|
+
case sql_type
|
510
|
+
when /^bigint/i, /^int8/i
|
511
|
+
8
|
512
|
+
when /^smallint/i
|
513
|
+
2
|
759
514
|
else
|
760
|
-
|
515
|
+
super
|
761
516
|
end
|
762
517
|
end
|
763
518
|
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
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
|
519
|
+
# Extracts the value from a PostgreSQL column default definition.
|
520
|
+
def extract_value_from_default(default) # :nodoc:
|
521
|
+
case default
|
522
|
+
# Quoted types
|
523
|
+
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
524
|
+
# The default 'now'::date is CURRENT_DATE
|
525
|
+
if $1 == "now".freeze && $2 == "date".freeze
|
526
|
+
nil
|
527
|
+
else
|
528
|
+
$1.gsub("''".freeze, "'".freeze)
|
529
|
+
end
|
530
|
+
# Boolean types
|
531
|
+
when 'true'.freeze, 'false'.freeze
|
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
|
-
|
1022
|
-
execute add_column_sql
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
# Changes the column of a table.
|
1026
|
-
def change_column(table_name, column_name, type, options = {})
|
1027
|
-
clear_cache!
|
1028
|
-
quoted_table_name = quote_table_name(table_name)
|
1029
|
-
|
1030
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1031
549
|
|
1032
|
-
|
1033
|
-
|
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+\(.*\)|\(.*\)::\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
|
-
|
1056
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
1057
|
-
execute "DROP INDEX #{quote_table_name(index_name)}"
|
1058
|
-
end
|
1059
553
|
|
1060
|
-
|
1061
|
-
|
1062
|
-
end
|
1063
|
-
|
1064
|
-
def index_name_length
|
1065
|
-
63
|
1066
|
-
end
|
554
|
+
def load_additional_types(type_map, oids = nil) # :nodoc:
|
555
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
1067
556
|
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
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
|
-
|
1099
|
-
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
1100
|
-
#
|
1101
|
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
1102
|
-
# requires that the ORDER BY include the distinct column.
|
1103
|
-
#
|
1104
|
-
# distinct("posts.id", "posts.created_at desc")
|
1105
|
-
def distinct(columns, orders) #:nodoc:
|
1106
|
-
return "DISTINCT #{columns}" if orders.empty?
|
1107
|
-
|
1108
|
-
# Construct a clean list of column names from the ORDER BY clause, removing
|
1109
|
-
# any ASC/DESC modifiers
|
1110
|
-
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
|
1111
|
-
order_columns.delete_if { |c| c.blank? }
|
1112
|
-
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
1113
|
-
|
1114
|
-
"DISTINCT #{columns}, #{order_columns * ', '}"
|
1115
|
-
end
|
1116
580
|
|
1117
|
-
|
1118
|
-
extend self
|
581
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
1119
582
|
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
1132
|
-
[schema, table]
|
583
|
+
def execute_and_clear(sql, name, binds, prepare: false)
|
584
|
+
if without_prepared_statement?(binds)
|
585
|
+
result = exec_no_cache(sql, name, [])
|
586
|
+
elsif !prepare
|
587
|
+
result = exec_no_cache(sql, name, binds)
|
588
|
+
else
|
589
|
+
result = exec_cache(sql, name, binds)
|
590
|
+
end
|
591
|
+
ret = yield result
|
592
|
+
result.clear
|
593
|
+
ret
|
1133
594
|
end
|
1134
|
-
end
|
1135
595
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
@connection.server_version
|
596
|
+
def exec_no_cache(sql, name, binds)
|
597
|
+
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
598
|
+
log(sql, name, binds) { @connection.async_exec(sql, type_casted_binds) }
|
1140
599
|
end
|
1141
600
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
601
|
+
def exec_cache(sql, name, binds)
|
602
|
+
stmt_key = prepare_statement(sql)
|
603
|
+
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
1145
604
|
|
1146
|
-
|
1147
|
-
|
605
|
+
log(sql, name, binds, stmt_key) do
|
606
|
+
@connection.exec_prepared(stmt_key, type_casted_binds)
|
607
|
+
end
|
608
|
+
rescue ActiveRecord::StatementInvalid => e
|
609
|
+
raise unless is_cached_plan_failure?(e)
|
1148
610
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
InvalidForeignKey.new(message, exception)
|
611
|
+
# Nothing we can do if we are in a transaction because all commands
|
612
|
+
# will raise InFailedSQLTransaction
|
613
|
+
if in_transaction?
|
614
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
1154
615
|
else
|
1155
|
-
|
616
|
+
# outside of transactions we can simply flush this query and retry
|
617
|
+
@statements.delete sql_key(sql)
|
618
|
+
retry
|
1156
619
|
end
|
1157
620
|
end
|
1158
621
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
622
|
+
# Annoyingly, the code for prepared statements whose return value may
|
623
|
+
# have changed is FEATURE_NOT_SUPPORTED.
|
624
|
+
#
|
625
|
+
# This covers various different error types so we need to do additional
|
626
|
+
# work to classify the exception definitively as a
|
627
|
+
# ActiveRecord::PreparedStatementCacheExpired
|
628
|
+
#
|
629
|
+
# Check here for more details:
|
630
|
+
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
631
|
+
CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze
|
632
|
+
def is_cached_plan_failure?(e)
|
633
|
+
pgerror = e.cause
|
634
|
+
code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
635
|
+
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
|
636
|
+
rescue
|
637
|
+
false
|
1164
638
|
end
|
1165
639
|
|
1166
|
-
def
|
1167
|
-
|
1168
|
-
stmt_key = prepare_statement sql
|
1169
|
-
|
1170
|
-
# Clear the queue
|
1171
|
-
@connection.get_last_result
|
1172
|
-
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
|
1173
|
-
type_cast(val, col)
|
1174
|
-
})
|
1175
|
-
@connection.block
|
1176
|
-
@connection.get_last_result
|
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
|
1193
|
-
end
|
640
|
+
def in_transaction?
|
641
|
+
open_transactions > 0
|
1194
642
|
end
|
1195
643
|
|
1196
644
|
# Returns the statement identifier for the client side cache
|
@@ -1205,28 +653,29 @@ module ActiveRecord
|
|
1205
653
|
sql_key = sql_key(sql)
|
1206
654
|
unless @statements.key? sql_key
|
1207
655
|
nextkey = @statements.next_key
|
1208
|
-
|
656
|
+
begin
|
657
|
+
@connection.prepare nextkey, sql
|
658
|
+
rescue => e
|
659
|
+
raise translate_exception_class(e, sql)
|
660
|
+
end
|
661
|
+
# Clear the queue
|
662
|
+
@connection.get_last_result
|
1209
663
|
@statements[sql_key] = nextkey
|
1210
664
|
end
|
1211
665
|
@statements[sql_key]
|
1212
666
|
end
|
1213
667
|
|
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
668
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1220
669
|
# connected server's characteristics.
|
1221
670
|
def connect
|
1222
|
-
@connection = PGconn.connect(
|
1223
|
-
|
1224
|
-
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
1225
|
-
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
1226
|
-
# should know about this but can't detect it there, so deal with it here.
|
1227
|
-
PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
|
1228
|
-
|
671
|
+
@connection = PGconn.connect(@connection_parameters)
|
1229
672
|
configure_connection
|
673
|
+
rescue ::PG::Error => error
|
674
|
+
if error.message.include?("does not exist")
|
675
|
+
raise ActiveRecord::NoDatabaseError
|
676
|
+
else
|
677
|
+
raise
|
678
|
+
end
|
1230
679
|
end
|
1231
680
|
|
1232
681
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -1235,45 +684,43 @@ module ActiveRecord
|
|
1235
684
|
if @config[:encoding]
|
1236
685
|
@connection.set_client_encoding(@config[:encoding])
|
1237
686
|
end
|
1238
|
-
self.client_min_messages = @config[:min_messages]
|
687
|
+
self.client_min_messages = @config[:min_messages] || 'warning'
|
1239
688
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1240
689
|
|
1241
|
-
# Use standard-conforming strings
|
690
|
+
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
1242
691
|
set_standard_conforming_strings
|
1243
692
|
|
1244
693
|
# If using Active Record's time zone support configure the connection to return
|
1245
694
|
# TIMESTAMP WITH ZONE types in UTC.
|
695
|
+
# (SET TIME ZONE does not use an equals sign like other SET variables)
|
1246
696
|
if ActiveRecord::Base.default_timezone == :utc
|
1247
697
|
execute("SET time zone 'UTC'", 'SCHEMA')
|
1248
698
|
elsif @local_tz
|
1249
699
|
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
|
1250
700
|
end
|
1251
|
-
end
|
1252
|
-
|
1253
|
-
# Returns the current ID of a table's sequence.
|
1254
|
-
def last_insert_id(sequence_name) #:nodoc:
|
1255
|
-
r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1256
|
-
Integer(r.rows.first.first)
|
1257
|
-
end
|
1258
701
|
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
702
|
+
# SET statements from :variables config hash
|
703
|
+
# http://www.postgresql.org/docs/current/static/sql-set.html
|
704
|
+
variables = @config[:variables] || {}
|
705
|
+
variables.map do |k, v|
|
706
|
+
if v == ':default' || v == :default
|
707
|
+
# Sets the value to the global or compile default
|
708
|
+
execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
|
709
|
+
elsif !v.nil?
|
710
|
+
execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
|
711
|
+
end
|
712
|
+
end
|
1263
713
|
end
|
1264
714
|
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
fields = res.fields
|
1269
|
-
res.clear
|
1270
|
-
return fields, results
|
715
|
+
# Returns the current ID of a table's sequence.
|
716
|
+
def last_insert_id_result(sequence_name) # :nodoc:
|
717
|
+
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1271
718
|
end
|
1272
719
|
|
1273
720
|
# Returns the list of a table's column names, data types, and default values.
|
1274
721
|
#
|
1275
722
|
# The underlying query is roughly:
|
1276
|
-
# SELECT column.name, column.type, default.value
|
723
|
+
# SELECT column.name, column.type, default.value, column.comment
|
1277
724
|
# FROM column LEFT JOIN default
|
1278
725
|
# ON column.table_id = default.table_id
|
1279
726
|
# AND column.num = default.column_num
|
@@ -1288,36 +735,108 @@ module ActiveRecord
|
|
1288
735
|
# Query implementation notes:
|
1289
736
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1290
737
|
# - ::regclass is a function that gives the id for a table name
|
1291
|
-
def column_definitions(table_name)
|
1292
|
-
|
1293
|
-
|
1294
|
-
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
738
|
+
def column_definitions(table_name) # :nodoc:
|
739
|
+
query(<<-end_sql, 'SCHEMA')
|
740
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
741
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
742
|
+
(SELECT c.collname FROM pg_collation c, pg_type t
|
743
|
+
WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
|
744
|
+
col_description(a.attrelid, a.attnum) AS comment
|
745
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
746
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
747
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
748
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
749
|
+
ORDER BY a.attnum
|
1300
750
|
end_sql
|
1301
751
|
end
|
1302
752
|
|
1303
|
-
def
|
1304
|
-
|
753
|
+
def extract_table_ref_from_insert_sql(sql) # :nodoc:
|
754
|
+
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
755
|
+
$1.strip if $1
|
756
|
+
end
|
1305
757
|
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
758
|
+
def create_table_definition(*args) # :nodoc:
|
759
|
+
PostgreSQL::TableDefinition.new(*args)
|
760
|
+
end
|
761
|
+
|
762
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
763
|
+
@case_insensitive_cache ||= {}
|
764
|
+
@case_insensitive_cache[column.sql_type] ||= begin
|
765
|
+
sql = <<-end_sql
|
766
|
+
SELECT exists(
|
767
|
+
SELECT * FROM pg_proc
|
768
|
+
INNER JOIN pg_cast
|
769
|
+
ON casttarget::text::oidvector = proargtypes
|
770
|
+
WHERE proname = 'lower'
|
771
|
+
AND castsource = '#{column.sql_type}'::regtype::oid
|
772
|
+
)
|
773
|
+
end_sql
|
774
|
+
execute_and_clear(sql, "SCHEMA", []) do |result|
|
775
|
+
result.getvalue(0, 0)
|
776
|
+
end
|
1310
777
|
end
|
1311
778
|
end
|
1312
779
|
|
1313
|
-
def
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
780
|
+
def add_pg_encoders
|
781
|
+
map = PG::TypeMapByClass.new
|
782
|
+
map[Integer] = PG::TextEncoder::Integer.new
|
783
|
+
map[TrueClass] = PG::TextEncoder::Boolean.new
|
784
|
+
map[FalseClass] = PG::TextEncoder::Boolean.new
|
785
|
+
map[Float] = PG::TextEncoder::Float.new
|
786
|
+
@connection.type_map_for_queries = map
|
787
|
+
end
|
788
|
+
|
789
|
+
def add_pg_decoders
|
790
|
+
coders_by_name = {
|
791
|
+
'int2' => PG::TextDecoder::Integer,
|
792
|
+
'int4' => PG::TextDecoder::Integer,
|
793
|
+
'int8' => PG::TextDecoder::Integer,
|
794
|
+
'oid' => PG::TextDecoder::Integer,
|
795
|
+
'float4' => PG::TextDecoder::Float,
|
796
|
+
'float8' => PG::TextDecoder::Float,
|
797
|
+
'bool' => PG::TextDecoder::Boolean,
|
798
|
+
}
|
799
|
+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
800
|
+
query = <<-SQL % known_coder_types.join(", ")
|
801
|
+
SELECT t.oid, t.typname
|
802
|
+
FROM pg_type as t
|
803
|
+
WHERE t.typname IN (%s)
|
804
|
+
SQL
|
805
|
+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
806
|
+
result
|
807
|
+
.map { |row| construct_coder(row, coders_by_name[row['typname']]) }
|
808
|
+
.compact
|
809
|
+
end
|
1317
810
|
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
811
|
+
map = PG::TypeMapByOid.new
|
812
|
+
coders.each { |coder| map.add_coder(coder) }
|
813
|
+
@connection.type_map_for_results = map
|
814
|
+
end
|
815
|
+
|
816
|
+
def construct_coder(row, coder_class)
|
817
|
+
return unless coder_class
|
818
|
+
coder_class.new(oid: row['oid'].to_i, name: row['typname'])
|
819
|
+
end
|
820
|
+
|
821
|
+
ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
|
822
|
+
ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
|
823
|
+
ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
|
824
|
+
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
|
825
|
+
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
|
826
|
+
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
|
827
|
+
ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
|
828
|
+
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
|
829
|
+
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
|
830
|
+
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
|
831
|
+
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
|
832
|
+
ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
|
833
|
+
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
|
834
|
+
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
|
835
|
+
ActiveRecord::Type.register(:point, OID::Rails51Point, adapter: :postgresql)
|
836
|
+
ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql)
|
837
|
+
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
|
838
|
+
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
|
839
|
+
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
|
1321
840
|
end
|
1322
841
|
end
|
1323
842
|
end
|