activerecord 4.2.0 → 5.2.8.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 +5 -5
- data/CHANGELOG.md +640 -928
- data/MIT-LICENSE +2 -2
- data/README.rdoc +10 -11
- data/examples/performance.rb +32 -31
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +264 -247
- data/lib/active_record/association_relation.rb +24 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +87 -41
- data/lib/active_record/associations/association_scope.rb +106 -132
- data/lib/active_record/associations/belongs_to_association.rb +55 -36
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
- data/lib/active_record/associations/builder/association.rb +29 -38
- data/lib/active_record/associations/builder/belongs_to.rb +77 -30
- data/lib/active_record/associations/builder/collection_association.rb +14 -23
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
- data/lib/active_record/associations/builder/has_many.rb +6 -4
- data/lib/active_record/associations/builder/has_one.rb +13 -6
- data/lib/active_record/associations/builder/singular_association.rb +15 -11
- data/lib/active_record/associations/collection_association.rb +145 -266
- data/lib/active_record/associations/collection_proxy.rb +242 -138
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +35 -75
- data/lib/active_record/associations/has_many_through_association.rb +51 -69
- data/lib/active_record/associations/has_one_association.rb +39 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/join_dependency.rb +134 -154
- data/lib/active_record/associations/preloader/association.rb +85 -116
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +83 -93
- data/lib/active_record/associations/singular_association.rb +27 -40
- data/lib/active_record/associations/through_association.rb +48 -23
- data/lib/active_record/associations.rb +1732 -1596
- data/lib/active_record/attribute_assignment.rb +58 -182
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
- data/lib/active_record/attribute_methods/dirty.rb +94 -125
- data/lib/active_record/attribute_methods/primary_key.rb +86 -71
- data/lib/active_record/attribute_methods/query.rb +4 -2
- data/lib/active_record/attribute_methods/read.rb +45 -63
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
- data/lib/active_record/attribute_methods/write.rb +31 -46
- data/lib/active_record/attribute_methods.rb +170 -117
- data/lib/active_record/attributes.rb +201 -74
- data/lib/active_record/autosave_association.rb +118 -45
- data/lib/active_record/base.rb +60 -48
- data/lib/active_record/callbacks.rb +97 -57
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +37 -13
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
- data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
- data/lib/active_record/connection_adapters/column.rb +50 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +40 -27
- data/lib/active_record/core.rb +205 -202
- data/lib/active_record/counter_cache.rb +80 -37
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +136 -90
- data/lib/active_record/errors.rb +180 -52
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +193 -135
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +148 -112
- data/lib/active_record/integration.rb +70 -28
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +92 -98
- data/lib/active_record/locking/pessimistic.rb +15 -3
- data/lib/active_record/log_subscriber.rb +95 -33
- data/lib/active_record/migration/command_recorder.rb +133 -90
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/migration.rb +594 -267
- data/lib/active_record/model_schema.rb +292 -111
- data/lib/active_record/nested_attributes.rb +266 -214
- data/lib/active_record/no_touching.rb +8 -2
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +350 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +117 -35
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +9 -3
- data/lib/active_record/railties/databases.rake +160 -174
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +447 -288
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +204 -55
- data/lib/active_record/relation/calculations.rb +259 -244
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +290 -253
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +91 -68
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +118 -92
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +446 -389
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -16
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +287 -339
- data/lib/active_record/result.rb +54 -36
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +155 -124
- data/lib/active_record/schema.rb +30 -24
- data/lib/active_record/schema_dumper.rb +91 -87
- data/lib/active_record/schema_migration.rb +19 -19
- data/lib/active_record/scoping/default.rb +102 -84
- data/lib/active_record/scoping/named.rb +81 -32
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +45 -35
- data/lib/active_record/store.rb +42 -36
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +136 -95
- data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +208 -123
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +4 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +30 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type.rb +79 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +41 -32
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +36 -21
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
- data/lib/rails/generators/active_record/migration.rb +18 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +77 -53
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -149
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -30
- data/lib/active_record/type/decimal.rb +0 -40
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -55
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,40 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module ConnectionAdapters
|
3
5
|
module PostgreSQL
|
4
|
-
class SchemaCreation < AbstractAdapter::SchemaCreation
|
5
|
-
private
|
6
|
-
|
7
|
-
def visit_ColumnDefinition(o)
|
8
|
-
sql = super
|
9
|
-
if o.primary_key? && o.type != :primary_key
|
10
|
-
sql << " PRIMARY KEY "
|
11
|
-
add_column_options!(sql, column_options(o))
|
12
|
-
end
|
13
|
-
sql
|
14
|
-
end
|
15
|
-
|
16
|
-
def add_column_options!(sql, options)
|
17
|
-
if options[:array] || options[:column].try(:array)
|
18
|
-
sql << '[]'
|
19
|
-
end
|
20
|
-
|
21
|
-
column = options.fetch(:column) { return super }
|
22
|
-
if column.type == :uuid && options[:default] =~ /\(\)/
|
23
|
-
sql << " DEFAULT #{options[:default]}"
|
24
|
-
else
|
25
|
-
super
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def type_for_column(column)
|
30
|
-
if column.array
|
31
|
-
@conn.lookup_cast_type("#{column.sql_type}[]")
|
32
|
-
else
|
33
|
-
super
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
6
|
module SchemaStatements
|
39
7
|
# Drops the database specified on the +name+ attribute
|
40
8
|
# and creates it again using the provided +options+.
|
@@ -52,26 +20,26 @@ module ActiveRecord
|
|
52
20
|
# create_database config[:database], config
|
53
21
|
# create_database 'foo_development', encoding: 'unicode'
|
54
22
|
def create_database(name, options = {})
|
55
|
-
options = { encoding:
|
23
|
+
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
|
56
24
|
|
57
25
|
option_string = options.inject("") do |memo, (key, value)|
|
58
26
|
memo += case key
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
27
|
+
when :owner
|
28
|
+
" OWNER = \"#{value}\""
|
29
|
+
when :template
|
30
|
+
" TEMPLATE = \"#{value}\""
|
31
|
+
when :encoding
|
32
|
+
" ENCODING = '#{value}'"
|
33
|
+
when :collation
|
34
|
+
" LC_COLLATE = '#{value}'"
|
35
|
+
when :ctype
|
36
|
+
" LC_CTYPE = '#{value}'"
|
37
|
+
when :tablespace
|
38
|
+
" TABLESPACE = \"#{value}\""
|
39
|
+
when :connection_limit
|
40
|
+
" CONNECTION LIMIT = #{value}"
|
41
|
+
else
|
42
|
+
""
|
75
43
|
end
|
76
44
|
end
|
77
45
|
|
@@ -86,150 +54,149 @@ module ActiveRecord
|
|
86
54
|
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
87
55
|
end
|
88
56
|
|
89
|
-
|
90
|
-
|
91
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
92
|
-
SELECT tablename
|
93
|
-
FROM pg_tables
|
94
|
-
WHERE schemaname = ANY (current_schemas(false))
|
95
|
-
SQL
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns true if table exists.
|
99
|
-
# If the schema is not specified as part of +name+ then it will only find tables within
|
100
|
-
# the current schema search path (regardless of permissions to access tables in other schemas)
|
101
|
-
def table_exists?(name)
|
102
|
-
name = Utils.extract_schema_qualified_name(name.to_s)
|
103
|
-
return false unless name.identifier
|
104
|
-
|
105
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
106
|
-
SELECT COUNT(*)
|
107
|
-
FROM pg_class c
|
108
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
109
|
-
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
|
110
|
-
AND c.relname = '#{name.identifier}'
|
111
|
-
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
112
|
-
SQL
|
113
|
-
end
|
114
|
-
|
115
|
-
def drop_table(table_name, options = {})
|
116
|
-
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
57
|
+
def drop_table(table_name, options = {}) # :nodoc:
|
58
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
117
59
|
end
|
118
60
|
|
119
61
|
# Returns true if schema exists.
|
120
62
|
def schema_exists?(name)
|
121
|
-
|
122
|
-
SELECT COUNT(*)
|
123
|
-
FROM pg_namespace
|
124
|
-
WHERE nspname = '#{name}'
|
125
|
-
SQL
|
63
|
+
query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
|
126
64
|
end
|
127
65
|
|
128
|
-
|
129
|
-
|
66
|
+
# Verifies existence of an index with a given name.
|
67
|
+
def index_name_exists?(table_name, index_name)
|
68
|
+
table = quoted_scope(table_name)
|
69
|
+
index = quoted_scope(index_name)
|
70
|
+
|
71
|
+
query_value(<<-SQL, "SCHEMA").to_i > 0
|
130
72
|
SELECT COUNT(*)
|
131
73
|
FROM pg_class t
|
132
74
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
133
75
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
76
|
+
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
134
77
|
WHERE i.relkind = 'i'
|
135
|
-
AND i.relname =
|
136
|
-
AND t.relname =
|
137
|
-
AND
|
78
|
+
AND i.relname = #{index[:name]}
|
79
|
+
AND t.relname = #{table[:name]}
|
80
|
+
AND n.nspname = #{index[:schema]}
|
138
81
|
SQL
|
139
82
|
end
|
140
83
|
|
141
84
|
# Returns an array of indexes for the given table.
|
142
|
-
def indexes(table_name
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
85
|
+
def indexes(table_name) # :nodoc:
|
86
|
+
scope = quoted_scope(table_name)
|
87
|
+
|
88
|
+
result = query(<<-SQL, "SCHEMA")
|
89
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
90
|
+
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
91
|
+
FROM pg_class t
|
92
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
93
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
94
|
+
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
95
|
+
WHERE i.relkind = 'i'
|
96
|
+
AND d.indisprimary = 'f'
|
97
|
+
AND t.relname = #{scope[:name]}
|
98
|
+
AND n.nspname = #{scope[:schema]}
|
152
99
|
ORDER BY i.relname
|
153
100
|
SQL
|
154
101
|
|
155
102
|
result.map do |row|
|
156
103
|
index_name = row[0]
|
157
|
-
unique = row[1]
|
158
|
-
indkey = row[2].split(" ")
|
104
|
+
unique = row[1]
|
105
|
+
indkey = row[2].split(" ").map(&:to_i)
|
159
106
|
inddef = row[3]
|
160
107
|
oid = row[4]
|
108
|
+
comment = row[5]
|
161
109
|
|
162
|
-
|
163
|
-
SELECT a.attnum, a.attname
|
164
|
-
FROM pg_attribute a
|
165
|
-
WHERE a.attrelid = #{oid}
|
166
|
-
AND a.attnum IN (#{indkey.join(",")})
|
167
|
-
SQL
|
168
|
-
|
169
|
-
column_names = columns.values_at(*indkey).compact
|
110
|
+
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
|
170
111
|
|
171
|
-
|
172
|
-
|
173
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
174
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
175
|
-
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
176
|
-
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
112
|
+
orders = {}
|
113
|
+
opclasses = {}
|
177
114
|
|
178
|
-
|
115
|
+
if indkey.include?(0)
|
116
|
+
columns = expressions
|
117
|
+
else
|
118
|
+
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
|
119
|
+
SELECT a.attnum, a.attname
|
120
|
+
FROM pg_attribute a
|
121
|
+
WHERE a.attrelid = #{oid}
|
122
|
+
AND a.attnum IN (#{indkey.join(",")})
|
123
|
+
SQL
|
124
|
+
|
125
|
+
# add info on sort order (only desc order is explicitly specified, asc is the default)
|
126
|
+
# and non-default opclasses
|
127
|
+
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
|
128
|
+
opclasses[column] = opclass.to_sym if opclass
|
129
|
+
if nulls
|
130
|
+
orders[column] = [desc, nulls].compact.join(" ")
|
131
|
+
else
|
132
|
+
orders[column] = :desc if desc
|
133
|
+
end
|
134
|
+
end
|
179
135
|
end
|
180
|
-
|
136
|
+
|
137
|
+
IndexDefinition.new(
|
138
|
+
table_name,
|
139
|
+
index_name,
|
140
|
+
unique,
|
141
|
+
columns,
|
142
|
+
orders: orders,
|
143
|
+
opclasses: opclasses,
|
144
|
+
where: where,
|
145
|
+
using: using.to_sym,
|
146
|
+
comment: comment.presence
|
147
|
+
)
|
148
|
+
end
|
181
149
|
end
|
182
150
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
|
187
|
-
oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
|
188
|
-
default_value = extract_value_from_default(oid, default)
|
189
|
-
default_function = extract_default_function(default_value, default)
|
190
|
-
new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
|
151
|
+
def table_options(table_name) # :nodoc:
|
152
|
+
if comment = table_comment(table_name)
|
153
|
+
{ comment: comment }
|
191
154
|
end
|
192
155
|
end
|
193
156
|
|
194
|
-
|
195
|
-
|
157
|
+
# Returns a comment stored in database for given table
|
158
|
+
def table_comment(table_name) # :nodoc:
|
159
|
+
scope = quoted_scope(table_name, type: "BASE TABLE")
|
160
|
+
if scope[:name]
|
161
|
+
query_value(<<-SQL.strip_heredoc, "SCHEMA")
|
162
|
+
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
|
163
|
+
FROM pg_catalog.pg_class c
|
164
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
165
|
+
WHERE c.relname = #{scope[:name]}
|
166
|
+
AND c.relkind IN (#{scope[:type]})
|
167
|
+
AND n.nspname = #{scope[:schema]}
|
168
|
+
SQL
|
169
|
+
end
|
196
170
|
end
|
197
171
|
|
198
172
|
# Returns the current database name.
|
199
173
|
def current_database
|
200
|
-
|
174
|
+
query_value("SELECT current_database()", "SCHEMA")
|
201
175
|
end
|
202
176
|
|
203
177
|
# Returns the current schema name.
|
204
178
|
def current_schema
|
205
|
-
|
179
|
+
query_value("SELECT current_schema", "SCHEMA")
|
206
180
|
end
|
207
181
|
|
208
182
|
# Returns the current database encoding format.
|
209
183
|
def encoding
|
210
|
-
|
211
|
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
212
|
-
WHERE pg_database.datname LIKE '#{current_database}'
|
213
|
-
end_sql
|
184
|
+
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
214
185
|
end
|
215
186
|
|
216
187
|
# Returns the current database collation.
|
217
188
|
def collation
|
218
|
-
|
219
|
-
SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
|
220
|
-
end_sql
|
189
|
+
query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
221
190
|
end
|
222
191
|
|
223
192
|
# Returns the current database ctype.
|
224
193
|
def ctype
|
225
|
-
|
226
|
-
SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
|
227
|
-
end_sql
|
194
|
+
query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
|
228
195
|
end
|
229
196
|
|
230
197
|
# Returns an array of schema names.
|
231
198
|
def schema_names
|
232
|
-
|
199
|
+
query_values(<<-SQL, "SCHEMA")
|
233
200
|
SELECT nspname
|
234
201
|
FROM pg_namespace
|
235
202
|
WHERE nspname !~ '^pg_.*'
|
@@ -239,56 +206,53 @@ module ActiveRecord
|
|
239
206
|
end
|
240
207
|
|
241
208
|
# Creates a schema for the given schema name.
|
242
|
-
def create_schema
|
243
|
-
execute "CREATE SCHEMA #{schema_name}"
|
209
|
+
def create_schema(schema_name)
|
210
|
+
execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
|
244
211
|
end
|
245
212
|
|
246
213
|
# Drops the schema for the given schema name.
|
247
|
-
def drop_schema
|
248
|
-
execute "DROP SCHEMA #{schema_name} CASCADE"
|
214
|
+
def drop_schema(schema_name, options = {})
|
215
|
+
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
|
249
216
|
end
|
250
217
|
|
251
218
|
# Sets the schema search path to a string of comma-separated schema names.
|
252
219
|
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
253
|
-
# See:
|
220
|
+
# See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
|
254
221
|
#
|
255
222
|
# This should be not be called manually but set in database.yml.
|
256
223
|
def schema_search_path=(schema_csv)
|
257
224
|
if schema_csv
|
258
|
-
execute("SET search_path TO #{schema_csv}",
|
225
|
+
execute("SET search_path TO #{schema_csv}", "SCHEMA")
|
259
226
|
@schema_search_path = schema_csv
|
260
227
|
end
|
261
228
|
end
|
262
229
|
|
263
230
|
# Returns the active schema search path.
|
264
231
|
def schema_search_path
|
265
|
-
@schema_search_path ||=
|
232
|
+
@schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
|
266
233
|
end
|
267
234
|
|
268
235
|
# Returns the current client message level.
|
269
236
|
def client_min_messages
|
270
|
-
|
237
|
+
query_value("SHOW client_min_messages", "SCHEMA")
|
271
238
|
end
|
272
239
|
|
273
240
|
# Set the client message level.
|
274
241
|
def client_min_messages=(level)
|
275
|
-
execute("SET client_min_messages TO '#{level}'",
|
242
|
+
execute("SET client_min_messages TO '#{level}'", "SCHEMA")
|
276
243
|
end
|
277
244
|
|
278
245
|
# Returns the sequence name for a table's primary key or some other specified key.
|
279
|
-
def default_sequence_name(table_name, pk =
|
280
|
-
result = serial_sequence(table_name, pk
|
246
|
+
def default_sequence_name(table_name, pk = "id") #:nodoc:
|
247
|
+
result = serial_sequence(table_name, pk)
|
281
248
|
return nil unless result
|
282
249
|
Utils.extract_schema_qualified_name(result).to_s
|
283
250
|
rescue ActiveRecord::StatementInvalid
|
284
|
-
PostgreSQL::Name.new(nil, "#{table_name}_#{pk
|
251
|
+
PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
|
285
252
|
end
|
286
253
|
|
287
254
|
def serial_sequence(table, column)
|
288
|
-
|
289
|
-
SELECT pg_get_serial_sequence('#{table}', '#{column}')
|
290
|
-
eosql
|
291
|
-
result.rows.first.first
|
255
|
+
query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
|
292
256
|
end
|
293
257
|
|
294
258
|
# Sets the sequence of a table's primary key to the specified value.
|
@@ -299,18 +263,16 @@ module ActiveRecord
|
|
299
263
|
if sequence
|
300
264
|
quoted_sequence = quote_table_name(sequence)
|
301
265
|
|
302
|
-
|
303
|
-
SELECT setval('#{quoted_sequence}', #{value})
|
304
|
-
end_sql
|
266
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
|
305
267
|
else
|
306
|
-
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
268
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
|
307
269
|
end
|
308
270
|
end
|
309
271
|
end
|
310
272
|
|
311
273
|
# Resets the sequence of a table's primary key to the maximum value.
|
312
274
|
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
313
|
-
unless pk
|
275
|
+
unless pk && sequence
|
314
276
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
315
277
|
|
316
278
|
pk ||= default_pk
|
@@ -318,15 +280,21 @@ module ActiveRecord
|
|
318
280
|
end
|
319
281
|
|
320
282
|
if @logger && pk && !sequence
|
321
|
-
@logger.warn "#{table} has primary key #{pk} with no default sequence"
|
283
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
322
284
|
end
|
323
285
|
|
324
286
|
if pk && sequence
|
325
287
|
quoted_sequence = quote_table_name(sequence)
|
288
|
+
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
289
|
+
if max_pk.nil?
|
290
|
+
if postgresql_version >= 100000
|
291
|
+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
292
|
+
else
|
293
|
+
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
294
|
+
end
|
295
|
+
end
|
326
296
|
|
327
|
-
|
328
|
-
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
329
|
-
end_sql
|
297
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
330
298
|
end
|
331
299
|
end
|
332
300
|
|
@@ -334,7 +302,7 @@ module ActiveRecord
|
|
334
302
|
def pk_and_sequence_for(table) #:nodoc:
|
335
303
|
# First try looking for a sequence with a dependency on the
|
336
304
|
# given table's primary key.
|
337
|
-
result = query(<<-end_sql,
|
305
|
+
result = query(<<-end_sql, "SCHEMA")[0]
|
338
306
|
SELECT attr.attname, nsp.nspname, seq.relname
|
339
307
|
FROM pg_class seq,
|
340
308
|
pg_attribute attr,
|
@@ -350,11 +318,11 @@ module ActiveRecord
|
|
350
318
|
AND seq.relnamespace = nsp.oid
|
351
319
|
AND cons.contype = 'p'
|
352
320
|
AND dep.classid = 'pg_class'::regclass
|
353
|
-
AND dep.refobjid =
|
321
|
+
AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
|
354
322
|
end_sql
|
355
323
|
|
356
|
-
if result.nil?
|
357
|
-
result = query(<<-end_sql,
|
324
|
+
if result.nil? || result.empty?
|
325
|
+
result = query(<<-end_sql, "SCHEMA")[0]
|
358
326
|
SELECT attr.attname, nsp.nspname,
|
359
327
|
CASE
|
360
328
|
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
@@ -368,7 +336,7 @@ module ActiveRecord
|
|
368
336
|
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
369
337
|
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
370
338
|
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
371
|
-
WHERE t.oid =
|
339
|
+
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
372
340
|
AND cons.contype = 'p'
|
373
341
|
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
|
374
342
|
end_sql
|
@@ -384,17 +352,45 @@ module ActiveRecord
|
|
384
352
|
nil
|
385
353
|
end
|
386
354
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
355
|
+
def primary_keys(table_name) # :nodoc:
|
356
|
+
query_values(<<-SQL.strip_heredoc, "SCHEMA")
|
357
|
+
SELECT a.attname
|
358
|
+
FROM (
|
359
|
+
SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
|
360
|
+
FROM pg_index
|
361
|
+
WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
|
362
|
+
AND indisprimary
|
363
|
+
) i
|
364
|
+
JOIN pg_attribute a
|
365
|
+
ON a.attrelid = i.indrelid
|
366
|
+
AND a.attnum = i.indkey[i.idx]
|
367
|
+
ORDER BY i.idx
|
368
|
+
SQL
|
369
|
+
end
|
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
|
396
391
|
|
397
|
-
|
392
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
393
|
+
non_combinable_operations.each(&:call)
|
398
394
|
end
|
399
395
|
|
400
396
|
# Renames a table.
|
@@ -407,92 +403,107 @@ module ActiveRecord
|
|
407
403
|
clear_cache!
|
408
404
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
409
405
|
pk, seq = pk_and_sequence_for(new_name)
|
410
|
-
if
|
411
|
-
new_seq = "#{new_name}_#{pk}_seq"
|
406
|
+
if pk
|
412
407
|
idx = "#{table_name}_pkey"
|
413
408
|
new_idx = "#{new_name}_pkey"
|
414
|
-
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
415
409
|
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
410
|
+
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
|
411
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
412
|
+
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
|
413
|
+
end
|
416
414
|
end
|
417
|
-
|
418
415
|
rename_table_indexes(table_name, new_name)
|
419
416
|
end
|
420
417
|
|
421
|
-
|
422
|
-
# See TableDefinition#column for details of the options you can use.
|
423
|
-
def add_column(table_name, column_name, type, options = {})
|
418
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
424
419
|
clear_cache!
|
425
420
|
super
|
421
|
+
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
426
422
|
end
|
427
423
|
|
428
|
-
|
429
|
-
def change_column(table_name, column_name, type, options = {})
|
424
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
430
425
|
clear_cache!
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
|
435
|
-
sql << " USING #{options[:using]}" if options[:using]
|
436
|
-
if options[:cast_as]
|
437
|
-
sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
|
438
|
-
end
|
439
|
-
execute sql
|
440
|
-
|
441
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
442
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
426
|
+
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
|
427
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
428
|
+
procs.each(&:call)
|
443
429
|
end
|
444
430
|
|
445
431
|
# Changes the default value of a table column.
|
446
|
-
def change_column_default(table_name, column_name,
|
447
|
-
|
448
|
-
column = column_for(table_name, column_name)
|
449
|
-
return unless column
|
450
|
-
|
451
|
-
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
|
452
|
-
if default.nil?
|
453
|
-
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
454
|
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
455
|
-
execute alter_column_query % "DROP DEFAULT"
|
456
|
-
else
|
457
|
-
execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
|
458
|
-
end
|
432
|
+
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
433
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
459
434
|
end
|
460
435
|
|
461
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
436
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
462
437
|
clear_cache!
|
463
438
|
unless null || default.nil?
|
464
439
|
column = column_for(table_name, column_name)
|
465
|
-
execute
|
440
|
+
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
|
466
441
|
end
|
467
|
-
execute
|
442
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
|
443
|
+
end
|
444
|
+
|
445
|
+
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
446
|
+
def change_column_comment(table_name, column_name, comment) # :nodoc:
|
447
|
+
clear_cache!
|
448
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
|
449
|
+
end
|
450
|
+
|
451
|
+
# Adds comment for given table or drops it if +comment+ is a +nil+
|
452
|
+
def change_table_comment(table_name, comment) # :nodoc:
|
453
|
+
clear_cache!
|
454
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
468
455
|
end
|
469
456
|
|
470
457
|
# Renames a column in a table.
|
471
|
-
def rename_column(table_name, column_name, new_column_name)
|
458
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
472
459
|
clear_cache!
|
473
460
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
474
461
|
rename_column_indexes(table_name, column_name, new_column_name)
|
475
462
|
end
|
476
463
|
|
477
464
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
478
|
-
index_name, index_type,
|
479
|
-
execute
|
465
|
+
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
466
|
+
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
|
+
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
468
|
+
end
|
480
469
|
end
|
481
470
|
|
482
|
-
def remove_index
|
483
|
-
|
471
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
472
|
+
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
473
|
+
|
474
|
+
if options.is_a?(Hash) && options.key?(:name)
|
475
|
+
provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
|
476
|
+
|
477
|
+
options[:name] = provided_index.identifier
|
478
|
+
table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
|
479
|
+
|
480
|
+
if provided_index.schema.present? && table.schema != provided_index.schema
|
481
|
+
raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
|
486
|
+
algorithm =
|
487
|
+
if options.is_a?(Hash) && options.key?(:algorithm)
|
488
|
+
index_algorithms.fetch(options[:algorithm]) do
|
489
|
+
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
|
490
|
+
end
|
491
|
+
end
|
492
|
+
execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
|
484
493
|
end
|
485
494
|
|
495
|
+
# Renames an index of a table. Raises error if length of new
|
496
|
+
# index name is greater than allowed limit.
|
486
497
|
def rename_index(table_name, old_name, new_name)
|
487
|
-
|
488
|
-
|
489
|
-
end
|
498
|
+
validate_index_length!(table_name, new_name)
|
499
|
+
|
490
500
|
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
491
501
|
end
|
492
502
|
|
493
503
|
def foreign_keys(table_name)
|
494
|
-
|
495
|
-
|
504
|
+
scope = quoted_scope(table_name)
|
505
|
+
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
|
506
|
+
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
|
496
507
|
FROM pg_constraint c
|
497
508
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
498
509
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
@@ -500,88 +511,263 @@ module ActiveRecord
|
|
500
511
|
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
501
512
|
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
502
513
|
WHERE c.contype = 'f'
|
503
|
-
AND t1.relname = #{
|
504
|
-
AND t3.nspname =
|
514
|
+
AND t1.relname = #{scope[:name]}
|
515
|
+
AND t3.nspname = #{scope[:schema]}
|
505
516
|
ORDER BY c.conname
|
506
517
|
SQL
|
507
518
|
|
508
519
|
fk_info.map do |row|
|
509
520
|
options = {
|
510
|
-
column: row[
|
511
|
-
name: row[
|
512
|
-
primary_key: row[
|
521
|
+
column: row["column"],
|
522
|
+
name: row["name"],
|
523
|
+
primary_key: row["primary_key"]
|
513
524
|
}
|
514
525
|
|
515
|
-
options[:on_delete] = extract_foreign_key_action(row[
|
516
|
-
options[:on_update] = extract_foreign_key_action(row[
|
526
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
527
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
528
|
+
options[:validate] = row["valid"]
|
517
529
|
|
518
|
-
ForeignKeyDefinition.new(table_name, row[
|
530
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
519
531
|
end
|
520
532
|
end
|
521
533
|
|
522
|
-
def
|
523
|
-
|
524
|
-
when 'c'; :cascade
|
525
|
-
when 'n'; :nullify
|
526
|
-
when 'r'; :restrict
|
527
|
-
end
|
534
|
+
def foreign_tables
|
535
|
+
query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
|
528
536
|
end
|
529
537
|
|
530
|
-
def
|
531
|
-
|
538
|
+
def foreign_table_exists?(table_name)
|
539
|
+
query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
|
532
540
|
end
|
533
541
|
|
534
542
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
535
|
-
def type_to_sql(type, limit
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
when
|
556
|
-
when
|
557
|
-
|
558
|
-
|
543
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
544
|
+
sql = \
|
545
|
+
case type.to_s
|
546
|
+
when "binary"
|
547
|
+
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
548
|
+
# The hard limit is 1GB, because of a 32-bit size field, and TOAST.
|
549
|
+
case limit
|
550
|
+
when nil, 0..0x3fffffff; super(type)
|
551
|
+
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
552
|
+
end
|
553
|
+
when "text"
|
554
|
+
# PostgreSQL doesn't support limits on text columns.
|
555
|
+
# The hard limit is 1GB, according to section 8.3 in the manual.
|
556
|
+
case limit
|
557
|
+
when nil, 0..0x3fffffff; super(type)
|
558
|
+
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
559
|
+
end
|
560
|
+
when "integer"
|
561
|
+
case limit
|
562
|
+
when 1, 2; "smallint"
|
563
|
+
when nil, 3, 4; "integer"
|
564
|
+
when 5..8; "bigint"
|
565
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
|
566
|
+
end
|
567
|
+
else
|
568
|
+
super
|
559
569
|
end
|
560
|
-
when 'datetime'
|
561
|
-
return super unless precision
|
562
570
|
|
563
|
-
|
564
|
-
|
565
|
-
else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
|
566
|
-
end
|
567
|
-
else
|
568
|
-
super
|
569
|
-
end
|
571
|
+
sql = "#{sql}[]" if array && type != :primary_key
|
572
|
+
sql
|
570
573
|
end
|
571
574
|
|
572
575
|
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
573
576
|
# requires that the ORDER BY include the distinct column.
|
574
577
|
def columns_for_distinct(columns, orders) #:nodoc:
|
575
|
-
order_columns = orders.reject(&:blank?).map{ |s|
|
578
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
576
579
|
# Convert Arel node to string
|
577
580
|
s = s.to_sql unless s.is_a?(String)
|
578
581
|
# Remove any ASC/DESC modifiers
|
579
|
-
s.gsub(/\s+(?:ASC|DESC)\b/i,
|
580
|
-
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i,
|
582
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
583
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
581
584
|
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
582
585
|
|
583
|
-
|
586
|
+
(order_columns << super).join(", ")
|
587
|
+
end
|
588
|
+
|
589
|
+
def update_table_definition(table_name, base) # :nodoc:
|
590
|
+
PostgreSQL::Table.new(table_name, base)
|
591
|
+
end
|
592
|
+
|
593
|
+
def create_schema_dumper(options) # :nodoc:
|
594
|
+
PostgreSQL::SchemaDumper.create(self, options)
|
584
595
|
end
|
596
|
+
|
597
|
+
# Validates the given constraint.
|
598
|
+
#
|
599
|
+
# Validates the constraint named +constraint_name+ on +accounts+.
|
600
|
+
#
|
601
|
+
# validate_constraint :accounts, :constraint_name
|
602
|
+
def validate_constraint(table_name, constraint_name)
|
603
|
+
return unless supports_validate_constraints?
|
604
|
+
|
605
|
+
at = create_alter_table table_name
|
606
|
+
at.validate_constraint constraint_name
|
607
|
+
|
608
|
+
execute schema_creation.accept(at)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Validates the given foreign key.
|
612
|
+
#
|
613
|
+
# Validates the foreign key on +accounts.branch_id+.
|
614
|
+
#
|
615
|
+
# validate_foreign_key :accounts, :branches
|
616
|
+
#
|
617
|
+
# Validates the foreign key on +accounts.owner_id+.
|
618
|
+
#
|
619
|
+
# validate_foreign_key :accounts, column: :owner_id
|
620
|
+
#
|
621
|
+
# Validates the foreign key named +special_fk_name+ on the +accounts+ table.
|
622
|
+
#
|
623
|
+
# validate_foreign_key :accounts, name: :special_fk_name
|
624
|
+
#
|
625
|
+
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
|
626
|
+
def validate_foreign_key(from_table, options_or_to_table = {})
|
627
|
+
return unless supports_validate_constraints?
|
628
|
+
|
629
|
+
fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
|
630
|
+
|
631
|
+
validate_constraint from_table, fk_name_to_validate
|
632
|
+
end
|
633
|
+
|
634
|
+
private
|
635
|
+
def schema_creation
|
636
|
+
PostgreSQL::SchemaCreation.new(self)
|
637
|
+
end
|
638
|
+
|
639
|
+
def create_table_definition(*args)
|
640
|
+
PostgreSQL::TableDefinition.new(*args)
|
641
|
+
end
|
642
|
+
|
643
|
+
def create_alter_table(name)
|
644
|
+
PostgreSQL::AlterTable.new create_table_definition(name)
|
645
|
+
end
|
646
|
+
|
647
|
+
def new_column_from_field(table_name, field)
|
648
|
+
column_name, type, default, notnull, oid, fmod, collation, comment = field
|
649
|
+
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
650
|
+
default_value = extract_value_from_default(default)
|
651
|
+
default_function = extract_default_function(default_value, default)
|
652
|
+
|
653
|
+
PostgreSQLColumn.new(
|
654
|
+
column_name,
|
655
|
+
default_value,
|
656
|
+
type_metadata,
|
657
|
+
!notnull,
|
658
|
+
table_name,
|
659
|
+
default_function,
|
660
|
+
collation,
|
661
|
+
comment: comment.presence,
|
662
|
+
max_identifier_length: max_identifier_length
|
663
|
+
)
|
664
|
+
end
|
665
|
+
|
666
|
+
def fetch_type_metadata(column_name, sql_type, oid, fmod)
|
667
|
+
cast_type = get_oid_type(oid, fmod, column_name, sql_type)
|
668
|
+
simple_type = SqlTypeMetadata.new(
|
669
|
+
sql_type: sql_type,
|
670
|
+
type: cast_type.type,
|
671
|
+
limit: cast_type.limit,
|
672
|
+
precision: cast_type.precision,
|
673
|
+
scale: cast_type.scale,
|
674
|
+
)
|
675
|
+
PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
|
676
|
+
end
|
677
|
+
|
678
|
+
def extract_foreign_key_action(specifier)
|
679
|
+
case specifier
|
680
|
+
when "c"; :cascade
|
681
|
+
when "n"; :nullify
|
682
|
+
when "r"; :restrict
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def add_column_for_alter(table_name, column_name, type, options = {})
|
687
|
+
return super unless options.key?(:comment)
|
688
|
+
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
689
|
+
end
|
690
|
+
|
691
|
+
def change_column_for_alter(table_name, column_name, type, options = {})
|
692
|
+
td = create_table_definition(table_name)
|
693
|
+
cd = td.new_column_definition(column_name, type, options)
|
694
|
+
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
695
|
+
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
696
|
+
sqls
|
697
|
+
end
|
698
|
+
|
699
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
700
|
+
column = column_for(table_name, column_name)
|
701
|
+
return unless column
|
702
|
+
|
703
|
+
default = extract_new_default_value(default_or_changes)
|
704
|
+
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
|
705
|
+
if default.nil?
|
706
|
+
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
|
707
|
+
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
|
708
|
+
alter_column_query % "DROP DEFAULT"
|
709
|
+
else
|
710
|
+
alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
715
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
716
|
+
end
|
717
|
+
|
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
|
+
def add_index_opclass(quoted_columns, **options)
|
727
|
+
opclasses = options_for_index_columns(options[:opclass])
|
728
|
+
quoted_columns.each do |name, column|
|
729
|
+
column << " #{opclasses[name]}" if opclasses[name].present?
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
def add_options_for_index_columns(quoted_columns, **options)
|
734
|
+
quoted_columns = add_index_opclass(quoted_columns, options)
|
735
|
+
super
|
736
|
+
end
|
737
|
+
|
738
|
+
def data_source_sql(name = nil, type: nil)
|
739
|
+
scope = quoted_scope(name, type: type)
|
740
|
+
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
|
741
|
+
|
742
|
+
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
|
743
|
+
sql << " WHERE n.nspname = #{scope[:schema]}"
|
744
|
+
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
|
745
|
+
sql << " AND c.relkind IN (#{scope[:type]})"
|
746
|
+
sql
|
747
|
+
end
|
748
|
+
|
749
|
+
def quoted_scope(name = nil, type: nil)
|
750
|
+
schema, name = extract_schema_qualified_name(name)
|
751
|
+
type = \
|
752
|
+
case type
|
753
|
+
when "BASE TABLE"
|
754
|
+
"'r','p'"
|
755
|
+
when "VIEW"
|
756
|
+
"'v','m'"
|
757
|
+
when "FOREIGN TABLE"
|
758
|
+
"'f'"
|
759
|
+
end
|
760
|
+
scope = {}
|
761
|
+
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
|
762
|
+
scope[:name] = quote(name) if name
|
763
|
+
scope[:type] = type if type
|
764
|
+
scope
|
765
|
+
end
|
766
|
+
|
767
|
+
def extract_schema_qualified_name(string)
|
768
|
+
name = Utils.extract_schema_qualified_name(string.to_s)
|
769
|
+
[name.schema, name.identifier]
|
770
|
+
end
|
585
771
|
end
|
586
772
|
end
|
587
773
|
end
|