activerecord 5.2.1.1 → 6.0.1
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 +738 -445
- data/MIT-LICENSE +3 -1
- data/README.rdoc +4 -2
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +9 -2
- data/lib/active_record/aggregations.rb +4 -2
- data/lib/active_record/association_relation.rb +18 -9
- data/lib/active_record/associations.rb +20 -15
- data/lib/active_record/associations/association.rb +69 -20
- 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 +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
- 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 +15 -29
- data/lib/active_record/associations/collection_proxy.rb +19 -48
- data/lib/active_record/associations/foreign_association.rb +7 -0
- data/lib/active_record/associations/has_many_association.rb +11 -10
- data/lib/active_record/associations/has_many_through_association.rb +42 -25
- 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 +28 -28
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -7
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +39 -31
- 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 -10
- data/lib/active_record/attribute_methods.rb +28 -100
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +114 -38
- data/lib/active_record/attribute_methods/primary_key.rb +15 -22
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +15 -53
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
- data/lib/active_record/attribute_methods/write.rb +17 -24
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +27 -13
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +6 -20
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +140 -27
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +22 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +116 -127
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +26 -11
- data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +135 -56
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
- data/lib/active_record/connection_adapters/abstract_adapter.rb +189 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +151 -198
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +55 -45
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +9 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
- 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 +129 -13
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.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 +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +65 -77
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
- data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +172 -74
- 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 +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +131 -143
- data/lib/active_record/connection_handling.rb +155 -26
- data/lib/active_record/core.rb +104 -59
- data/lib/active_record/counter_cache.rb +4 -29
- 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 +79 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +38 -7
- data/lib/active_record/errors.rb +30 -16
- data/lib/active_record/explain.rb +1 -1
- 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 +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +145 -472
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +13 -3
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +68 -16
- data/lib/active_record/internal_metadata.rb +10 -2
- data/lib/active_record/locking/optimistic.rb +5 -6
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +7 -26
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +100 -81
- data/lib/active_record/migration/command_recorder.rb +50 -6
- data/lib/active_record/migration/compatibility.rb +91 -64
- data/lib/active_record/model_schema.rb +34 -10
- data/lib/active_record/nested_attributes.rb +2 -2
- data/lib/active_record/no_touching.rb +7 -0
- data/lib/active_record/persistence.rb +233 -28
- data/lib/active_record/query_cache.rb +11 -4
- data/lib/active_record/querying.rb +33 -21
- data/lib/active_record/railtie.rb +81 -46
- 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 +196 -46
- data/lib/active_record/reflection.rb +42 -44
- data/lib/active_record/relation.rb +320 -70
- data/lib/active_record/relation/batches.rb +13 -10
- data/lib/active_record/relation/calculations.rb +67 -57
- data/lib/active_record/relation/delegation.rb +48 -35
- data/lib/active_record/relation/finder_methods.rb +30 -30
- data/lib/active_record/relation/merger.rb +19 -25
- data/lib/active_record/relation/predicate_builder.rb +18 -15
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -6
- 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 +17 -10
- data/lib/active_record/relation/query_methods.rb +236 -73
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +14 -10
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/result.rb +30 -11
- 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 +5 -1
- data/lib/active_record/scoping.rb +8 -8
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +21 -15
- data/lib/active_record/statement_cache.rb +32 -5
- data/lib/active_record/store.rb +87 -8
- data/lib/active_record/table_metadata.rb +10 -17
- data/lib/active_record/tasks/database_tasks.rb +195 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +224 -0
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +57 -66
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type.rb +3 -4
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- 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 +1 -0
- data/lib/active_record/validations/uniqueness.rb +15 -27
- data/lib/arel.rb +58 -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 +257 -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 +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record/migration.rb +14 -1
- 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 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +111 -27
- 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,7 +68,7 @@ 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
|
@@ -85,7 +85,7 @@ 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
|
@@ -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}
|
@@ -124,7 +124,7 @@ module ActiveRecord
|
|
124
124
|
|
125
125
|
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
126
126
|
# and non-default opclasses
|
127
|
-
expressions.scan(/(?<column>\w+)
|
127
|
+
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
128
128
|
opclasses[column] = opclass.to_sym if opclass
|
129
129
|
if nulls
|
130
130
|
orders[column] = [desc, nulls].compact.join(" ")
|
@@ -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.
|
@@ -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
|
|
@@ -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
|
@@ -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
|
@@ -637,7 +614,7 @@ module ActiveRecord
|
|
637
614
|
end
|
638
615
|
|
639
616
|
def create_table_definition(*args)
|
640
|
-
PostgreSQL::TableDefinition.new(*args)
|
617
|
+
PostgreSQL::TableDefinition.new(self, *args)
|
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,34 +679,20 @@ module ActiveRecord
|
|
683
679
|
end
|
684
680
|
end
|
685
681
|
|
686
|
-
def
|
687
|
-
|
688
|
-
|
689
|
-
sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
|
690
|
-
if options[:collation]
|
691
|
-
sql << " COLLATE \"#{options[:collation]}\""
|
692
|
-
end
|
693
|
-
if options[:using]
|
694
|
-
sql << " USING #{options[:using]}"
|
695
|
-
elsif options[:cast_as]
|
696
|
-
cast_as_type = type_to_sql(options[:cast_as], options)
|
697
|
-
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
698
|
-
end
|
699
|
-
|
700
|
-
sql
|
682
|
+
def add_column_for_alter(table_name, column_name, type, options = {})
|
683
|
+
return super unless options.key?(:comment)
|
684
|
+
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
701
685
|
end
|
702
686
|
|
703
687
|
def change_column_for_alter(table_name, column_name, type, options = {})
|
704
|
-
|
705
|
-
|
706
|
-
sqls
|
688
|
+
td = create_table_definition(table_name)
|
689
|
+
cd = td.new_column_definition(column_name, type, options)
|
690
|
+
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
707
691
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
708
692
|
sqls
|
709
693
|
end
|
710
694
|
|
711
|
-
|
712
|
-
# Changes the default value of a table column.
|
713
|
-
def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
|
695
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
714
696
|
column = column_for(table_name, column_name)
|
715
697
|
return unless column
|
716
698
|
|
@@ -725,11 +707,17 @@ module ActiveRecord
|
|
725
707
|
end
|
726
708
|
end
|
727
709
|
|
728
|
-
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
729
|
-
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
710
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
711
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
730
712
|
end
|
731
713
|
|
732
714
|
def add_timestamps_for_alter(table_name, options = {})
|
715
|
+
options[:null] = false if options[:null].nil?
|
716
|
+
|
717
|
+
if !options.key?(:precision) && supports_datetime_with_precision?
|
718
|
+
options[:precision] = 6
|
719
|
+
end
|
720
|
+
|
733
721
|
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
734
722
|
end
|
735
723
|
|
@@ -753,7 +741,7 @@ module ActiveRecord
|
|
753
741
|
scope = quoted_scope(name, type: type)
|
754
742
|
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
755
743
|
|
756
|
-
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
|
744
|
+
sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
|
757
745
|
sql << " WHERE n.nspname = #{scope[:schema]}"
|
758
746
|
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
|
759
747
|
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
|
@@ -68,7 +68,7 @@ module ActiveRecord
|
|
68
68
|
# * <tt>"schema_name".table_name</tt>
|
69
69
|
# * <tt>"schema.name"."table name"</tt>
|
70
70
|
def extract_schema_qualified_name(string)
|
71
|
-
schema, table = string.scan(/[^"
|
71
|
+
schema, table = string.scan(/[^".]+|"[^"]*"/)
|
72
72
|
if table.nil?
|
73
73
|
table = schema
|
74
74
|
schema = nil
|
@@ -4,6 +4,14 @@
|
|
4
4
|
gem "pg", ">= 0.18", "< 2.0"
|
5
5
|
require "pg"
|
6
6
|
|
7
|
+
# Use async_exec instead of exec_params on pg versions before 1.1
|
8
|
+
class ::PG::Connection # :nodoc:
|
9
|
+
unless self.public_method_defined?(:async_exec_params)
|
10
|
+
remove_method :exec_params
|
11
|
+
alias exec_params async_exec
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
7
15
|
require "active_record/connection_adapters/abstract_adapter"
|
8
16
|
require "active_record/connection_adapters/statement_pool"
|
9
17
|
require "active_record/connection_adapters/postgresql/column"
|
@@ -35,9 +43,14 @@ module ActiveRecord
|
|
35
43
|
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
36
44
|
conn_params.slice!(*valid_conn_param_keys)
|
37
45
|
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
@@ -70,7 +83,20 @@ module ActiveRecord
|
|
70
83
|
# In addition, default connection parameters of libpq can be set per environment variables.
|
71
84
|
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
|
72
85
|
class PostgreSQLAdapter < AbstractAdapter
|
73
|
-
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
|
74
100
|
|
75
101
|
NATIVE_DATABASE_TYPES = {
|
76
102
|
primary_key: "bigserial primary key",
|
@@ -159,7 +185,7 @@ module ActiveRecord
|
|
159
185
|
end
|
160
186
|
|
161
187
|
def supports_json?
|
162
|
-
|
188
|
+
true
|
163
189
|
end
|
164
190
|
|
165
191
|
def supports_comments?
|
@@ -170,6 +196,17 @@ module ActiveRecord
|
|
170
196
|
true
|
171
197
|
end
|
172
198
|
|
199
|
+
def supports_insert_returning?
|
200
|
+
true
|
201
|
+
end
|
202
|
+
|
203
|
+
def supports_insert_on_conflict?
|
204
|
+
database_version >= 90500
|
205
|
+
end
|
206
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
207
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
208
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
209
|
+
|
173
210
|
def index_algorithms
|
174
211
|
{ concurrently: "CONCURRENTLY" }
|
175
212
|
end
|
@@ -212,15 +249,8 @@ module ActiveRecord
|
|
212
249
|
@local_tz = nil
|
213
250
|
@max_identifier_length = nil
|
214
251
|
|
215
|
-
|
252
|
+
configure_connection
|
216
253
|
add_pg_encoders
|
217
|
-
@statements = StatementPool.new @connection,
|
218
|
-
self.class.type_cast_config_to_integer(config[:statement_limit])
|
219
|
-
|
220
|
-
if postgresql_version < 90100
|
221
|
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
222
|
-
end
|
223
|
-
|
224
254
|
add_pg_decoders
|
225
255
|
|
226
256
|
@type_map = Type::HashLookupTypeMap.new
|
@@ -229,15 +259,10 @@ module ActiveRecord
|
|
229
259
|
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
230
260
|
end
|
231
261
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def truncate(table_name, name = nil)
|
240
|
-
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
262
|
+
def self.database_exists?(config)
|
263
|
+
!!ActiveRecord::Base.postgresql_connection(config)
|
264
|
+
rescue ActiveRecord::NoDatabaseError
|
265
|
+
false
|
241
266
|
end
|
242
267
|
|
243
268
|
# Is this connection alive and ready for queries?
|
@@ -256,6 +281,8 @@ module ActiveRecord
|
|
256
281
|
super
|
257
282
|
@connection.reset
|
258
283
|
configure_connection
|
284
|
+
rescue PG::ConnectionBad
|
285
|
+
connect
|
259
286
|
end
|
260
287
|
end
|
261
288
|
|
@@ -281,6 +308,7 @@ module ActiveRecord
|
|
281
308
|
end
|
282
309
|
|
283
310
|
def discard! # :nodoc:
|
311
|
+
super
|
284
312
|
@connection.socket_io.reopen(IO::NULL) rescue nil
|
285
313
|
@connection = nil
|
286
314
|
end
|
@@ -310,20 +338,35 @@ module ActiveRecord
|
|
310
338
|
end
|
311
339
|
|
312
340
|
def supports_ranges?
|
313
|
-
|
314
|
-
postgresql_version >= 90200
|
341
|
+
true
|
315
342
|
end
|
343
|
+
deprecate :supports_ranges?
|
316
344
|
|
317
345
|
def supports_materialized_views?
|
318
|
-
|
346
|
+
true
|
319
347
|
end
|
320
348
|
|
321
349
|
def supports_foreign_tables?
|
322
|
-
|
350
|
+
true
|
323
351
|
end
|
324
352
|
|
325
353
|
def supports_pgcrypto_uuid?
|
326
|
-
|
354
|
+
database_version >= 90400
|
355
|
+
end
|
356
|
+
|
357
|
+
def supports_optimizer_hints?
|
358
|
+
unless defined?(@has_pg_hint_plan)
|
359
|
+
@has_pg_hint_plan = extension_available?("pg_hint_plan")
|
360
|
+
end
|
361
|
+
@has_pg_hint_plan
|
362
|
+
end
|
363
|
+
|
364
|
+
def supports_common_table_expressions?
|
365
|
+
true
|
366
|
+
end
|
367
|
+
|
368
|
+
def supports_lazy_transactions?
|
369
|
+
true
|
327
370
|
end
|
328
371
|
|
329
372
|
def get_advisory_lock(lock_id) # :nodoc:
|
@@ -352,9 +395,12 @@ module ActiveRecord
|
|
352
395
|
}
|
353
396
|
end
|
354
397
|
|
398
|
+
def extension_available?(name)
|
399
|
+
query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
400
|
+
end
|
401
|
+
|
355
402
|
def extension_enabled?(name)
|
356
|
-
|
357
|
-
res.cast_values.first
|
403
|
+
query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
358
404
|
end
|
359
405
|
|
360
406
|
def extensions
|
@@ -365,8 +411,6 @@ module ActiveRecord
|
|
365
411
|
def max_identifier_length
|
366
412
|
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
367
413
|
end
|
368
|
-
alias table_alias_length max_identifier_length
|
369
|
-
alias index_name_length max_identifier_length
|
370
414
|
|
371
415
|
# Set the authorized user for this session
|
372
416
|
def session_auth=(user)
|
@@ -389,15 +433,37 @@ module ActiveRecord
|
|
389
433
|
}
|
390
434
|
|
391
435
|
# Returns the version of the connected PostgreSQL server.
|
392
|
-
def
|
436
|
+
def get_database_version # :nodoc:
|
393
437
|
@connection.server_version
|
394
438
|
end
|
439
|
+
alias :postgresql_version :database_version
|
395
440
|
|
396
441
|
def default_index_type?(index) # :nodoc:
|
397
442
|
index.using == :btree || super
|
398
443
|
end
|
399
444
|
|
445
|
+
def build_insert_sql(insert) # :nodoc:
|
446
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
447
|
+
|
448
|
+
if insert.skip_duplicates?
|
449
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
450
|
+
elsif insert.update_duplicates?
|
451
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
452
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
453
|
+
end
|
454
|
+
|
455
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
456
|
+
sql
|
457
|
+
end
|
458
|
+
|
459
|
+
def check_version # :nodoc:
|
460
|
+
if database_version < 90300
|
461
|
+
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
400
465
|
private
|
466
|
+
|
401
467
|
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
402
468
|
VALUE_LIMIT_VIOLATION = "22001"
|
403
469
|
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
@@ -409,34 +475,34 @@ module ActiveRecord
|
|
409
475
|
LOCK_NOT_AVAILABLE = "55P03"
|
410
476
|
QUERY_CANCELED = "57014"
|
411
477
|
|
412
|
-
def translate_exception(exception, message)
|
478
|
+
def translate_exception(exception, message:, sql:, binds:)
|
413
479
|
return exception unless exception.respond_to?(:result)
|
414
480
|
|
415
481
|
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
|
416
482
|
when UNIQUE_VIOLATION
|
417
|
-
RecordNotUnique.new(message)
|
483
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
418
484
|
when FOREIGN_KEY_VIOLATION
|
419
|
-
InvalidForeignKey.new(message)
|
485
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
420
486
|
when VALUE_LIMIT_VIOLATION
|
421
|
-
ValueTooLong.new(message)
|
487
|
+
ValueTooLong.new(message, sql: sql, binds: binds)
|
422
488
|
when NUMERIC_VALUE_OUT_OF_RANGE
|
423
|
-
RangeError.new(message)
|
489
|
+
RangeError.new(message, sql: sql, binds: binds)
|
424
490
|
when NOT_NULL_VIOLATION
|
425
|
-
NotNullViolation.new(message)
|
491
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
426
492
|
when SERIALIZATION_FAILURE
|
427
|
-
SerializationFailure.new(message)
|
493
|
+
SerializationFailure.new(message, sql: sql, binds: binds)
|
428
494
|
when DEADLOCK_DETECTED
|
429
|
-
Deadlocked.new(message)
|
495
|
+
Deadlocked.new(message, sql: sql, binds: binds)
|
430
496
|
when LOCK_NOT_AVAILABLE
|
431
|
-
LockWaitTimeout.new(message)
|
497
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
432
498
|
when QUERY_CANCELED
|
433
|
-
QueryCanceled.new(message)
|
499
|
+
QueryCanceled.new(message, sql: sql, binds: binds)
|
434
500
|
else
|
435
501
|
super
|
436
502
|
end
|
437
503
|
end
|
438
504
|
|
439
|
-
def get_oid_type(oid, fmod, column_name, sql_type = ""
|
505
|
+
def get_oid_type(oid, fmod, column_name, sql_type = "")
|
440
506
|
if !type_map.key?(oid)
|
441
507
|
load_additional_types([oid])
|
442
508
|
end
|
@@ -525,13 +591,13 @@ module ActiveRecord
|
|
525
591
|
# Quoted types
|
526
592
|
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
527
593
|
# The default 'now'::date is CURRENT_DATE
|
528
|
-
if $1 == "now"
|
594
|
+
if $1 == "now" && $2 == "date"
|
529
595
|
nil
|
530
596
|
else
|
531
|
-
$1.gsub("''"
|
597
|
+
$1.gsub("''", "'")
|
532
598
|
end
|
533
599
|
# Boolean types
|
534
|
-
when "true"
|
600
|
+
when "true", "false"
|
535
601
|
default
|
536
602
|
# Numeric types
|
537
603
|
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
|
@@ -557,18 +623,11 @@ module ActiveRecord
|
|
557
623
|
def load_additional_types(oids = nil)
|
558
624
|
initializer = OID::TypeMapInitializer.new(type_map)
|
559
625
|
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
SQL
|
566
|
-
else
|
567
|
-
query = <<-SQL
|
568
|
-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
|
569
|
-
FROM pg_type as t
|
570
|
-
SQL
|
571
|
-
end
|
626
|
+
query = <<~SQL
|
627
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
628
|
+
FROM pg_type as t
|
629
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
630
|
+
SQL
|
572
631
|
|
573
632
|
if oids
|
574
633
|
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
|
@@ -584,6 +643,10 @@ module ActiveRecord
|
|
584
643
|
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
|
585
644
|
|
586
645
|
def execute_and_clear(sql, name, binds, prepare: false)
|
646
|
+
if preventing_writes? && write_query?(sql)
|
647
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
648
|
+
end
|
649
|
+
|
587
650
|
if without_prepared_statement?(binds)
|
588
651
|
result = exec_no_cache(sql, name, [])
|
589
652
|
elsif !prepare
|
@@ -597,16 +660,25 @@ module ActiveRecord
|
|
597
660
|
end
|
598
661
|
|
599
662
|
def exec_no_cache(sql, name, binds)
|
663
|
+
materialize_transactions
|
664
|
+
|
665
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
666
|
+
# made since we established the connection
|
667
|
+
update_typemap_for_default_timezone
|
668
|
+
|
600
669
|
type_casted_binds = type_casted_binds(binds)
|
601
670
|
log(sql, name, binds, type_casted_binds) do
|
602
671
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
603
|
-
@connection.
|
672
|
+
@connection.exec_params(sql, type_casted_binds)
|
604
673
|
end
|
605
674
|
end
|
606
675
|
end
|
607
676
|
|
608
677
|
def exec_cache(sql, name, binds)
|
609
|
-
|
678
|
+
materialize_transactions
|
679
|
+
update_typemap_for_default_timezone
|
680
|
+
|
681
|
+
stmt_key = prepare_statement(sql, binds)
|
610
682
|
type_casted_binds = type_casted_binds(binds)
|
611
683
|
|
612
684
|
log(sql, name, binds, type_casted_binds, stmt_key) do
|
@@ -639,7 +711,7 @@ module ActiveRecord
|
|
639
711
|
#
|
640
712
|
# Check here for more details:
|
641
713
|
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
642
|
-
CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
|
714
|
+
CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
|
643
715
|
def is_cached_plan_failure?(e)
|
644
716
|
pgerror = e.cause
|
645
717
|
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
|
@@ -660,7 +732,7 @@ module ActiveRecord
|
|
660
732
|
|
661
733
|
# Prepare the statement if it hasn't been prepared, return
|
662
734
|
# the statement key.
|
663
|
-
def prepare_statement(sql)
|
735
|
+
def prepare_statement(sql, binds)
|
664
736
|
@lock.synchronize do
|
665
737
|
sql_key = sql_key(sql)
|
666
738
|
unless @statements.key? sql_key
|
@@ -668,7 +740,7 @@ module ActiveRecord
|
|
668
740
|
begin
|
669
741
|
@connection.prepare nextkey, sql
|
670
742
|
rescue => e
|
671
|
-
raise translate_exception_class(e, sql)
|
743
|
+
raise translate_exception_class(e, sql, binds)
|
672
744
|
end
|
673
745
|
# Clear the queue
|
674
746
|
@connection.get_last_result
|
@@ -683,12 +755,8 @@ module ActiveRecord
|
|
683
755
|
def connect
|
684
756
|
@connection = PG.connect(@connection_parameters)
|
685
757
|
configure_connection
|
686
|
-
|
687
|
-
|
688
|
-
raise ActiveRecord::NoDatabaseError
|
689
|
-
else
|
690
|
-
raise
|
691
|
-
end
|
758
|
+
add_pg_encoders
|
759
|
+
add_pg_decoders
|
692
760
|
end
|
693
761
|
|
694
762
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -746,7 +814,7 @@ module ActiveRecord
|
|
746
814
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
747
815
|
# - ::regclass is a function that gives the id for a table name
|
748
816
|
def column_definitions(table_name)
|
749
|
-
query(
|
817
|
+
query(<<~SQL, "SCHEMA")
|
750
818
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
751
819
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
752
820
|
c.collname, col_description(a.attrelid, a.attnum) AS comment
|
@@ -757,7 +825,7 @@ module ActiveRecord
|
|
757
825
|
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
758
826
|
AND a.attnum > 0 AND NOT a.attisdropped
|
759
827
|
ORDER BY a.attnum
|
760
|
-
|
828
|
+
SQL
|
761
829
|
end
|
762
830
|
|
763
831
|
def extract_table_ref_from_insert_sql(sql)
|
@@ -769,10 +837,14 @@ module ActiveRecord
|
|
769
837
|
Arel::Visitors::PostgreSQL.new(self)
|
770
838
|
end
|
771
839
|
|
840
|
+
def build_statement_pool
|
841
|
+
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
842
|
+
end
|
843
|
+
|
772
844
|
def can_perform_case_insensitive_comparison_for?(column)
|
773
845
|
@case_insensitive_cache ||= {}
|
774
846
|
@case_insensitive_cache[column.sql_type] ||= begin
|
775
|
-
sql =
|
847
|
+
sql = <<~SQL
|
776
848
|
SELECT exists(
|
777
849
|
SELECT * FROM pg_proc
|
778
850
|
WHERE proname = 'lower'
|
@@ -784,7 +856,7 @@ module ActiveRecord
|
|
784
856
|
WHERE proname = 'lower'
|
785
857
|
AND castsource = #{quote column.sql_type}::regtype
|
786
858
|
)
|
787
|
-
|
859
|
+
SQL
|
788
860
|
execute_and_clear(sql, "SCHEMA", []) do |result|
|
789
861
|
result.getvalue(0, 0)
|
790
862
|
end
|
@@ -799,7 +871,22 @@ module ActiveRecord
|
|
799
871
|
@connection.type_map_for_queries = map
|
800
872
|
end
|
801
873
|
|
874
|
+
def update_typemap_for_default_timezone
|
875
|
+
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
|
876
|
+
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
|
877
|
+
PG::TextDecoder::TimestampUtc :
|
878
|
+
PG::TextDecoder::TimestampWithoutTimeZone
|
879
|
+
|
880
|
+
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
|
881
|
+
@connection.type_map_for_results.add_coder(@timestamp_decoder)
|
882
|
+
@default_timezone = ActiveRecord::Base.default_timezone
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
802
886
|
def add_pg_decoders
|
887
|
+
@default_timezone = nil
|
888
|
+
@timestamp_decoder = nil
|
889
|
+
|
803
890
|
coders_by_name = {
|
804
891
|
"int2" => PG::TextDecoder::Integer,
|
805
892
|
"int4" => PG::TextDecoder::Integer,
|
@@ -809,8 +896,15 @@ module ActiveRecord
|
|
809
896
|
"float8" => PG::TextDecoder::Float,
|
810
897
|
"bool" => PG::TextDecoder::Boolean,
|
811
898
|
}
|
899
|
+
|
900
|
+
if defined?(PG::TextDecoder::TimestampUtc)
|
901
|
+
# Use native PG encoders available since pg-1.1
|
902
|
+
coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
|
903
|
+
coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
|
904
|
+
end
|
905
|
+
|
812
906
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
813
|
-
query =
|
907
|
+
query = <<~SQL % known_coder_types.join(", ")
|
814
908
|
SELECT t.oid, t.typname
|
815
909
|
FROM pg_type as t
|
816
910
|
WHERE t.typname IN (%s)
|
@@ -824,6 +918,10 @@ module ActiveRecord
|
|
824
918
|
map = PG::TypeMapByOid.new
|
825
919
|
coders.each { |coder| map.add_coder(coder) }
|
826
920
|
@connection.type_map_for_results = map
|
921
|
+
|
922
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
923
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
924
|
+
update_typemap_for_default_timezone
|
827
925
|
end
|
828
926
|
|
829
927
|
def construct_coder(row, coder_class)
|