activerecord 3.2.22.5 → 5.2.8
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 +5 -5
- data/CHANGELOG.md +657 -621
- data/MIT-LICENSE +2 -2
- data/README.rdoc +41 -46
- data/examples/performance.rb +55 -42
- data/examples/simple.rb +6 -5
- data/lib/active_record/aggregations.rb +264 -236
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -42
- data/lib/active_record/associations/association.rb +127 -75
- data/lib/active_record/associations/association_scope.rb +126 -92
- data/lib/active_record/associations/belongs_to_association.rb +78 -27
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
- data/lib/active_record/associations/builder/association.rb +117 -32
- data/lib/active_record/associations/builder/belongs_to.rb +135 -60
- data/lib/active_record/associations/builder/collection_association.rb +61 -54
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
- data/lib/active_record/associations/builder/has_many.rb +10 -64
- data/lib/active_record/associations/builder/has_one.rb +19 -51
- data/lib/active_record/associations/builder/singular_association.rb +28 -18
- data/lib/active_record/associations/collection_association.rb +226 -293
- data/lib/active_record/associations/collection_proxy.rb +1067 -69
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +83 -47
- data/lib/active_record/associations/has_many_through_association.rb +98 -65
- data/lib/active_record/associations/has_one_association.rb +57 -20
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
- data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
- data/lib/active_record/associations/join_dependency.rb +212 -164
- data/lib/active_record/associations/preloader/association.rb +95 -89
- data/lib/active_record/associations/preloader/through_association.rb +84 -44
- data/lib/active_record/associations/preloader.rb +123 -111
- data/lib/active_record/associations/singular_association.rb +33 -24
- data/lib/active_record/associations/through_association.rb +60 -26
- data/lib/active_record/associations.rb +1759 -1506
- data/lib/active_record/attribute_assignment.rb +60 -193
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
- data/lib/active_record/attribute_methods/dirty.rb +113 -74
- data/lib/active_record/attribute_methods/primary_key.rb +106 -77
- data/lib/active_record/attribute_methods/query.rb +8 -5
- data/lib/active_record/attribute_methods/read.rb +63 -114
- data/lib/active_record/attribute_methods/serialization.rb +60 -90
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
- data/lib/active_record/attribute_methods/write.rb +43 -45
- data/lib/active_record/attribute_methods.rb +366 -149
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +312 -225
- data/lib/active_record/base.rb +114 -505
- data/lib/active_record/callbacks.rb +145 -67
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +32 -23
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
- data/lib/active_record/connection_adapters/column.rb +50 -255
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
- data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +200 -105
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +107 -69
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +245 -60
- data/lib/active_record/explain.rb +35 -71
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +18 -9
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +418 -275
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +209 -100
- data/lib/active_record/integration.rb +116 -21
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +107 -94
- data/lib/active_record/locking/pessimistic.rb +20 -8
- data/lib/active_record/log_subscriber.rb +99 -34
- data/lib/active_record/migration/command_recorder.rb +199 -64
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/migration.rb +893 -296
- data/lib/active_record/model_schema.rb +328 -175
- data/lib/active_record/nested_attributes.rb +338 -242
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +557 -170
- data/lib/active_record/query_cache.rb +14 -43
- data/lib/active_record/querying.rb +36 -24
- data/lib/active_record/railtie.rb +147 -52
- data/lib/active_record/railties/console_sandbox.rb +5 -4
- data/lib/active_record/railties/controller_runtime.rb +13 -6
- data/lib/active_record/railties/databases.rake +206 -488
- data/lib/active_record/readonly_attributes.rb +4 -6
- data/lib/active_record/reflection.rb +734 -228
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +249 -52
- data/lib/active_record/relation/calculations.rb +330 -284
- data/lib/active_record/relation/delegation.rb +135 -37
- data/lib/active_record/relation/finder_methods.rb +450 -287
- data/lib/active_record/relation/from_clause.rb +26 -0
- 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/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder.rb +132 -43
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1037 -221
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +48 -151
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +451 -359
- data/lib/active_record/result.rb +129 -20
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +164 -136
- data/lib/active_record/schema.rb +31 -19
- data/lib/active_record/schema_dumper.rb +154 -107
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping/default.rb +108 -98
- data/lib/active_record/scoping/named.rb +125 -112
- data/lib/active_record/scoping.rb +77 -123
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +10 -6
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +175 -16
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +80 -41
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +240 -119
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +35 -18
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +133 -75
- data/lib/active_record/validations.rb +53 -43
- data/lib/active_record/version.rb +7 -7
- data/lib/active_record.rb +89 -57
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/migration.rb +28 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
- data/lib/rails/generators/active_record.rb +10 -16
- metadata +141 -62
- 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/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- 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/railties/jdbcmysql_error.rb +0 -16
- 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/migration/templates/migration.rb +0 -34
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
- 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,1196 +1,663 @@
|
|
1
|
-
|
2
|
-
require 'active_support/core_ext/object/blank'
|
3
|
-
require 'active_record/connection_adapters/statement_pool'
|
4
|
-
require 'arel/visitors/bind_visitor'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
# Make sure we're using pg high enough for
|
7
|
-
gem
|
8
|
-
require
|
3
|
+
# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
|
4
|
+
gem "pg", ">= 0.18", "< 2.0"
|
5
|
+
require "pg"
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
host = config[:host]
|
16
|
-
port = config[:port] || 5432
|
17
|
-
username = config[:username].to_s if config[:username]
|
18
|
-
password = config[:password].to_s if config[:password]
|
19
|
-
|
20
|
-
if config.key?(:database)
|
21
|
-
database = config[:database]
|
22
|
-
else
|
23
|
-
raise ArgumentError, "No database specified. Missing argument: database."
|
24
|
-
end
|
25
|
-
|
26
|
-
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
27
|
-
# so just pass a nil connection object for the time being.
|
28
|
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
|
29
|
-
end
|
7
|
+
# Use async_exec instead of exec_params on pg versions before 1.1
|
8
|
+
class ::PG::Connection
|
9
|
+
unless self.public_method_defined?(:async_exec_params)
|
10
|
+
remove_method :exec_params
|
11
|
+
alias exec_params async_exec
|
30
12
|
end
|
13
|
+
end
|
31
14
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
15
|
+
require "active_record/connection_adapters/abstract_adapter"
|
16
|
+
require "active_record/connection_adapters/statement_pool"
|
17
|
+
require "active_record/connection_adapters/postgresql/column"
|
18
|
+
require "active_record/connection_adapters/postgresql/database_statements"
|
19
|
+
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
|
20
|
+
require "active_record/connection_adapters/postgresql/oid"
|
21
|
+
require "active_record/connection_adapters/postgresql/quoting"
|
22
|
+
require "active_record/connection_adapters/postgresql/referential_integrity"
|
23
|
+
require "active_record/connection_adapters/postgresql/schema_creation"
|
24
|
+
require "active_record/connection_adapters/postgresql/schema_definitions"
|
25
|
+
require "active_record/connection_adapters/postgresql/schema_dumper"
|
26
|
+
require "active_record/connection_adapters/postgresql/schema_statements"
|
27
|
+
require "active_record/connection_adapters/postgresql/type_metadata"
|
28
|
+
require "active_record/connection_adapters/postgresql/utils"
|
39
29
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
30
|
+
module ActiveRecord
|
31
|
+
module ConnectionHandling # :nodoc:
|
32
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
33
|
+
def postgresql_connection(config)
|
34
|
+
conn_params = config.symbolize_keys
|
45
35
|
|
46
|
-
|
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:
|
36
|
+
conn_params.delete_if { |_, v| v.nil? }
|
55
37
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
when /^bigint/i; 8
|
60
|
-
when /^smallint/i; 2
|
61
|
-
else super
|
62
|
-
end
|
63
|
-
end
|
38
|
+
# Map ActiveRecords param names to PGs.
|
39
|
+
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
40
|
+
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
64
41
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
sql_type =~ /^money/ ? 2 : super
|
69
|
-
end
|
42
|
+
# Forward only valid config params to PG::Connection.connect.
|
43
|
+
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
44
|
+
conn_params.slice!(*valid_conn_param_keys)
|
70
45
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
46
|
+
# The postgres drivers don't allow the creation of an unconnected PG::Connection object,
|
47
|
+
# so just pass a nil connection object for the time being.
|
48
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
187
49
|
end
|
50
|
+
end
|
188
51
|
|
189
|
-
|
190
|
-
#
|
52
|
+
module ConnectionAdapters
|
53
|
+
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
|
191
54
|
#
|
192
55
|
# Options:
|
193
56
|
#
|
194
|
-
# * <tt>:host</tt> - Defaults to
|
57
|
+
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
|
58
|
+
# the default is to connect to localhost.
|
195
59
|
# * <tt>:port</tt> - Defaults to 5432.
|
196
|
-
# * <tt>:username</tt> - Defaults to
|
197
|
-
# * <tt>:password</tt> -
|
198
|
-
# * <tt>:database</tt> -
|
60
|
+
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
|
61
|
+
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
|
62
|
+
# * <tt>:database</tt> - Defaults to be the same as the user name.
|
199
63
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
200
64
|
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
201
65
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
202
66
|
# <encoding></tt> call on the connection.
|
203
67
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
204
68
|
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
69
|
+
# * <tt>:variables</tt> - An optional hash of additional parameters that
|
70
|
+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
|
71
|
+
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
|
72
|
+
# defaults to true.
|
73
|
+
#
|
74
|
+
# Any further options are used as connection parameters to libpq. See
|
75
|
+
# https://www.postgresql.org/docs/current/static/libpq-connect.html for the
|
76
|
+
# list of parameters.
|
77
|
+
#
|
78
|
+
# In addition, default connection parameters of libpq can be set per environment variables.
|
79
|
+
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
|
205
80
|
class PostgreSQLAdapter < AbstractAdapter
|
206
|
-
|
207
|
-
def xml(*args)
|
208
|
-
options = args.extract_options!
|
209
|
-
column(args[0], 'xml', options)
|
210
|
-
end
|
81
|
+
ADAPTER_NAME = "PostgreSQL".freeze
|
211
82
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
83
|
+
NATIVE_DATABASE_TYPES = {
|
84
|
+
primary_key: "bigserial primary key",
|
85
|
+
string: { name: "character varying" },
|
86
|
+
text: { name: "text" },
|
87
|
+
integer: { name: "integer", limit: 4 },
|
88
|
+
float: { name: "float" },
|
89
|
+
decimal: { name: "decimal" },
|
90
|
+
datetime: { name: "timestamp" },
|
91
|
+
time: { name: "time" },
|
92
|
+
date: { name: "date" },
|
93
|
+
daterange: { name: "daterange" },
|
94
|
+
numrange: { name: "numrange" },
|
95
|
+
tsrange: { name: "tsrange" },
|
96
|
+
tstzrange: { name: "tstzrange" },
|
97
|
+
int4range: { name: "int4range" },
|
98
|
+
int8range: { name: "int8range" },
|
99
|
+
binary: { name: "bytea" },
|
100
|
+
boolean: { name: "boolean" },
|
101
|
+
xml: { name: "xml" },
|
102
|
+
tsvector: { name: "tsvector" },
|
103
|
+
hstore: { name: "hstore" },
|
104
|
+
inet: { name: "inet" },
|
105
|
+
cidr: { name: "cidr" },
|
106
|
+
macaddr: { name: "macaddr" },
|
107
|
+
uuid: { name: "uuid" },
|
108
|
+
json: { name: "json" },
|
109
|
+
jsonb: { name: "jsonb" },
|
110
|
+
ltree: { name: "ltree" },
|
111
|
+
citext: { name: "citext" },
|
112
|
+
point: { name: "point" },
|
113
|
+
line: { name: "line" },
|
114
|
+
lseg: { name: "lseg" },
|
115
|
+
box: { name: "box" },
|
116
|
+
path: { name: "path" },
|
117
|
+
polygon: { name: "polygon" },
|
118
|
+
circle: { name: "circle" },
|
119
|
+
bit: { name: "bit" },
|
120
|
+
bit_varying: { name: "bit varying" },
|
121
|
+
money: { name: "money" },
|
122
|
+
interval: { name: "interval" },
|
123
|
+
oid: { name: "oid" },
|
124
|
+
}
|
125
|
+
|
126
|
+
OID = PostgreSQL::OID #:nodoc:
|
127
|
+
|
128
|
+
include PostgreSQL::Quoting
|
129
|
+
include PostgreSQL::ReferentialIntegrity
|
130
|
+
include PostgreSQL::SchemaStatements
|
131
|
+
include PostgreSQL::DatabaseStatements
|
132
|
+
|
133
|
+
def supports_bulk_alter?
|
134
|
+
true
|
216
135
|
end
|
217
136
|
|
218
|
-
|
137
|
+
def supports_index_sort_order?
|
138
|
+
true
|
139
|
+
end
|
219
140
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
:text => { :name => "text" },
|
224
|
-
:integer => { :name => "integer" },
|
225
|
-
:float => { :name => "float" },
|
226
|
-
:decimal => { :name => "decimal" },
|
227
|
-
:datetime => { :name => "timestamp" },
|
228
|
-
:timestamp => { :name => "timestamp" },
|
229
|
-
:time => { :name => "time" },
|
230
|
-
:date => { :name => "date" },
|
231
|
-
:binary => { :name => "bytea" },
|
232
|
-
:boolean => { :name => "boolean" },
|
233
|
-
:xml => { :name => "xml" },
|
234
|
-
:tsvector => { :name => "tsvector" }
|
235
|
-
}
|
141
|
+
def supports_partial_index?
|
142
|
+
true
|
143
|
+
end
|
236
144
|
|
237
|
-
|
238
|
-
|
239
|
-
ADAPTER_NAME
|
145
|
+
def supports_expression_index?
|
146
|
+
true
|
240
147
|
end
|
241
148
|
|
242
|
-
|
243
|
-
# caching.
|
244
|
-
def supports_statement_cache?
|
149
|
+
def supports_transaction_isolation?
|
245
150
|
true
|
246
151
|
end
|
247
152
|
|
248
|
-
def
|
153
|
+
def supports_foreign_keys?
|
154
|
+
true
|
155
|
+
end
|
156
|
+
|
157
|
+
def supports_validate_constraints?
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
def supports_views?
|
162
|
+
true
|
163
|
+
end
|
164
|
+
|
165
|
+
def supports_datetime_with_precision?
|
249
166
|
true
|
250
167
|
end
|
251
168
|
|
252
|
-
|
169
|
+
def supports_json?
|
170
|
+
postgresql_version >= 90200
|
171
|
+
end
|
172
|
+
|
173
|
+
def supports_comments?
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def supports_savepoints?
|
178
|
+
true
|
179
|
+
end
|
180
|
+
|
181
|
+
def index_algorithms
|
182
|
+
{ concurrently: "CONCURRENTLY" }
|
183
|
+
end
|
184
|
+
|
185
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
253
186
|
def initialize(connection, max)
|
254
|
-
super
|
187
|
+
super(max)
|
188
|
+
@connection = connection
|
255
189
|
@counter = 0
|
256
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
257
190
|
end
|
258
191
|
|
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
192
|
def next_key
|
265
193
|
"a#{@counter + 1}"
|
266
194
|
end
|
267
195
|
|
268
196
|
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
|
197
|
+
super.tap { @counter += 1 }
|
286
198
|
end
|
287
199
|
|
288
200
|
private
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
def dealloc(key)
|
294
|
-
@connection.query "DEALLOCATE #{key}" if connection_active?
|
295
|
-
end
|
296
|
-
|
297
|
-
def connection_active?
|
298
|
-
@connection.status == PGconn::CONNECTION_OK
|
299
|
-
rescue PGError
|
300
|
-
false
|
301
|
-
end
|
302
|
-
end
|
201
|
+
def dealloc(key)
|
202
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
203
|
+
rescue PG::Error
|
204
|
+
end
|
303
205
|
|
304
|
-
|
305
|
-
|
206
|
+
def connection_active?
|
207
|
+
@connection.status == PG::CONNECTION_OK
|
208
|
+
rescue PG::Error
|
209
|
+
false
|
210
|
+
end
|
306
211
|
end
|
307
212
|
|
308
213
|
# Initializes and connects a PostgreSQL adapter.
|
309
214
|
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
|
215
|
+
super(connection, logger, config)
|
317
216
|
|
318
|
-
@connection_parameters
|
217
|
+
@connection_parameters = connection_parameters
|
319
218
|
|
320
219
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
321
220
|
@local_tz = nil
|
322
|
-
@
|
221
|
+
@max_identifier_length = nil
|
323
222
|
|
324
223
|
connect
|
224
|
+
add_pg_encoders
|
325
225
|
@statements = StatementPool.new @connection,
|
326
|
-
|
226
|
+
self.class.type_cast_config_to_integer(config[:statement_limit])
|
327
227
|
|
328
|
-
if postgresql_version <
|
329
|
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old
|
228
|
+
if postgresql_version < 90100
|
229
|
+
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
330
230
|
end
|
331
231
|
|
332
|
-
|
232
|
+
add_pg_decoders
|
233
|
+
|
234
|
+
@type_map = Type::HashLookupTypeMap.new
|
235
|
+
initialize_type_map
|
236
|
+
@local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
|
237
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
333
238
|
end
|
334
239
|
|
335
240
|
# Clears the prepared statements cache.
|
336
241
|
def clear_cache!
|
337
|
-
@
|
242
|
+
@lock.synchronize do
|
243
|
+
@statements.clear
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def truncate(table_name, name = nil)
|
248
|
+
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
338
249
|
end
|
339
250
|
|
340
251
|
# Is this connection alive and ready for queries?
|
341
252
|
def active?
|
342
|
-
@
|
253
|
+
@lock.synchronize do
|
254
|
+
@connection.query "SELECT 1"
|
255
|
+
end
|
343
256
|
true
|
344
|
-
rescue
|
257
|
+
rescue PG::Error
|
345
258
|
false
|
346
259
|
end
|
347
260
|
|
348
261
|
# Close then reopen the connection.
|
349
262
|
def reconnect!
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
263
|
+
@lock.synchronize do
|
264
|
+
super
|
265
|
+
@connection.reset
|
266
|
+
configure_connection
|
267
|
+
end
|
354
268
|
end
|
355
269
|
|
356
270
|
def reset!
|
357
|
-
|
358
|
-
|
271
|
+
@lock.synchronize do
|
272
|
+
clear_cache!
|
273
|
+
reset_transaction
|
274
|
+
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
|
275
|
+
@connection.query "ROLLBACK"
|
276
|
+
end
|
277
|
+
@connection.query "DISCARD ALL"
|
278
|
+
configure_connection
|
279
|
+
end
|
359
280
|
end
|
360
281
|
|
361
282
|
# Disconnects from the database if already connected. Otherwise, this
|
362
283
|
# method does nothing.
|
363
284
|
def disconnect!
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
def native_database_types #:nodoc:
|
369
|
-
NATIVE_DATABASE_TYPES
|
285
|
+
@lock.synchronize do
|
286
|
+
super
|
287
|
+
@connection.close rescue nil
|
288
|
+
end
|
370
289
|
end
|
371
290
|
|
372
|
-
|
373
|
-
|
374
|
-
|
291
|
+
def discard! # :nodoc:
|
292
|
+
@connection.socket_io.reopen(IO::NULL) rescue nil
|
293
|
+
@connection = nil
|
375
294
|
end
|
376
295
|
|
377
|
-
|
378
|
-
|
379
|
-
true
|
296
|
+
def native_database_types #:nodoc:
|
297
|
+
NATIVE_DATABASE_TYPES
|
380
298
|
end
|
381
299
|
|
382
|
-
# Enable standard-conforming strings if available.
|
383
300
|
def set_standard_conforming_strings
|
384
|
-
|
385
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
386
|
-
ensure
|
387
|
-
self.client_min_messages = old
|
301
|
+
execute("SET standard_conforming_strings = on", "SCHEMA")
|
388
302
|
end
|
389
303
|
|
390
|
-
def
|
304
|
+
def supports_ddl_transactions?
|
391
305
|
true
|
392
306
|
end
|
393
307
|
|
394
|
-
def
|
308
|
+
def supports_advisory_locks?
|
395
309
|
true
|
396
310
|
end
|
397
311
|
|
398
|
-
|
399
|
-
def supports_savepoints?
|
312
|
+
def supports_explain?
|
400
313
|
true
|
401
314
|
end
|
402
315
|
|
403
|
-
|
404
|
-
def supports_explain?
|
316
|
+
def supports_extensions?
|
405
317
|
true
|
406
318
|
end
|
407
319
|
|
408
|
-
|
409
|
-
|
410
|
-
|
320
|
+
def supports_ranges?
|
321
|
+
# Range datatypes weren't introduced until PostgreSQL 9.2
|
322
|
+
postgresql_version >= 90200
|
411
323
|
end
|
412
324
|
|
413
|
-
|
325
|
+
def supports_materialized_views?
|
326
|
+
postgresql_version >= 90300
|
327
|
+
end
|
414
328
|
|
415
|
-
|
416
|
-
|
417
|
-
@connection.escape_bytea(value) if value
|
329
|
+
def supports_foreign_tables?
|
330
|
+
postgresql_version >= 90300
|
418
331
|
end
|
419
332
|
|
420
|
-
|
421
|
-
|
422
|
-
# on escaped binary output from database drive.
|
423
|
-
def unescape_bytea(value)
|
424
|
-
@connection.unescape_bytea(value) if value
|
333
|
+
def supports_pgcrypto_uuid?
|
334
|
+
postgresql_version >= 90400
|
425
335
|
end
|
426
336
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
case value
|
432
|
-
when Float
|
433
|
-
return super unless value.infinite? && column.type == :datetime
|
434
|
-
"'#{value.to_s.downcase}'"
|
435
|
-
when Numeric
|
436
|
-
return super unless column.sql_type == 'money'
|
437
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
438
|
-
"'#{value}'"
|
439
|
-
when String
|
440
|
-
case column.sql_type
|
441
|
-
when 'bytea' then "'#{escape_bytea(value)}'"
|
442
|
-
when 'xml' then "xml '#{quote_string(value)}'"
|
443
|
-
when /^bit/
|
444
|
-
case value
|
445
|
-
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
446
|
-
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
447
|
-
end
|
448
|
-
else
|
449
|
-
super
|
450
|
-
end
|
451
|
-
else
|
452
|
-
super
|
337
|
+
def get_advisory_lock(lock_id) # :nodoc:
|
338
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
339
|
+
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
|
453
340
|
end
|
341
|
+
query_value("SELECT pg_try_advisory_lock(#{lock_id})")
|
454
342
|
end
|
455
343
|
|
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
|
344
|
+
def release_advisory_lock(lock_id) # :nodoc:
|
345
|
+
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
|
346
|
+
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
|
465
347
|
end
|
348
|
+
query_value("SELECT pg_advisory_unlock(#{lock_id})")
|
349
|
+
end
|
350
|
+
|
351
|
+
def enable_extension(name)
|
352
|
+
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
|
353
|
+
reload_type_map
|
354
|
+
}
|
466
355
|
end
|
467
356
|
|
468
|
-
|
469
|
-
|
470
|
-
|
357
|
+
def disable_extension(name)
|
358
|
+
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
|
359
|
+
reload_type_map
|
360
|
+
}
|
471
361
|
end
|
472
362
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
# - "table.name"
|
477
|
-
# - schema_name.table_name
|
478
|
-
# - schema_name."table.name"
|
479
|
-
# - "schema.name".table_name
|
480
|
-
# - "schema.name"."table.name"
|
481
|
-
def quote_table_name(name)
|
482
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
483
|
-
|
484
|
-
unless name_part
|
485
|
-
quote_column_name(schema)
|
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
|
363
|
+
def extension_enabled?(name)
|
364
|
+
res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
|
365
|
+
res.cast_values.first
|
490
366
|
end
|
491
367
|
|
492
|
-
|
493
|
-
|
494
|
-
PGconn.quote_ident(name.to_s)
|
368
|
+
def extensions
|
369
|
+
exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
|
495
370
|
end
|
496
371
|
|
497
|
-
#
|
498
|
-
|
499
|
-
|
500
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
501
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
502
|
-
else
|
503
|
-
super
|
504
|
-
end
|
372
|
+
# Returns the configured supported identifier length supported by PostgreSQL
|
373
|
+
def max_identifier_length
|
374
|
+
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
505
375
|
end
|
376
|
+
alias table_alias_length max_identifier_length
|
377
|
+
alias index_name_length max_identifier_length
|
506
378
|
|
507
379
|
# Set the authorized user for this session
|
508
380
|
def session_auth=(user)
|
509
381
|
clear_cache!
|
510
|
-
|
382
|
+
execute("SET SESSION AUTHORIZATION #{user}")
|
511
383
|
end
|
512
384
|
|
513
|
-
|
514
|
-
|
515
|
-
def supports_disable_referential_integrity? #:nodoc:
|
516
|
-
true
|
385
|
+
def use_insert_returning?
|
386
|
+
@use_insert_returning
|
517
387
|
end
|
518
388
|
|
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
|
389
|
+
def column_name_for_operation(operation, node) # :nodoc:
|
390
|
+
OPERATION_ALIASES.fetch(operation) { operation.downcase }
|
528
391
|
end
|
529
392
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
end
|
536
|
-
|
537
|
-
class ExplainPrettyPrinter # :nodoc:
|
538
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
539
|
-
# PostgreSQL shell:
|
540
|
-
#
|
541
|
-
# QUERY PLAN
|
542
|
-
# ------------------------------------------------------------------------------
|
543
|
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
544
|
-
# Join Filter: (posts.user_id = users.id)
|
545
|
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
546
|
-
# Index Cond: (id = 1)
|
547
|
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
548
|
-
# Filter: (posts.user_id = 1)
|
549
|
-
# (6 rows)
|
550
|
-
#
|
551
|
-
def pp(result)
|
552
|
-
header = result.columns.first
|
553
|
-
lines = result.rows.map(&:first)
|
554
|
-
|
555
|
-
# We add 2 because there's one char of padding at both sides, note
|
556
|
-
# the extra hyphens in the example above.
|
557
|
-
width = [header, *lines].map(&:length).max + 2
|
558
|
-
|
559
|
-
pp = []
|
560
|
-
|
561
|
-
pp << header.center(width).rstrip
|
562
|
-
pp << '-' * width
|
563
|
-
|
564
|
-
pp += lines.map {|line| " #{line}"}
|
565
|
-
|
566
|
-
nrows = result.rows.length
|
567
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
568
|
-
pp << "(#{nrows} #{rows_label})"
|
393
|
+
OPERATION_ALIASES = { # :nodoc:
|
394
|
+
"maximum" => "max",
|
395
|
+
"minimum" => "min",
|
396
|
+
"average" => "avg",
|
397
|
+
}
|
569
398
|
|
570
|
-
|
571
|
-
|
399
|
+
# Returns the version of the connected PostgreSQL server.
|
400
|
+
def postgresql_version
|
401
|
+
@connection.server_version
|
572
402
|
end
|
573
403
|
|
574
|
-
|
575
|
-
|
576
|
-
def select_rows(sql, name = nil)
|
577
|
-
select_raw(sql, name).last
|
404
|
+
def default_index_type?(index) # :nodoc:
|
405
|
+
index.using == :btree || super
|
578
406
|
end
|
579
407
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
408
|
+
private
|
409
|
+
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
410
|
+
VALUE_LIMIT_VIOLATION = "22001"
|
411
|
+
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
412
|
+
NOT_NULL_VIOLATION = "23502"
|
413
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
414
|
+
UNIQUE_VIOLATION = "23505"
|
415
|
+
SERIALIZATION_FAILURE = "40001"
|
416
|
+
DEADLOCK_DETECTED = "40P01"
|
417
|
+
LOCK_NOT_AVAILABLE = "55P03"
|
418
|
+
QUERY_CANCELED = "57014"
|
587
419
|
|
588
|
-
|
589
|
-
|
590
|
-
else
|
591
|
-
super
|
592
|
-
end
|
593
|
-
end
|
594
|
-
alias :create :insert
|
420
|
+
def translate_exception(exception, message)
|
421
|
+
return exception unless exception.respond_to?(:result)
|
595
422
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
423
|
+
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
424
|
+
when UNIQUE_VIOLATION
|
425
|
+
RecordNotUnique.new(message)
|
426
|
+
when FOREIGN_KEY_VIOLATION
|
427
|
+
InvalidForeignKey.new(message)
|
428
|
+
when VALUE_LIMIT_VIOLATION
|
429
|
+
ValueTooLong.new(message)
|
430
|
+
when NUMERIC_VALUE_OUT_OF_RANGE
|
431
|
+
RangeError.new(message)
|
432
|
+
when NOT_NULL_VIOLATION
|
433
|
+
NotNullViolation.new(message)
|
434
|
+
when SERIALIZATION_FAILURE
|
435
|
+
SerializationFailure.new(message)
|
436
|
+
when DEADLOCK_DETECTED
|
437
|
+
Deadlocked.new(message)
|
438
|
+
when LOCK_NOT_AVAILABLE
|
439
|
+
LockWaitTimeout.new(message)
|
440
|
+
when QUERY_CANCELED
|
441
|
+
QueryCanceled.new(message)
|
442
|
+
else
|
443
|
+
super
|
444
|
+
end
|
601
445
|
end
|
602
446
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
}
|
607
|
-
|
608
|
-
typehash = ftypes.group_by { |_, type| type }
|
609
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
610
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
611
|
-
|
612
|
-
rows.each do |row|
|
613
|
-
# unescape string passed BYTEA field (OID == 17)
|
614
|
-
binaries.each do |index, _|
|
615
|
-
row[index] = unescape_bytea(row[index])
|
447
|
+
def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
|
448
|
+
if !type_map.key?(oid)
|
449
|
+
load_additional_types([oid])
|
616
450
|
end
|
617
451
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
monies.each do |index, _|
|
623
|
-
data = row[index]
|
624
|
-
# Because money output is formatted according to the locale, there are two
|
625
|
-
# cases to consider (note the decimal separators):
|
626
|
-
# (1) $12,345,678.12
|
627
|
-
# (2) $12.345.678,12
|
628
|
-
case data
|
629
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
630
|
-
data.gsub!(/[^-\d.]/, '')
|
631
|
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
632
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
452
|
+
type_map.fetch(oid, fmod, sql_type) {
|
453
|
+
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
|
454
|
+
Type.default_value.tap do |cast_type|
|
455
|
+
type_map.register_type(oid, cast_type)
|
633
456
|
end
|
457
|
+
}
|
458
|
+
end
|
459
|
+
|
460
|
+
def initialize_type_map(m = type_map)
|
461
|
+
m.register_type "int2", Type::Integer.new(limit: 2)
|
462
|
+
m.register_type "int4", Type::Integer.new(limit: 4)
|
463
|
+
m.register_type "int8", Type::Integer.new(limit: 8)
|
464
|
+
m.register_type "oid", OID::Oid.new
|
465
|
+
m.register_type "float4", Type::Float.new
|
466
|
+
m.alias_type "float8", "float4"
|
467
|
+
m.register_type "text", Type::Text.new
|
468
|
+
register_class_with_limit m, "varchar", Type::String
|
469
|
+
m.alias_type "char", "varchar"
|
470
|
+
m.alias_type "name", "varchar"
|
471
|
+
m.alias_type "bpchar", "varchar"
|
472
|
+
m.register_type "bool", Type::Boolean.new
|
473
|
+
register_class_with_limit m, "bit", OID::Bit
|
474
|
+
register_class_with_limit m, "varbit", OID::BitVarying
|
475
|
+
m.alias_type "timestamptz", "timestamp"
|
476
|
+
m.register_type "date", OID::Date.new
|
477
|
+
|
478
|
+
m.register_type "money", OID::Money.new
|
479
|
+
m.register_type "bytea", OID::Bytea.new
|
480
|
+
m.register_type "point", OID::Point.new
|
481
|
+
m.register_type "hstore", OID::Hstore.new
|
482
|
+
m.register_type "json", Type::Json.new
|
483
|
+
m.register_type "jsonb", OID::Jsonb.new
|
484
|
+
m.register_type "cidr", OID::Cidr.new
|
485
|
+
m.register_type "inet", OID::Inet.new
|
486
|
+
m.register_type "uuid", OID::Uuid.new
|
487
|
+
m.register_type "xml", OID::Xml.new
|
488
|
+
m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
|
489
|
+
m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
|
490
|
+
m.register_type "citext", OID::SpecializedString.new(:citext)
|
491
|
+
m.register_type "ltree", OID::SpecializedString.new(:ltree)
|
492
|
+
m.register_type "line", OID::SpecializedString.new(:line)
|
493
|
+
m.register_type "lseg", OID::SpecializedString.new(:lseg)
|
494
|
+
m.register_type "box", OID::SpecializedString.new(:box)
|
495
|
+
m.register_type "path", OID::SpecializedString.new(:path)
|
496
|
+
m.register_type "polygon", OID::SpecializedString.new(:polygon)
|
497
|
+
m.register_type "circle", OID::SpecializedString.new(:circle)
|
498
|
+
|
499
|
+
m.register_type "interval" do |_, _, sql_type|
|
500
|
+
precision = extract_precision(sql_type)
|
501
|
+
OID::SpecializedString.new(:interval, precision: precision)
|
634
502
|
end
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
|
639
|
-
# Queries the database and returns the results in an Array-like object
|
640
|
-
def query(sql, name = nil) #:nodoc:
|
641
|
-
log(sql, name) do
|
642
|
-
result_as_array @connection.async_exec(sql)
|
643
|
-
end
|
644
|
-
end
|
645
503
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
def exec_delete(sql, name = 'SQL', binds = [])
|
670
|
-
log(sql, name, binds) do
|
671
|
-
result = binds.empty? ? exec_no_cache(sql, binds) :
|
672
|
-
exec_cache(sql, binds)
|
673
|
-
affected = result.cmd_tuples
|
674
|
-
result.clear
|
675
|
-
affected
|
676
|
-
end
|
677
|
-
end
|
678
|
-
alias :exec_update :exec_delete
|
504
|
+
register_class_with_precision m, "time", Type::Time
|
505
|
+
register_class_with_precision m, "timestamp", OID::DateTime
|
506
|
+
|
507
|
+
m.register_type "numeric" do |_, fmod, sql_type|
|
508
|
+
precision = extract_precision(sql_type)
|
509
|
+
scale = extract_scale(sql_type)
|
510
|
+
|
511
|
+
# The type for the numeric depends on the width of the field,
|
512
|
+
# so we'll do something special here.
|
513
|
+
#
|
514
|
+
# When dealing with decimal columns:
|
515
|
+
#
|
516
|
+
# places after decimal = fmod - 4 & 0xffff
|
517
|
+
# places before decimal = (fmod - 4) >> 16 & 0xffff
|
518
|
+
if fmod && (fmod - 4 & 0xffff).zero?
|
519
|
+
# FIXME: Remove this class, and the second argument to
|
520
|
+
# lookups on PG
|
521
|
+
Type::DecimalWithoutScale.new(precision: precision)
|
522
|
+
else
|
523
|
+
OID::Decimal.new(precision: precision, scale: scale)
|
524
|
+
end
|
525
|
+
end
|
679
526
|
|
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
|
527
|
+
load_additional_types
|
685
528
|
end
|
686
529
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
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}"
|
530
|
+
# Extracts the value from a PostgreSQL column default definition.
|
531
|
+
def extract_value_from_default(default)
|
532
|
+
case default
|
533
|
+
# Quoted types
|
534
|
+
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
535
|
+
# The default 'now'::date is CURRENT_DATE
|
536
|
+
if $1 == "now".freeze && $2 == "date".freeze
|
537
|
+
nil
|
538
|
+
else
|
539
|
+
$1.gsub("''".freeze, "'".freeze)
|
540
|
+
end
|
541
|
+
# Boolean types
|
542
|
+
when "true".freeze, "false".freeze
|
543
|
+
default
|
544
|
+
# Numeric types
|
545
|
+
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
546
|
+
$1
|
547
|
+
# Object identifier types
|
548
|
+
when /\A-?\d+\z/
|
549
|
+
$1
|
759
550
|
else
|
760
|
-
|
551
|
+
# Anything else is blank, some user type, or some function
|
552
|
+
# and we can't know the value of that, so return nil.
|
553
|
+
nil
|
761
554
|
end
|
762
555
|
end
|
763
556
|
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
# Drops a PostgreSQL database.
|
768
|
-
#
|
769
|
-
# Example:
|
770
|
-
# drop_database 'matt_development'
|
771
|
-
def drop_database(name) #:nodoc:
|
772
|
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
773
|
-
end
|
774
|
-
|
775
|
-
# Returns the list of all tables in the schema search path or a specified schema.
|
776
|
-
def tables(name = nil)
|
777
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
778
|
-
SELECT tablename
|
779
|
-
FROM pg_tables
|
780
|
-
WHERE schemaname = ANY (current_schemas(false))
|
781
|
-
SQL
|
782
|
-
end
|
783
|
-
|
784
|
-
# Returns true if table exists.
|
785
|
-
# If the schema is not specified as part of +name+ then it will only find tables within
|
786
|
-
# the current schema search path (regardless of permissions to access tables in other schemas)
|
787
|
-
def table_exists?(name)
|
788
|
-
schema, table = Utils.extract_schema_and_table(name.to_s)
|
789
|
-
return false unless table
|
790
|
-
|
791
|
-
binds = [[nil, table]]
|
792
|
-
binds << [nil, schema] if schema
|
793
|
-
|
794
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
795
|
-
SELECT COUNT(*)
|
796
|
-
FROM pg_class c
|
797
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
798
|
-
WHERE c.relkind in ('v','r')
|
799
|
-
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
|
800
|
-
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
801
|
-
SQL
|
802
|
-
end
|
803
|
-
|
804
|
-
# Returns true if schema exists.
|
805
|
-
def schema_exists?(name)
|
806
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
807
|
-
SELECT COUNT(*)
|
808
|
-
FROM pg_namespace
|
809
|
-
WHERE nspname = '#{name}'
|
810
|
-
SQL
|
811
|
-
end
|
812
|
-
|
813
|
-
# Returns an array of indexes for the given table.
|
814
|
-
def indexes(table_name, name = nil)
|
815
|
-
result = query(<<-SQL, 'SCHEMA')
|
816
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
817
|
-
FROM pg_class t
|
818
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
819
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
820
|
-
WHERE i.relkind = 'i'
|
821
|
-
AND d.indisprimary = 'f'
|
822
|
-
AND t.relname = '#{table_name}'
|
823
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
824
|
-
ORDER BY i.relname
|
825
|
-
SQL
|
826
|
-
|
827
|
-
|
828
|
-
result.map do |row|
|
829
|
-
index_name = row[0]
|
830
|
-
unique = row[1] == 't'
|
831
|
-
indkey = row[2].split(" ")
|
832
|
-
inddef = row[3]
|
833
|
-
oid = row[4]
|
834
|
-
|
835
|
-
columns = Hash[query(<<-SQL, "SCHEMA")]
|
836
|
-
SELECT a.attnum, a.attname
|
837
|
-
FROM pg_attribute a
|
838
|
-
WHERE a.attrelid = #{oid}
|
839
|
-
AND a.attnum IN (#{indkey.join(",")})
|
840
|
-
SQL
|
841
|
-
|
842
|
-
column_names = columns.values_at(*indkey).compact
|
843
|
-
|
844
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
845
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
846
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
847
|
-
|
848
|
-
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
849
|
-
end.compact
|
850
|
-
end
|
851
|
-
|
852
|
-
# Returns the list of all column definitions for a table.
|
853
|
-
def columns(table_name, name = nil)
|
854
|
-
# Limit, precision, and scale are all handled by the superclass.
|
855
|
-
column_definitions(table_name).collect do |column_name, type, default, notnull|
|
856
|
-
PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
|
857
|
-
end
|
858
|
-
end
|
859
|
-
|
860
|
-
# Returns the current database name.
|
861
|
-
def current_database
|
862
|
-
query('select current_database()', 'SCHEMA')[0][0]
|
863
|
-
end
|
864
|
-
|
865
|
-
# Returns the current schema name.
|
866
|
-
def current_schema
|
867
|
-
query('SELECT current_schema', 'SCHEMA')[0][0]
|
868
|
-
end
|
869
|
-
|
870
|
-
# Returns the current database encoding format.
|
871
|
-
def encoding
|
872
|
-
query(<<-end_sql, 'SCHEMA')[0][0]
|
873
|
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
874
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
875
|
-
end_sql
|
876
|
-
end
|
877
|
-
|
878
|
-
# Sets the schema search path to a string of comma-separated schema names.
|
879
|
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
880
|
-
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
881
|
-
#
|
882
|
-
# This should be not be called manually but set in database.yml.
|
883
|
-
def schema_search_path=(schema_csv)
|
884
|
-
if schema_csv
|
885
|
-
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
|
886
|
-
@schema_search_path = schema_csv
|
557
|
+
def extract_default_function(default_value, default)
|
558
|
+
default if has_default_function?(default_value, default)
|
887
559
|
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
560
|
|
924
|
-
|
925
|
-
|
561
|
+
def has_default_function?(default_value, default)
|
562
|
+
!default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
926
563
|
end
|
927
564
|
|
928
|
-
|
929
|
-
|
930
|
-
end
|
931
|
-
|
932
|
-
if pk && sequence
|
933
|
-
quoted_sequence = quote_table_name(sequence)
|
934
|
-
|
935
|
-
select_value <<-end_sql, 'SCHEMA'
|
936
|
-
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
937
|
-
end_sql
|
938
|
-
end
|
939
|
-
end
|
940
|
-
|
941
|
-
# Returns a table's primary key and belonging sequence.
|
942
|
-
def pk_and_sequence_for(table) #:nodoc:
|
943
|
-
# First try looking for a sequence with a dependency on the
|
944
|
-
# given table's primary key.
|
945
|
-
result = query(<<-end_sql, 'SCHEMA')[0]
|
946
|
-
SELECT attr.attname, seq.relname
|
947
|
-
FROM pg_class seq,
|
948
|
-
pg_attribute attr,
|
949
|
-
pg_depend dep,
|
950
|
-
pg_namespace name,
|
951
|
-
pg_constraint cons
|
952
|
-
WHERE seq.oid = dep.objid
|
953
|
-
AND seq.relkind = 'S'
|
954
|
-
AND attr.attrelid = dep.refobjid
|
955
|
-
AND attr.attnum = dep.refobjsubid
|
956
|
-
AND attr.attrelid = cons.conrelid
|
957
|
-
AND attr.attnum = cons.conkey[1]
|
958
|
-
AND cons.contype = 'p'
|
959
|
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
960
|
-
end_sql
|
961
|
-
|
962
|
-
if result.nil? or result.empty?
|
963
|
-
result = query(<<-end_sql, 'SCHEMA')[0]
|
964
|
-
SELECT attr.attname,
|
965
|
-
CASE
|
966
|
-
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
967
|
-
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
968
|
-
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
969
|
-
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
970
|
-
END
|
971
|
-
FROM pg_class t
|
972
|
-
JOIN pg_attribute attr ON (t.oid = attrelid)
|
973
|
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
974
|
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
975
|
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
976
|
-
AND cons.contype = 'p'
|
977
|
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
|
978
|
-
end_sql
|
979
|
-
end
|
980
|
-
|
981
|
-
[result.first, result.last]
|
982
|
-
rescue
|
983
|
-
nil
|
984
|
-
end
|
565
|
+
def load_additional_types(oids = nil)
|
566
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
985
567
|
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
end
|
998
|
-
|
999
|
-
# Renames a table.
|
1000
|
-
# Also renames a table's primary key sequence if the sequence name matches the
|
1001
|
-
# Active Record default.
|
1002
|
-
#
|
1003
|
-
# Example:
|
1004
|
-
# rename_table('octopuses', 'octopi')
|
1005
|
-
def rename_table(name, new_name)
|
1006
|
-
clear_cache!
|
1007
|
-
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
1008
|
-
pk, seq = pk_and_sequence_for(new_name)
|
1009
|
-
if seq == "#{name}_#{pk}_seq"
|
1010
|
-
new_seq = "#{new_name}_#{pk}_seq"
|
1011
|
-
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
1012
|
-
end
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
# Adds a new column to the named table.
|
1016
|
-
# See TableDefinition#column for details of the options you can use.
|
1017
|
-
def add_column(table_name, column_name, type, options = {})
|
1018
|
-
clear_cache!
|
1019
|
-
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1020
|
-
add_column_options!(add_column_sql, options)
|
1021
|
-
|
1022
|
-
execute add_column_sql
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
# Changes the column of a table.
|
1026
|
-
def change_column(table_name, column_name, type, options = {})
|
1027
|
-
clear_cache!
|
1028
|
-
quoted_table_name = quote_table_name(table_name)
|
1029
|
-
|
1030
|
-
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
1031
|
-
|
1032
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
1033
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
1034
|
-
end
|
1035
|
-
|
1036
|
-
# Changes the default value of a table column.
|
1037
|
-
def change_column_default(table_name, column_name, default)
|
1038
|
-
clear_cache!
|
1039
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
1040
|
-
end
|
1041
|
-
|
1042
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
1043
|
-
clear_cache!
|
1044
|
-
unless null || default.nil?
|
1045
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
1046
|
-
end
|
1047
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
# Renames a column in a table.
|
1051
|
-
def rename_column(table_name, column_name, new_column_name)
|
1052
|
-
clear_cache!
|
1053
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
1054
|
-
end
|
1055
|
-
|
1056
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
1057
|
-
execute "DROP INDEX #{quote_table_name(index_name)}"
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
def rename_index(table_name, old_name, new_name)
|
1061
|
-
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
1062
|
-
end
|
1063
|
-
|
1064
|
-
def index_name_length
|
1065
|
-
63
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
# Maps logical Rails types to PostgreSQL-specific data types.
|
1069
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
1070
|
-
case type.to_s
|
1071
|
-
when 'binary'
|
1072
|
-
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
1073
|
-
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
1074
|
-
case limit
|
1075
|
-
when nil, 0..0x3fffffff; super(type)
|
1076
|
-
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
568
|
+
if supports_ranges?
|
569
|
+
query = <<-SQL
|
570
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
571
|
+
FROM pg_type as t
|
572
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
573
|
+
SQL
|
574
|
+
else
|
575
|
+
query = <<-SQL
|
576
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
|
577
|
+
FROM pg_type as t
|
578
|
+
SQL
|
1077
579
|
end
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
580
|
+
|
581
|
+
if oids
|
582
|
+
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
|
583
|
+
else
|
584
|
+
query += initializer.query_conditions_for_initial_load
|
1084
585
|
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.")
|
586
|
+
|
587
|
+
execute_and_clear(query, "SCHEMA", []) do |records|
|
588
|
+
initializer.run(records)
|
1093
589
|
end
|
1094
|
-
else
|
1095
|
-
super
|
1096
590
|
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
591
|
|
1117
|
-
|
1118
|
-
extend self
|
592
|
+
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
1119
593
|
|
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]
|
594
|
+
def execute_and_clear(sql, name, binds, prepare: false)
|
595
|
+
if without_prepared_statement?(binds)
|
596
|
+
result = exec_no_cache(sql, name, [])
|
597
|
+
elsif !prepare
|
598
|
+
result = exec_no_cache(sql, name, binds)
|
599
|
+
else
|
600
|
+
result = exec_cache(sql, name, binds)
|
601
|
+
end
|
602
|
+
ret = yield result
|
603
|
+
result.clear
|
604
|
+
ret
|
1133
605
|
end
|
1134
|
-
end
|
1135
606
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
607
|
+
def exec_no_cache(sql, name, binds)
|
608
|
+
type_casted_binds = type_casted_binds(binds)
|
609
|
+
log(sql, name, binds, type_casted_binds) do
|
610
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
611
|
+
@connection.exec_params(sql, type_casted_binds)
|
612
|
+
end
|
613
|
+
end
|
1140
614
|
end
|
1141
615
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
616
|
+
def exec_cache(sql, name, binds)
|
617
|
+
stmt_key = prepare_statement(sql)
|
618
|
+
type_casted_binds = type_casted_binds(binds)
|
1145
619
|
|
1146
|
-
|
1147
|
-
|
620
|
+
log(sql, name, binds, type_casted_binds, stmt_key) do
|
621
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
622
|
+
@connection.exec_prepared(stmt_key, type_casted_binds)
|
623
|
+
end
|
624
|
+
end
|
625
|
+
rescue ActiveRecord::StatementInvalid => e
|
626
|
+
raise unless is_cached_plan_failure?(e)
|
1148
627
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
InvalidForeignKey.new(message, exception)
|
628
|
+
# Nothing we can do if we are in a transaction because all commands
|
629
|
+
# will raise InFailedSQLTransaction
|
630
|
+
if in_transaction?
|
631
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
1154
632
|
else
|
1155
|
-
|
633
|
+
@lock.synchronize do
|
634
|
+
# outside of transactions we can simply flush this query and retry
|
635
|
+
@statements.delete sql_key(sql)
|
636
|
+
end
|
637
|
+
retry
|
1156
638
|
end
|
1157
639
|
end
|
1158
640
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
641
|
+
# Annoyingly, the code for prepared statements whose return value may
|
642
|
+
# have changed is FEATURE_NOT_SUPPORTED.
|
643
|
+
#
|
644
|
+
# This covers various different error types so we need to do additional
|
645
|
+
# work to classify the exception definitively as a
|
646
|
+
# ActiveRecord::PreparedStatementCacheExpired
|
647
|
+
#
|
648
|
+
# Check here for more details:
|
649
|
+
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
650
|
+
CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
|
651
|
+
def is_cached_plan_failure?(e)
|
652
|
+
pgerror = e.cause
|
653
|
+
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
|
654
|
+
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
|
655
|
+
rescue
|
656
|
+
false
|
1164
657
|
end
|
1165
658
|
|
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
|
659
|
+
def in_transaction?
|
660
|
+
open_transactions > 0
|
1194
661
|
end
|
1195
662
|
|
1196
663
|
# Returns the statement identifier for the client side cache
|
@@ -1202,31 +669,34 @@ module ActiveRecord
|
|
1202
669
|
# Prepare the statement if it hasn't been prepared, return
|
1203
670
|
# the statement key.
|
1204
671
|
def prepare_statement(sql)
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
672
|
+
@lock.synchronize do
|
673
|
+
sql_key = sql_key(sql)
|
674
|
+
unless @statements.key? sql_key
|
675
|
+
nextkey = @statements.next_key
|
676
|
+
begin
|
677
|
+
@connection.prepare nextkey, sql
|
678
|
+
rescue => e
|
679
|
+
raise translate_exception_class(e, sql)
|
680
|
+
end
|
681
|
+
# Clear the queue
|
682
|
+
@connection.get_last_result
|
683
|
+
@statements[sql_key] = nextkey
|
684
|
+
end
|
685
|
+
@statements[sql_key]
|
1210
686
|
end
|
1211
|
-
@statements[sql_key]
|
1212
687
|
end
|
1213
688
|
|
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
689
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
1220
690
|
# connected server's characteristics.
|
1221
691
|
def connect
|
1222
|
-
@connection =
|
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
|
-
|
692
|
+
@connection = PG.connect(@connection_parameters)
|
1229
693
|
configure_connection
|
694
|
+
rescue ::PG::Error => error
|
695
|
+
if error.message.include?("does not exist")
|
696
|
+
raise ActiveRecord::NoDatabaseError
|
697
|
+
else
|
698
|
+
raise
|
699
|
+
end
|
1230
700
|
end
|
1231
701
|
|
1232
702
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -1235,45 +705,40 @@ module ActiveRecord
|
|
1235
705
|
if @config[:encoding]
|
1236
706
|
@connection.set_client_encoding(@config[:encoding])
|
1237
707
|
end
|
1238
|
-
self.client_min_messages = @config[:min_messages]
|
708
|
+
self.client_min_messages = @config[:min_messages] || "warning"
|
1239
709
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
1240
710
|
|
1241
|
-
# Use standard-conforming strings
|
711
|
+
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
1242
712
|
set_standard_conforming_strings
|
1243
713
|
|
714
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
715
|
+
|
1244
716
|
# If using Active Record's time zone support configure the connection to return
|
1245
717
|
# TIMESTAMP WITH ZONE types in UTC.
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
718
|
+
unless variables["timezone"]
|
719
|
+
if ActiveRecord::Base.default_timezone == :utc
|
720
|
+
variables["timezone"] = "UTC"
|
721
|
+
elsif @local_tz
|
722
|
+
variables["timezone"] = @local_tz
|
723
|
+
end
|
1250
724
|
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
725
|
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
res.clear
|
1270
|
-
return fields, results
|
726
|
+
# SET statements from :variables config hash
|
727
|
+
# https://www.postgresql.org/docs/current/static/sql-set.html
|
728
|
+
variables.map do |k, v|
|
729
|
+
if v == ":default" || v == :default
|
730
|
+
# Sets the value to the global or compile default
|
731
|
+
execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
|
732
|
+
elsif !v.nil?
|
733
|
+
execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
|
734
|
+
end
|
735
|
+
end
|
1271
736
|
end
|
1272
737
|
|
1273
738
|
# Returns the list of a table's column names, data types, and default values.
|
1274
739
|
#
|
1275
740
|
# The underlying query is roughly:
|
1276
|
-
# SELECT column.name, column.type, default.value
|
741
|
+
# SELECT column.name, column.type, default.value, column.comment
|
1277
742
|
# FROM column LEFT JOIN default
|
1278
743
|
# ON column.table_id = default.table_id
|
1279
744
|
# AND column.num = default.column_num
|
@@ -1288,36 +753,111 @@ module ActiveRecord
|
|
1288
753
|
# Query implementation notes:
|
1289
754
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
1290
755
|
# - ::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
|
-
|
756
|
+
def column_definitions(table_name)
|
757
|
+
query(<<-end_sql, "SCHEMA")
|
758
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
759
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
760
|
+
c.collname, col_description(a.attrelid, a.attnum) AS comment
|
761
|
+
FROM pg_attribute a
|
762
|
+
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
763
|
+
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
764
|
+
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
|
765
|
+
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
766
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
767
|
+
ORDER BY a.attnum
|
1300
768
|
end_sql
|
1301
769
|
end
|
1302
770
|
|
1303
|
-
def extract_pg_identifier_from_name(name)
|
1304
|
-
match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
|
1305
|
-
|
1306
|
-
if match_data
|
1307
|
-
rest = name[match_data[0].length, name.length]
|
1308
|
-
rest = rest[1, rest.length] if rest.start_with? "."
|
1309
|
-
[match_data[1], (rest.length > 0 ? rest : nil)]
|
1310
|
-
end
|
1311
|
-
end
|
1312
|
-
|
1313
771
|
def extract_table_ref_from_insert_sql(sql)
|
1314
|
-
sql[/into\s
|
772
|
+
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
1315
773
|
$1.strip if $1
|
1316
774
|
end
|
1317
775
|
|
1318
|
-
def
|
1319
|
-
|
776
|
+
def arel_visitor
|
777
|
+
Arel::Visitors::PostgreSQL.new(self)
|
778
|
+
end
|
779
|
+
|
780
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
781
|
+
@case_insensitive_cache ||= {}
|
782
|
+
@case_insensitive_cache[column.sql_type] ||= begin
|
783
|
+
sql = <<-end_sql
|
784
|
+
SELECT exists(
|
785
|
+
SELECT * FROM pg_proc
|
786
|
+
WHERE proname = 'lower'
|
787
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
788
|
+
) OR exists(
|
789
|
+
SELECT * FROM pg_proc
|
790
|
+
INNER JOIN pg_cast
|
791
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
792
|
+
WHERE proname = 'lower'
|
793
|
+
AND castsource = #{quote column.sql_type}::regtype
|
794
|
+
)
|
795
|
+
end_sql
|
796
|
+
execute_and_clear(sql, "SCHEMA", []) do |result|
|
797
|
+
result.getvalue(0, 0)
|
798
|
+
end
|
799
|
+
end
|
1320
800
|
end
|
801
|
+
|
802
|
+
def add_pg_encoders
|
803
|
+
map = PG::TypeMapByClass.new
|
804
|
+
map[Integer] = PG::TextEncoder::Integer.new
|
805
|
+
map[TrueClass] = PG::TextEncoder::Boolean.new
|
806
|
+
map[FalseClass] = PG::TextEncoder::Boolean.new
|
807
|
+
@connection.type_map_for_queries = map
|
808
|
+
end
|
809
|
+
|
810
|
+
def add_pg_decoders
|
811
|
+
coders_by_name = {
|
812
|
+
"int2" => PG::TextDecoder::Integer,
|
813
|
+
"int4" => PG::TextDecoder::Integer,
|
814
|
+
"int8" => PG::TextDecoder::Integer,
|
815
|
+
"oid" => PG::TextDecoder::Integer,
|
816
|
+
"float4" => PG::TextDecoder::Float,
|
817
|
+
"float8" => PG::TextDecoder::Float,
|
818
|
+
"bool" => PG::TextDecoder::Boolean,
|
819
|
+
}
|
820
|
+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
821
|
+
query = <<-SQL % known_coder_types.join(", ")
|
822
|
+
SELECT t.oid, t.typname
|
823
|
+
FROM pg_type as t
|
824
|
+
WHERE t.typname IN (%s)
|
825
|
+
SQL
|
826
|
+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
827
|
+
result
|
828
|
+
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
829
|
+
.compact
|
830
|
+
end
|
831
|
+
|
832
|
+
map = PG::TypeMapByOid.new
|
833
|
+
coders.each { |coder| map.add_coder(coder) }
|
834
|
+
@connection.type_map_for_results = map
|
835
|
+
end
|
836
|
+
|
837
|
+
def construct_coder(row, coder_class)
|
838
|
+
return unless coder_class
|
839
|
+
coder_class.new(oid: row["oid"].to_i, name: row["typname"])
|
840
|
+
end
|
841
|
+
|
842
|
+
ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
|
843
|
+
ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
|
844
|
+
ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
|
845
|
+
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
|
846
|
+
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
|
847
|
+
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
|
848
|
+
ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
|
849
|
+
ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
|
850
|
+
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
|
851
|
+
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
|
852
|
+
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
|
853
|
+
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
|
854
|
+
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
|
855
|
+
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
|
856
|
+
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
|
857
|
+
ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
|
858
|
+
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
|
859
|
+
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
|
860
|
+
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
|
1321
861
|
end
|
1322
862
|
end
|
1323
863
|
end
|