activerecord 5.2.4.4 → 6.0.3.4
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 +4 -4
- data/CHANGELOG.md +777 -552
- data/MIT-LICENSE +3 -1
- data/README.rdoc +5 -3
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +10 -2
- data/lib/active_record/advisory_lock_base.rb +18 -0
- data/lib/active_record/aggregations.rb +4 -3
- data/lib/active_record/association_relation.rb +10 -8
- data/lib/active_record/associations.rb +21 -16
- data/lib/active_record/associations/alias_tracker.rb +0 -1
- data/lib/active_record/associations/association.rb +56 -19
- data/lib/active_record/associations/association_scope.rb +4 -6
- data/lib/active_record/associations/belongs_to_association.rb +36 -42
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +19 -52
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +12 -23
- data/lib/active_record/associations/collection_proxy.rb +13 -17
- data/lib/active_record/associations/foreign_association.rb +7 -0
- data/lib/active_record/associations/has_many_association.rb +2 -11
- data/lib/active_record/associations/has_many_through_association.rb +14 -14
- data/lib/active_record/associations/has_one_association.rb +28 -30
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +37 -28
- data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +39 -32
- data/lib/active_record/associations/preloader/association.rb +38 -36
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/singular_association.rb +2 -16
- data/lib/active_record/attribute_assignment.rb +7 -11
- data/lib/active_record/attribute_decorators.rb +0 -2
- data/lib/active_record/attribute_methods.rb +28 -100
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
- data/lib/active_record/attribute_methods/dirty.rb +111 -40
- data/lib/active_record/attribute_methods/primary_key.rb +15 -24
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +15 -54
- data/lib/active_record/attribute_methods/serialization.rb +1 -2
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
- data/lib/active_record/attribute_methods/write.rb +17 -25
- data/lib/active_record/attributes.rb +13 -1
- data/lib/active_record/autosave_association.rb +3 -5
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +6 -21
- data/lib/active_record/coders/yaml_column.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +103 -18
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +100 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +191 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +142 -215
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +132 -16
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +135 -146
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_handling.rb +139 -26
- data/lib/active_record/core.rb +103 -61
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +37 -7
- data/lib/active_record/errors.rb +15 -7
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +144 -474
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +13 -6
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +68 -16
- data/lib/active_record/internal_metadata.rb +11 -3
- data/lib/active_record/locking/optimistic.rb +5 -7
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +8 -27
- data/lib/active_record/middleware/database_selector.rb +74 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +104 -85
- data/lib/active_record/migration/command_recorder.rb +54 -22
- data/lib/active_record/migration/compatibility.rb +79 -52
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/model_schema.rb +33 -11
- data/lib/active_record/nested_attributes.rb +2 -4
- data/lib/active_record/no_touching.rb +9 -2
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +232 -29
- data/lib/active_record/query_cache.rb +11 -4
- data/lib/active_record/querying.rb +33 -21
- data/lib/active_record/railtie.rb +80 -43
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +199 -46
- data/lib/active_record/reflection.rb +40 -38
- data/lib/active_record/relation.rb +322 -80
- data/lib/active_record/relation/batches.rb +13 -11
- data/lib/active_record/relation/calculations.rb +54 -48
- data/lib/active_record/relation/delegation.rb +33 -49
- data/lib/active_record/relation/finder_methods.rb +23 -28
- data/lib/active_record/relation/from_clause.rb +4 -0
- data/lib/active_record/relation/merger.rb +11 -21
- data/lib/active_record/relation/predicate_builder.rb +5 -11
- data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +221 -70
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/relation/where_clause.rb +14 -11
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/result.rb +30 -12
- data/lib/active_record/sanitization.rb +32 -40
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +22 -7
- data/lib/active_record/schema_migration.rb +6 -2
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/scoping/default.rb +4 -6
- data/lib/active_record/scoping/named.rb +21 -17
- data/lib/active_record/statement_cache.rb +30 -3
- data/lib/active_record/store.rb +87 -8
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +23 -15
- data/lib/active_record/tasks/database_tasks.rb +194 -25
- data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +225 -0
- data/lib/active_record/timestamp.rb +39 -26
- data/lib/active_record/touch_later.rb +5 -4
- data/lib/active_record/transactions.rb +64 -73
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +0 -1
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type_caster/connection.rb +15 -14
- data/lib/active_record/type_caster/map.rb +1 -4
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/uniqueness.rb +15 -27
- data/lib/arel.rb +62 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +256 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +203 -0
- data/lib/arel/visitors/dot.rb +296 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +156 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +158 -0
- data/lib/arel/visitors/oracle12.rb +65 -0
- data/lib/arel/visitors/postgresql.rb +109 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +888 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors/where_sql.rb +22 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration.rb +14 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +115 -29
- data/lib/active_record/collection_cache_key.rb +0 -53
@@ -22,8 +22,8 @@ module ActiveRecord
|
|
22
22
|
def create_database(name, options = {})
|
23
23
|
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
|
24
24
|
|
25
|
-
option_string = options.
|
26
|
-
memo
|
25
|
+
option_string = options.each_with_object(+"") do |(key, value), memo|
|
26
|
+
memo << case key
|
27
27
|
when :owner
|
28
28
|
" OWNER = \"#{value}\""
|
29
29
|
when :template
|
@@ -68,13 +68,13 @@ module ActiveRecord
|
|
68
68
|
table = quoted_scope(table_name)
|
69
69
|
index = quoted_scope(index_name)
|
70
70
|
|
71
|
-
query_value(
|
71
|
+
query_value(<<~SQL, "SCHEMA").to_i > 0
|
72
72
|
SELECT COUNT(*)
|
73
73
|
FROM pg_class t
|
74
74
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
75
75
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
76
76
|
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
77
|
-
WHERE i.relkind
|
77
|
+
WHERE i.relkind IN ('i', 'I')
|
78
78
|
AND i.relname = #{index[:name]}
|
79
79
|
AND t.relname = #{table[:name]}
|
80
80
|
AND n.nspname = #{index[:schema]}
|
@@ -85,14 +85,14 @@ module ActiveRecord
|
|
85
85
|
def indexes(table_name) # :nodoc:
|
86
86
|
scope = quoted_scope(table_name)
|
87
87
|
|
88
|
-
result = query(
|
88
|
+
result = query(<<~SQL, "SCHEMA")
|
89
89
|
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
90
90
|
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
91
91
|
FROM pg_class t
|
92
92
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
93
93
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
94
94
|
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
95
|
-
WHERE i.relkind
|
95
|
+
WHERE i.relkind IN ('i', 'I')
|
96
96
|
AND d.indisprimary = 'f'
|
97
97
|
AND t.relname = #{scope[:name]}
|
98
98
|
AND n.nspname = #{scope[:schema]}
|
@@ -115,7 +115,7 @@ module ActiveRecord
|
|
115
115
|
if indkey.include?(0)
|
116
116
|
columns = expressions
|
117
117
|
else
|
118
|
-
columns = Hash[query(
|
118
|
+
columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact
|
119
119
|
SELECT a.attnum, a.attname
|
120
120
|
FROM pg_attribute a
|
121
121
|
WHERE a.attrelid = #{oid}
|
@@ -158,7 +158,7 @@ module ActiveRecord
|
|
158
158
|
def table_comment(table_name) # :nodoc:
|
159
159
|
scope = quoted_scope(table_name, type: "BASE TABLE")
|
160
160
|
if scope[:name]
|
161
|
-
query_value(
|
161
|
+
query_value(<<~SQL, "SCHEMA")
|
162
162
|
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
|
163
163
|
FROM pg_catalog.pg_class c
|
164
164
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
@@ -196,7 +196,7 @@ module ActiveRecord
|
|
196
196
|
|
197
197
|
# Returns an array of schema names.
|
198
198
|
def schema_names
|
199
|
-
query_values(
|
199
|
+
query_values(<<~SQL, "SCHEMA")
|
200
200
|
SELECT nspname
|
201
201
|
FROM pg_namespace
|
202
202
|
WHERE nspname !~ '^pg_.*'
|
@@ -287,7 +287,7 @@ module ActiveRecord
|
|
287
287
|
quoted_sequence = quote_table_name(sequence)
|
288
288
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
289
289
|
if max_pk.nil?
|
290
|
-
if
|
290
|
+
if database_version >= 100000
|
291
291
|
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
292
292
|
else
|
293
293
|
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
@@ -302,7 +302,7 @@ module ActiveRecord
|
|
302
302
|
def pk_and_sequence_for(table) #:nodoc:
|
303
303
|
# First try looking for a sequence with a dependency on the
|
304
304
|
# given table's primary key.
|
305
|
-
result = query(
|
305
|
+
result = query(<<~SQL, "SCHEMA")[0]
|
306
306
|
SELECT attr.attname, nsp.nspname, seq.relname
|
307
307
|
FROM pg_class seq,
|
308
308
|
pg_attribute attr,
|
@@ -319,10 +319,10 @@ module ActiveRecord
|
|
319
319
|
AND cons.contype = 'p'
|
320
320
|
AND dep.classid = 'pg_class'::regclass
|
321
321
|
AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
|
322
|
-
|
322
|
+
SQL
|
323
323
|
|
324
324
|
if result.nil? || result.empty?
|
325
|
-
result = query(
|
325
|
+
result = query(<<~SQL, "SCHEMA")[0]
|
326
326
|
SELECT attr.attname, nsp.nspname,
|
327
327
|
CASE
|
328
328
|
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
@@ -339,7 +339,7 @@ module ActiveRecord
|
|
339
339
|
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
340
340
|
AND cons.contype = 'p'
|
341
341
|
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
342
|
-
|
342
|
+
SQL
|
343
343
|
end
|
344
344
|
|
345
345
|
pk = result.shift
|
@@ -353,7 +353,7 @@ module ActiveRecord
|
|
353
353
|
end
|
354
354
|
|
355
355
|
def primary_keys(table_name) # :nodoc:
|
356
|
-
query_values(
|
356
|
+
query_values(<<~SQL, "SCHEMA")
|
357
357
|
SELECT a.attname
|
358
358
|
FROM (
|
359
359
|
SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
|
@@ -368,31 +368,6 @@ module ActiveRecord
|
|
368
368
|
SQL
|
369
369
|
end
|
370
370
|
|
371
|
-
def bulk_change_table(table_name, operations)
|
372
|
-
sql_fragments = []
|
373
|
-
non_combinable_operations = []
|
374
|
-
|
375
|
-
operations.each do |command, args|
|
376
|
-
table, arguments = args.shift, args
|
377
|
-
method = :"#{command}_for_alter"
|
378
|
-
|
379
|
-
if respond_to?(method, true)
|
380
|
-
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
381
|
-
sql_fragments << sqls
|
382
|
-
non_combinable_operations.concat(procs)
|
383
|
-
else
|
384
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
385
|
-
non_combinable_operations.each(&:call)
|
386
|
-
sql_fragments = []
|
387
|
-
non_combinable_operations = []
|
388
|
-
send(command, table, *arguments)
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
393
|
-
non_combinable_operations.each(&:call)
|
394
|
-
end
|
395
|
-
|
396
371
|
# Renames a table.
|
397
372
|
# Also renames a table's primary key sequence if the sequence name exists and
|
398
373
|
# matches the Active Record default.
|
@@ -415,7 +390,7 @@ module ActiveRecord
|
|
415
390
|
rename_table_indexes(table_name, new_name)
|
416
391
|
end
|
417
392
|
|
418
|
-
def add_column(table_name, column_name, type, options
|
393
|
+
def add_column(table_name, column_name, type, **options) #:nodoc:
|
419
394
|
clear_cache!
|
420
395
|
super
|
421
396
|
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
@@ -443,14 +418,16 @@ module ActiveRecord
|
|
443
418
|
end
|
444
419
|
|
445
420
|
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
446
|
-
def change_column_comment(table_name, column_name,
|
421
|
+
def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
|
447
422
|
clear_cache!
|
423
|
+
comment = extract_new_comment_value(comment_or_changes)
|
448
424
|
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
|
449
425
|
end
|
450
426
|
|
451
427
|
# Adds comment for given table or drops it if +comment+ is a +nil+
|
452
|
-
def change_table_comment(table_name,
|
428
|
+
def change_table_comment(table_name, comment_or_changes) # :nodoc:
|
453
429
|
clear_cache!
|
430
|
+
comment = extract_new_comment_value(comment_or_changes)
|
454
431
|
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
455
432
|
end
|
456
433
|
|
@@ -462,7 +439,7 @@ module ActiveRecord
|
|
462
439
|
end
|
463
440
|
|
464
441
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
465
|
-
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
442
|
+
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
466
443
|
execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
|
467
444
|
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
468
445
|
end
|
@@ -502,7 +479,7 @@ module ActiveRecord
|
|
502
479
|
|
503
480
|
def foreign_keys(table_name)
|
504
481
|
scope = quoted_scope(table_name)
|
505
|
-
fk_info = exec_query(
|
482
|
+
fk_info = exec_query(<<~SQL, "SCHEMA")
|
506
483
|
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
|
507
484
|
FROM pg_constraint c
|
508
485
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
@@ -548,21 +525,21 @@ module ActiveRecord
|
|
548
525
|
# The hard limit is 1GB, because of a 32-bit size field, and TOAST.
|
549
526
|
case limit
|
550
527
|
when nil, 0..0x3fffffff; super(type)
|
551
|
-
else raise
|
528
|
+
else raise ArgumentError, "No binary type has byte size #{limit}. The limit on binary can be at most 1GB - 1byte."
|
552
529
|
end
|
553
530
|
when "text"
|
554
531
|
# PostgreSQL doesn't support limits on text columns.
|
555
532
|
# The hard limit is 1GB, according to section 8.3 in the manual.
|
556
533
|
case limit
|
557
534
|
when nil, 0..0x3fffffff; super(type)
|
558
|
-
else raise
|
535
|
+
else raise ArgumentError, "No text type has byte size #{limit}. The limit on text can be at most 1GB - 1byte."
|
559
536
|
end
|
560
537
|
when "integer"
|
561
538
|
case limit
|
562
539
|
when 1, 2; "smallint"
|
563
540
|
when nil, 3, 4; "integer"
|
564
541
|
when 5..8; "bigint"
|
565
|
-
else raise
|
542
|
+
else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
|
566
543
|
end
|
567
544
|
else
|
568
545
|
super
|
@@ -576,12 +553,12 @@ module ActiveRecord
|
|
576
553
|
# requires that the ORDER BY include the distinct column.
|
577
554
|
def columns_for_distinct(columns, orders) #:nodoc:
|
578
555
|
order_columns = orders.reject(&:blank?).map { |s|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
556
|
+
# Convert Arel node to string
|
557
|
+
s = visitor.compile(s) unless s.is_a?(String)
|
558
|
+
# Remove any ASC/DESC modifiers
|
559
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
560
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
561
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
585
562
|
|
586
563
|
(order_columns << super).join(", ")
|
587
564
|
end
|
@@ -623,10 +600,10 @@ module ActiveRecord
|
|
623
600
|
# validate_foreign_key :accounts, name: :special_fk_name
|
624
601
|
#
|
625
602
|
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
|
626
|
-
def validate_foreign_key(from_table,
|
603
|
+
def validate_foreign_key(from_table, to_table = nil, **options)
|
627
604
|
return unless supports_validate_constraints?
|
628
605
|
|
629
|
-
fk_name_to_validate = foreign_key_for!(from_table,
|
606
|
+
fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
|
630
607
|
|
631
608
|
validate_constraint from_table, fk_name_to_validate
|
632
609
|
end
|
@@ -636,8 +613,8 @@ module ActiveRecord
|
|
636
613
|
PostgreSQL::SchemaCreation.new(self)
|
637
614
|
end
|
638
615
|
|
639
|
-
def create_table_definition(*args)
|
640
|
-
PostgreSQL::TableDefinition.new(*args)
|
616
|
+
def create_table_definition(*args, **options)
|
617
|
+
PostgreSQL::TableDefinition.new(self, *args, **options)
|
641
618
|
end
|
642
619
|
|
643
620
|
def create_alter_table(name)
|
@@ -650,16 +627,19 @@ module ActiveRecord
|
|
650
627
|
default_value = extract_value_from_default(default)
|
651
628
|
default_function = extract_default_function(default_value, default)
|
652
629
|
|
653
|
-
|
630
|
+
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
631
|
+
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
632
|
+
end
|
633
|
+
|
634
|
+
PostgreSQL::Column.new(
|
654
635
|
column_name,
|
655
636
|
default_value,
|
656
637
|
type_metadata,
|
657
638
|
!notnull,
|
658
|
-
table_name,
|
659
639
|
default_function,
|
660
|
-
collation,
|
640
|
+
collation: collation,
|
661
641
|
comment: comment.presence,
|
662
|
-
|
642
|
+
serial: serial
|
663
643
|
)
|
664
644
|
end
|
665
645
|
|
@@ -672,7 +652,23 @@ module ActiveRecord
|
|
672
652
|
precision: cast_type.precision,
|
673
653
|
scale: cast_type.scale,
|
674
654
|
)
|
675
|
-
|
655
|
+
PostgreSQL::TypeMetadata.new(simple_type, oid: oid, fmod: fmod)
|
656
|
+
end
|
657
|
+
|
658
|
+
def sequence_name_from_parts(table_name, column_name, suffix)
|
659
|
+
over_length = [table_name, column_name, suffix].sum(&:length) + 2 - max_identifier_length
|
660
|
+
|
661
|
+
if over_length > 0
|
662
|
+
column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
|
663
|
+
over_length -= column_name.length - column_name_length
|
664
|
+
column_name = column_name[0, column_name_length - [over_length, 0].min]
|
665
|
+
end
|
666
|
+
|
667
|
+
if over_length > 0
|
668
|
+
table_name = table_name[0, table_name.length - over_length]
|
669
|
+
end
|
670
|
+
|
671
|
+
"#{table_name}_#{column_name}_#{suffix}"
|
676
672
|
end
|
677
673
|
|
678
674
|
def extract_foreign_key_action(specifier)
|
@@ -683,14 +679,14 @@ module ActiveRecord
|
|
683
679
|
end
|
684
680
|
end
|
685
681
|
|
686
|
-
def add_column_for_alter(table_name, column_name, type, options
|
682
|
+
def add_column_for_alter(table_name, column_name, type, **options)
|
687
683
|
return super unless options.key?(:comment)
|
688
684
|
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
689
685
|
end
|
690
686
|
|
691
687
|
def change_column_for_alter(table_name, column_name, type, options = {})
|
692
688
|
td = create_table_definition(table_name)
|
693
|
-
cd = td.new_column_definition(column_name, type, options)
|
689
|
+
cd = td.new_column_definition(column_name, type, **options)
|
694
690
|
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
695
691
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
696
692
|
sqls
|
@@ -715,14 +711,6 @@ module ActiveRecord
|
|
715
711
|
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
716
712
|
end
|
717
713
|
|
718
|
-
def add_timestamps_for_alter(table_name, options = {})
|
719
|
-
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
720
|
-
end
|
721
|
-
|
722
|
-
def remove_timestamps_for_alter(table_name, options = {})
|
723
|
-
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
724
|
-
end
|
725
|
-
|
726
714
|
def add_index_opclass(quoted_columns, **options)
|
727
715
|
opclasses = options_for_index_columns(options[:opclass])
|
728
716
|
quoted_columns.each do |name, column|
|
@@ -731,7 +719,7 @@ module ActiveRecord
|
|
731
719
|
end
|
732
720
|
|
733
721
|
def add_options_for_index_columns(quoted_columns, **options)
|
734
|
-
quoted_columns = add_index_opclass(quoted_columns, options)
|
722
|
+
quoted_columns = add_index_opclass(quoted_columns, **options)
|
735
723
|
super
|
736
724
|
end
|
737
725
|
|
@@ -739,7 +727,7 @@ module ActiveRecord
|
|
739
727
|
scope = quoted_scope(name, type: type)
|
740
728
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
741
729
|
|
742
|
-
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
|
730
|
+
sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
|
743
731
|
sql << " WHERE n.nspname = #{scope[:schema]}"
|
744
732
|
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
|
745
733
|
sql << " AND c.relkind IN (#{scope[:type]})"
|
@@ -1,39 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
|
+
# :stopdoc:
|
4
5
|
module ConnectionAdapters
|
5
|
-
|
6
|
-
|
6
|
+
module PostgreSQL
|
7
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
8
|
+
undef to_yaml if method_defined?(:to_yaml)
|
7
9
|
|
8
|
-
|
10
|
+
attr_reader :oid, :fmod
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@array = /\[\]$/.match?(type_metadata.sql_type)
|
16
|
-
end
|
17
|
-
|
18
|
-
def sql_type
|
19
|
-
super.gsub(/\[\]$/, "".freeze)
|
20
|
-
end
|
21
|
-
|
22
|
-
def ==(other)
|
23
|
-
other.is_a?(PostgreSQLTypeMetadata) &&
|
24
|
-
attributes_for_hash == other.attributes_for_hash
|
25
|
-
end
|
26
|
-
alias eql? ==
|
27
|
-
|
28
|
-
def hash
|
29
|
-
attributes_for_hash.hash
|
30
|
-
end
|
12
|
+
def initialize(type_metadata, oid: nil, fmod: nil)
|
13
|
+
super(type_metadata)
|
14
|
+
@oid = oid
|
15
|
+
@fmod = fmod
|
16
|
+
end
|
31
17
|
|
32
|
-
|
18
|
+
def ==(other)
|
19
|
+
other.is_a?(TypeMetadata) &&
|
20
|
+
__getobj__ == other.__getobj__ &&
|
21
|
+
oid == other.oid &&
|
22
|
+
fmod == other.fmod
|
23
|
+
end
|
24
|
+
alias eql? ==
|
33
25
|
|
34
|
-
def
|
35
|
-
|
26
|
+
def hash
|
27
|
+
TypeMetadata.hash ^
|
28
|
+
__getobj__.hash ^
|
29
|
+
oid.hash ^
|
30
|
+
fmod.hash
|
36
31
|
end
|
32
|
+
end
|
37
33
|
end
|
34
|
+
PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
|
38
35
|
end
|
39
36
|
end
|
@@ -5,7 +5,7 @@ gem "pg", ">= 0.18", "< 2.0"
|
|
5
5
|
require "pg"
|
6
6
|
|
7
7
|
# Use async_exec instead of exec_params on pg versions before 1.1
|
8
|
-
class ::PG::Connection
|
8
|
+
class ::PG::Connection # :nodoc:
|
9
9
|
unless self.public_method_defined?(:async_exec_params)
|
10
10
|
remove_method :exec_params
|
11
11
|
alias exec_params async_exec
|
@@ -43,9 +43,14 @@ module ActiveRecord
|
|
43
43
|
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
44
44
|
conn_params.slice!(*valid_conn_param_keys)
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
conn = PG.connect(conn_params)
|
47
|
+
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
|
48
|
+
rescue ::PG::Error => error
|
49
|
+
if error.message.include?(conn_params[:dbname])
|
50
|
+
raise ActiveRecord::NoDatabaseError
|
51
|
+
else
|
52
|
+
raise
|
53
|
+
end
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
@@ -78,7 +83,20 @@ module ActiveRecord
|
|
78
83
|
# In addition, default connection parameters of libpq can be set per environment variables.
|
79
84
|
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
|
80
85
|
class PostgreSQLAdapter < AbstractAdapter
|
81
|
-
ADAPTER_NAME = "PostgreSQL"
|
86
|
+
ADAPTER_NAME = "PostgreSQL"
|
87
|
+
|
88
|
+
##
|
89
|
+
# :singleton-method:
|
90
|
+
# PostgreSQL allows the creation of "unlogged" tables, which do not record
|
91
|
+
# data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
|
92
|
+
# but significantly increases the risk of data loss if the database
|
93
|
+
# crashes. As a result, this should not be used in production
|
94
|
+
# environments. If you would like all created tables to be unlogged in
|
95
|
+
# the test environment you can add the following line to your test.rb
|
96
|
+
# file:
|
97
|
+
#
|
98
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
|
99
|
+
class_attribute :create_unlogged_tables, default: false
|
82
100
|
|
83
101
|
NATIVE_DATABASE_TYPES = {
|
84
102
|
primary_key: "bigserial primary key",
|
@@ -138,6 +156,10 @@ module ActiveRecord
|
|
138
156
|
true
|
139
157
|
end
|
140
158
|
|
159
|
+
def supports_partitioned_indexes?
|
160
|
+
database_version >= 110_000
|
161
|
+
end
|
162
|
+
|
141
163
|
def supports_partial_index?
|
142
164
|
true
|
143
165
|
end
|
@@ -167,7 +189,7 @@ module ActiveRecord
|
|
167
189
|
end
|
168
190
|
|
169
191
|
def supports_json?
|
170
|
-
|
192
|
+
true
|
171
193
|
end
|
172
194
|
|
173
195
|
def supports_comments?
|
@@ -178,6 +200,17 @@ module ActiveRecord
|
|
178
200
|
true
|
179
201
|
end
|
180
202
|
|
203
|
+
def supports_insert_returning?
|
204
|
+
true
|
205
|
+
end
|
206
|
+
|
207
|
+
def supports_insert_on_conflict?
|
208
|
+
database_version >= 90500
|
209
|
+
end
|
210
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
211
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
212
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
213
|
+
|
181
214
|
def index_algorithms
|
182
215
|
{ concurrently: "CONCURRENTLY" }
|
183
216
|
end
|
@@ -220,15 +253,8 @@ module ActiveRecord
|
|
220
253
|
@local_tz = nil
|
221
254
|
@max_identifier_length = nil
|
222
255
|
|
223
|
-
|
256
|
+
configure_connection
|
224
257
|
add_pg_encoders
|
225
|
-
@statements = StatementPool.new @connection,
|
226
|
-
self.class.type_cast_config_to_integer(config[:statement_limit])
|
227
|
-
|
228
|
-
if postgresql_version < 90100
|
229
|
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
230
|
-
end
|
231
|
-
|
232
258
|
add_pg_decoders
|
233
259
|
|
234
260
|
@type_map = Type::HashLookupTypeMap.new
|
@@ -237,15 +263,10 @@ module ActiveRecord
|
|
237
263
|
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
238
264
|
end
|
239
265
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
def truncate(table_name, name = nil)
|
248
|
-
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
266
|
+
def self.database_exists?(config)
|
267
|
+
!!ActiveRecord::Base.postgresql_connection(config)
|
268
|
+
rescue ActiveRecord::NoDatabaseError
|
269
|
+
false
|
249
270
|
end
|
250
271
|
|
251
272
|
# Is this connection alive and ready for queries?
|
@@ -264,6 +285,8 @@ module ActiveRecord
|
|
264
285
|
super
|
265
286
|
@connection.reset
|
266
287
|
configure_connection
|
288
|
+
rescue PG::ConnectionBad
|
289
|
+
connect
|
267
290
|
end
|
268
291
|
end
|
269
292
|
|
@@ -289,6 +312,7 @@ module ActiveRecord
|
|
289
312
|
end
|
290
313
|
|
291
314
|
def discard! # :nodoc:
|
315
|
+
super
|
292
316
|
@connection.socket_io.reopen(IO::NULL) rescue nil
|
293
317
|
@connection = nil
|
294
318
|
end
|
@@ -318,20 +342,35 @@ module ActiveRecord
|
|
318
342
|
end
|
319
343
|
|
320
344
|
def supports_ranges?
|
321
|
-
|
322
|
-
postgresql_version >= 90200
|
345
|
+
true
|
323
346
|
end
|
347
|
+
deprecate :supports_ranges?
|
324
348
|
|
325
349
|
def supports_materialized_views?
|
326
|
-
|
350
|
+
true
|
327
351
|
end
|
328
352
|
|
329
353
|
def supports_foreign_tables?
|
330
|
-
|
354
|
+
true
|
331
355
|
end
|
332
356
|
|
333
357
|
def supports_pgcrypto_uuid?
|
334
|
-
|
358
|
+
database_version >= 90400
|
359
|
+
end
|
360
|
+
|
361
|
+
def supports_optimizer_hints?
|
362
|
+
unless defined?(@has_pg_hint_plan)
|
363
|
+
@has_pg_hint_plan = extension_available?("pg_hint_plan")
|
364
|
+
end
|
365
|
+
@has_pg_hint_plan
|
366
|
+
end
|
367
|
+
|
368
|
+
def supports_common_table_expressions?
|
369
|
+
true
|
370
|
+
end
|
371
|
+
|
372
|
+
def supports_lazy_transactions?
|
373
|
+
true
|
335
374
|
end
|
336
375
|
|
337
376
|
def get_advisory_lock(lock_id) # :nodoc:
|
@@ -360,9 +399,12 @@ module ActiveRecord
|
|
360
399
|
}
|
361
400
|
end
|
362
401
|
|
402
|
+
def extension_available?(name)
|
403
|
+
query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
404
|
+
end
|
405
|
+
|
363
406
|
def extension_enabled?(name)
|
364
|
-
|
365
|
-
res.cast_values.first
|
407
|
+
query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
366
408
|
end
|
367
409
|
|
368
410
|
def extensions
|
@@ -373,8 +415,6 @@ module ActiveRecord
|
|
373
415
|
def max_identifier_length
|
374
416
|
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
375
417
|
end
|
376
|
-
alias table_alias_length max_identifier_length
|
377
|
-
alias index_name_length max_identifier_length
|
378
418
|
|
379
419
|
# Set the authorized user for this session
|
380
420
|
def session_auth=(user)
|
@@ -397,14 +437,35 @@ module ActiveRecord
|
|
397
437
|
}
|
398
438
|
|
399
439
|
# Returns the version of the connected PostgreSQL server.
|
400
|
-
def
|
440
|
+
def get_database_version # :nodoc:
|
401
441
|
@connection.server_version
|
402
442
|
end
|
443
|
+
alias :postgresql_version :database_version
|
403
444
|
|
404
445
|
def default_index_type?(index) # :nodoc:
|
405
446
|
index.using == :btree || super
|
406
447
|
end
|
407
448
|
|
449
|
+
def build_insert_sql(insert) # :nodoc:
|
450
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
451
|
+
|
452
|
+
if insert.skip_duplicates?
|
453
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
454
|
+
elsif insert.update_duplicates?
|
455
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
456
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
457
|
+
end
|
458
|
+
|
459
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
460
|
+
sql
|
461
|
+
end
|
462
|
+
|
463
|
+
def check_version # :nodoc:
|
464
|
+
if database_version < 90300
|
465
|
+
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
408
469
|
private
|
409
470
|
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
410
471
|
VALUE_LIMIT_VIOLATION = "22001"
|
@@ -417,34 +478,34 @@ module ActiveRecord
|
|
417
478
|
LOCK_NOT_AVAILABLE = "55P03"
|
418
479
|
QUERY_CANCELED = "57014"
|
419
480
|
|
420
|
-
def translate_exception(exception, message)
|
481
|
+
def translate_exception(exception, message:, sql:, binds:)
|
421
482
|
return exception unless exception.respond_to?(:result)
|
422
483
|
|
423
484
|
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
424
485
|
when UNIQUE_VIOLATION
|
425
|
-
RecordNotUnique.new(message)
|
486
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
426
487
|
when FOREIGN_KEY_VIOLATION
|
427
|
-
InvalidForeignKey.new(message)
|
488
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
428
489
|
when VALUE_LIMIT_VIOLATION
|
429
|
-
ValueTooLong.new(message)
|
490
|
+
ValueTooLong.new(message, sql: sql, binds: binds)
|
430
491
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
431
|
-
RangeError.new(message)
|
492
|
+
RangeError.new(message, sql: sql, binds: binds)
|
432
493
|
when NOT_NULL_VIOLATION
|
433
|
-
NotNullViolation.new(message)
|
494
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
434
495
|
when SERIALIZATION_FAILURE
|
435
|
-
SerializationFailure.new(message)
|
496
|
+
SerializationFailure.new(message, sql: sql, binds: binds)
|
436
497
|
when DEADLOCK_DETECTED
|
437
|
-
Deadlocked.new(message)
|
498
|
+
Deadlocked.new(message, sql: sql, binds: binds)
|
438
499
|
when LOCK_NOT_AVAILABLE
|
439
|
-
LockWaitTimeout.new(message)
|
500
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
440
501
|
when QUERY_CANCELED
|
441
|
-
QueryCanceled.new(message)
|
502
|
+
QueryCanceled.new(message, sql: sql, binds: binds)
|
442
503
|
else
|
443
504
|
super
|
444
505
|
end
|
445
506
|
end
|
446
507
|
|
447
|
-
def get_oid_type(oid, fmod, column_name, sql_type = ""
|
508
|
+
def get_oid_type(oid, fmod, column_name, sql_type = "")
|
448
509
|
if !type_map.key?(oid)
|
449
510
|
load_additional_types([oid])
|
450
511
|
end
|
@@ -533,13 +594,13 @@ module ActiveRecord
|
|
533
594
|
# Quoted types
|
534
595
|
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
535
596
|
# The default 'now'::date is CURRENT_DATE
|
536
|
-
if $1 == "now"
|
597
|
+
if $1 == "now" && $2 == "date"
|
537
598
|
nil
|
538
599
|
else
|
539
|
-
$1.gsub("''"
|
600
|
+
$1.gsub("''", "'")
|
540
601
|
end
|
541
602
|
# Boolean types
|
542
|
-
when "true"
|
603
|
+
when "true", "false"
|
543
604
|
default
|
544
605
|
# Numeric types
|
545
606
|
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
@@ -565,21 +626,14 @@ module ActiveRecord
|
|
565
626
|
def load_additional_types(oids = nil)
|
566
627
|
initializer = OID::TypeMapInitializer.new(type_map)
|
567
628
|
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
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
|
579
|
-
end
|
629
|
+
query = <<~SQL
|
630
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
631
|
+
FROM pg_type as t
|
632
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
633
|
+
SQL
|
580
634
|
|
581
635
|
if oids
|
582
|
-
query += "WHERE t.oid
|
636
|
+
query += "WHERE t.oid IN (%s)" % oids.join(", ")
|
583
637
|
else
|
584
638
|
query += initializer.query_conditions_for_initial_load
|
585
639
|
end
|
@@ -592,6 +646,10 @@ module ActiveRecord
|
|
592
646
|
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
593
647
|
|
594
648
|
def execute_and_clear(sql, name, binds, prepare: false)
|
649
|
+
if preventing_writes? && write_query?(sql)
|
650
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
651
|
+
end
|
652
|
+
|
595
653
|
if without_prepared_statement?(binds)
|
596
654
|
result = exec_no_cache(sql, name, [])
|
597
655
|
elsif !prepare
|
@@ -605,6 +663,12 @@ module ActiveRecord
|
|
605
663
|
end
|
606
664
|
|
607
665
|
def exec_no_cache(sql, name, binds)
|
666
|
+
materialize_transactions
|
667
|
+
|
668
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
669
|
+
# made since we established the connection
|
670
|
+
update_typemap_for_default_timezone
|
671
|
+
|
608
672
|
type_casted_binds = type_casted_binds(binds)
|
609
673
|
log(sql, name, binds, type_casted_binds) do
|
610
674
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -614,7 +678,10 @@ module ActiveRecord
|
|
614
678
|
end
|
615
679
|
|
616
680
|
def exec_cache(sql, name, binds)
|
617
|
-
|
681
|
+
materialize_transactions
|
682
|
+
update_typemap_for_default_timezone
|
683
|
+
|
684
|
+
stmt_key = prepare_statement(sql, binds)
|
618
685
|
type_casted_binds = type_casted_binds(binds)
|
619
686
|
|
620
687
|
log(sql, name, binds, type_casted_binds, stmt_key) do
|
@@ -647,7 +714,7 @@ module ActiveRecord
|
|
647
714
|
#
|
648
715
|
# Check here for more details:
|
649
716
|
# 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"
|
717
|
+
CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
|
651
718
|
def is_cached_plan_failure?(e)
|
652
719
|
pgerror = e.cause
|
653
720
|
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
|
@@ -668,7 +735,7 @@ module ActiveRecord
|
|
668
735
|
|
669
736
|
# Prepare the statement if it hasn't been prepared, return
|
670
737
|
# the statement key.
|
671
|
-
def prepare_statement(sql)
|
738
|
+
def prepare_statement(sql, binds)
|
672
739
|
@lock.synchronize do
|
673
740
|
sql_key = sql_key(sql)
|
674
741
|
unless @statements.key? sql_key
|
@@ -676,7 +743,7 @@ module ActiveRecord
|
|
676
743
|
begin
|
677
744
|
@connection.prepare nextkey, sql
|
678
745
|
rescue => e
|
679
|
-
raise translate_exception_class(e, sql)
|
746
|
+
raise translate_exception_class(e, sql, binds)
|
680
747
|
end
|
681
748
|
# Clear the queue
|
682
749
|
@connection.get_last_result
|
@@ -691,12 +758,8 @@ module ActiveRecord
|
|
691
758
|
def connect
|
692
759
|
@connection = PG.connect(@connection_parameters)
|
693
760
|
configure_connection
|
694
|
-
|
695
|
-
|
696
|
-
raise ActiveRecord::NoDatabaseError
|
697
|
-
else
|
698
|
-
raise
|
699
|
-
end
|
761
|
+
add_pg_encoders
|
762
|
+
add_pg_decoders
|
700
763
|
end
|
701
764
|
|
702
765
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -754,7 +817,7 @@ module ActiveRecord
|
|
754
817
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
755
818
|
# - ::regclass is a function that gives the id for a table name
|
756
819
|
def column_definitions(table_name)
|
757
|
-
query(
|
820
|
+
query(<<~SQL, "SCHEMA")
|
758
821
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
759
822
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
760
823
|
c.collname, col_description(a.attrelid, a.attnum) AS comment
|
@@ -765,7 +828,7 @@ module ActiveRecord
|
|
765
828
|
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
766
829
|
AND a.attnum > 0 AND NOT a.attisdropped
|
767
830
|
ORDER BY a.attnum
|
768
|
-
|
831
|
+
SQL
|
769
832
|
end
|
770
833
|
|
771
834
|
def extract_table_ref_from_insert_sql(sql)
|
@@ -777,10 +840,14 @@ module ActiveRecord
|
|
777
840
|
Arel::Visitors::PostgreSQL.new(self)
|
778
841
|
end
|
779
842
|
|
843
|
+
def build_statement_pool
|
844
|
+
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
845
|
+
end
|
846
|
+
|
780
847
|
def can_perform_case_insensitive_comparison_for?(column)
|
781
848
|
@case_insensitive_cache ||= {}
|
782
849
|
@case_insensitive_cache[column.sql_type] ||= begin
|
783
|
-
sql =
|
850
|
+
sql = <<~SQL
|
784
851
|
SELECT exists(
|
785
852
|
SELECT * FROM pg_proc
|
786
853
|
WHERE proname = 'lower'
|
@@ -792,7 +859,7 @@ module ActiveRecord
|
|
792
859
|
WHERE proname = 'lower'
|
793
860
|
AND castsource = #{quote column.sql_type}::regtype
|
794
861
|
)
|
795
|
-
|
862
|
+
SQL
|
796
863
|
execute_and_clear(sql, "SCHEMA", []) do |result|
|
797
864
|
result.getvalue(0, 0)
|
798
865
|
end
|
@@ -807,7 +874,22 @@ module ActiveRecord
|
|
807
874
|
@connection.type_map_for_queries = map
|
808
875
|
end
|
809
876
|
|
877
|
+
def update_typemap_for_default_timezone
|
878
|
+
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
|
879
|
+
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
|
880
|
+
PG::TextDecoder::TimestampUtc :
|
881
|
+
PG::TextDecoder::TimestampWithoutTimeZone
|
882
|
+
|
883
|
+
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
|
884
|
+
@connection.type_map_for_results.add_coder(@timestamp_decoder)
|
885
|
+
@default_timezone = ActiveRecord::Base.default_timezone
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
810
889
|
def add_pg_decoders
|
890
|
+
@default_timezone = nil
|
891
|
+
@timestamp_decoder = nil
|
892
|
+
|
811
893
|
coders_by_name = {
|
812
894
|
"int2" => PG::TextDecoder::Integer,
|
813
895
|
"int4" => PG::TextDecoder::Integer,
|
@@ -817,8 +899,15 @@ module ActiveRecord
|
|
817
899
|
"float8" => PG::TextDecoder::Float,
|
818
900
|
"bool" => PG::TextDecoder::Boolean,
|
819
901
|
}
|
902
|
+
|
903
|
+
if defined?(PG::TextDecoder::TimestampUtc)
|
904
|
+
# Use native PG encoders available since pg-1.1
|
905
|
+
coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
|
906
|
+
coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
|
907
|
+
end
|
908
|
+
|
820
909
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
821
|
-
query =
|
910
|
+
query = <<~SQL % known_coder_types.join(", ")
|
822
911
|
SELECT t.oid, t.typname
|
823
912
|
FROM pg_type as t
|
824
913
|
WHERE t.typname IN (%s)
|
@@ -832,6 +921,10 @@ module ActiveRecord
|
|
832
921
|
map = PG::TypeMapByOid.new
|
833
922
|
coders.each { |coder| map.add_coder(coder) }
|
834
923
|
@connection.type_map_for_results = map
|
924
|
+
|
925
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
926
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
927
|
+
update_typemap_for_default_timezone
|
835
928
|
end
|
836
929
|
|
837
930
|
def construct_coder(row, coder_class)
|