activerecord 5.2.3

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.

Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module ReferentialIntegrity # :nodoc:
7
+ def disable_referential_integrity # :nodoc:
8
+ original_exception = nil
9
+
10
+ begin
11
+ transaction(requires_new: true) do
12
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
13
+ end
14
+ rescue ActiveRecord::ActiveRecordError => e
15
+ original_exception = e
16
+ end
17
+
18
+ begin
19
+ yield
20
+ rescue ActiveRecord::InvalidForeignKey => e
21
+ warn <<-WARNING
22
+ WARNING: Rails was not able to disable referential integrity.
23
+
24
+ This is most likely caused due to missing permissions.
25
+ Rails needs superuser privileges to disable referential integrity.
26
+
27
+ cause: #{original_exception.try(:message)}
28
+
29
+ WARNING
30
+ raise e
31
+ end
32
+
33
+ begin
34
+ transaction(requires_new: true) do
35
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
36
+ end
37
+ rescue ActiveRecord::ActiveRecordError
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
7
+ private
8
+ def visit_AlterTable(o)
9
+ super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
10
+ end
11
+
12
+ def visit_AddForeignKey(o)
13
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
14
+ end
15
+
16
+ def visit_ValidateConstraint(name)
17
+ "VALIDATE CONSTRAINT #{quote_column_name(name)}"
18
+ end
19
+
20
+ def visit_ChangeColumnDefinition(o)
21
+ column = o.column
22
+ column.sql_type = type_to_sql(column.type, column.options)
23
+ quoted_column_name = quote_column_name(o.name)
24
+
25
+ change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
26
+
27
+ options = column_options(column)
28
+
29
+ if options[:collation]
30
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
31
+ end
32
+
33
+ if options[:using]
34
+ change_column_sql << " USING #{options[:using]}"
35
+ elsif options[:cast_as]
36
+ cast_as_type = type_to_sql(options[:cast_as], options)
37
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
38
+ end
39
+
40
+ if options.key?(:default)
41
+ if options[:default].nil?
42
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
43
+ else
44
+ quoted_default = quote_default_expression(options[:default], column)
45
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
46
+ end
47
+ end
48
+
49
+ if options.key?(:null)
50
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
51
+ end
52
+
53
+ change_column_sql
54
+ end
55
+
56
+ def add_column_options!(sql, options)
57
+ if options[:collation]
58
+ sql << " COLLATE \"#{options[:collation]}\""
59
+ end
60
+ super
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module ColumnMethods
7
+ # Defines the primary key field.
8
+ # Use of the native PostgreSQL UUID type is supported, and can be used
9
+ # by defining your tables as such:
10
+ #
11
+ # create_table :stuffs, id: :uuid do |t|
12
+ # t.string :content
13
+ # t.timestamps
14
+ # end
15
+ #
16
+ # By default, this will use the +gen_random_uuid()+ function from the
17
+ # +pgcrypto+ extension. As that extension is only available in
18
+ # PostgreSQL 9.4+, for earlier versions an explicit default can be set
19
+ # to use +uuid_generate_v4()+ from the +uuid-ossp+ extension instead:
20
+ #
21
+ # create_table :stuffs, id: false do |t|
22
+ # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
23
+ # t.uuid :foo_id
24
+ # t.timestamps
25
+ # end
26
+ #
27
+ # To enable the appropriate extension, which is a requirement, use
28
+ # the +enable_extension+ method in your migrations.
29
+ #
30
+ # To use a UUID primary key without any of the extensions, set the
31
+ # +:default+ option to +nil+:
32
+ #
33
+ # create_table :stuffs, id: false do |t|
34
+ # t.primary_key :id, :uuid, default: nil
35
+ # t.uuid :foo_id
36
+ # t.timestamps
37
+ # end
38
+ #
39
+ # You may also pass a custom stored procedure that returns a UUID or use a
40
+ # different UUID generation function from another library.
41
+ #
42
+ # Note that setting the UUID primary key default value to +nil+ will
43
+ # require you to assure that you always provide a UUID value before saving
44
+ # a record (as primary keys cannot be +nil+). This might be done via the
45
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
46
+ def primary_key(name, type = :primary_key, **options)
47
+ if type == :uuid
48
+ options[:default] = options.fetch(:default, "gen_random_uuid()")
49
+ end
50
+
51
+ super
52
+ end
53
+
54
+ def bigserial(*args, **options)
55
+ args.each { |name| column(name, :bigserial, options) }
56
+ end
57
+
58
+ def bit(*args, **options)
59
+ args.each { |name| column(name, :bit, options) }
60
+ end
61
+
62
+ def bit_varying(*args, **options)
63
+ args.each { |name| column(name, :bit_varying, options) }
64
+ end
65
+
66
+ def cidr(*args, **options)
67
+ args.each { |name| column(name, :cidr, options) }
68
+ end
69
+
70
+ def citext(*args, **options)
71
+ args.each { |name| column(name, :citext, options) }
72
+ end
73
+
74
+ def daterange(*args, **options)
75
+ args.each { |name| column(name, :daterange, options) }
76
+ end
77
+
78
+ def hstore(*args, **options)
79
+ args.each { |name| column(name, :hstore, options) }
80
+ end
81
+
82
+ def inet(*args, **options)
83
+ args.each { |name| column(name, :inet, options) }
84
+ end
85
+
86
+ def interval(*args, **options)
87
+ args.each { |name| column(name, :interval, options) }
88
+ end
89
+
90
+ def int4range(*args, **options)
91
+ args.each { |name| column(name, :int4range, options) }
92
+ end
93
+
94
+ def int8range(*args, **options)
95
+ args.each { |name| column(name, :int8range, options) }
96
+ end
97
+
98
+ def jsonb(*args, **options)
99
+ args.each { |name| column(name, :jsonb, options) }
100
+ end
101
+
102
+ def ltree(*args, **options)
103
+ args.each { |name| column(name, :ltree, options) }
104
+ end
105
+
106
+ def macaddr(*args, **options)
107
+ args.each { |name| column(name, :macaddr, options) }
108
+ end
109
+
110
+ def money(*args, **options)
111
+ args.each { |name| column(name, :money, options) }
112
+ end
113
+
114
+ def numrange(*args, **options)
115
+ args.each { |name| column(name, :numrange, options) }
116
+ end
117
+
118
+ def oid(*args, **options)
119
+ args.each { |name| column(name, :oid, options) }
120
+ end
121
+
122
+ def point(*args, **options)
123
+ args.each { |name| column(name, :point, options) }
124
+ end
125
+
126
+ def line(*args, **options)
127
+ args.each { |name| column(name, :line, options) }
128
+ end
129
+
130
+ def lseg(*args, **options)
131
+ args.each { |name| column(name, :lseg, options) }
132
+ end
133
+
134
+ def box(*args, **options)
135
+ args.each { |name| column(name, :box, options) }
136
+ end
137
+
138
+ def path(*args, **options)
139
+ args.each { |name| column(name, :path, options) }
140
+ end
141
+
142
+ def polygon(*args, **options)
143
+ args.each { |name| column(name, :polygon, options) }
144
+ end
145
+
146
+ def circle(*args, **options)
147
+ args.each { |name| column(name, :circle, options) }
148
+ end
149
+
150
+ def serial(*args, **options)
151
+ args.each { |name| column(name, :serial, options) }
152
+ end
153
+
154
+ def tsrange(*args, **options)
155
+ args.each { |name| column(name, :tsrange, options) }
156
+ end
157
+
158
+ def tstzrange(*args, **options)
159
+ args.each { |name| column(name, :tstzrange, options) }
160
+ end
161
+
162
+ def tsvector(*args, **options)
163
+ args.each { |name| column(name, :tsvector, options) }
164
+ end
165
+
166
+ def uuid(*args, **options)
167
+ args.each { |name| column(name, :uuid, options) }
168
+ end
169
+
170
+ def xml(*args, **options)
171
+ args.each { |name| column(name, :xml, options) }
172
+ end
173
+ end
174
+
175
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
176
+ include ColumnMethods
177
+
178
+ private
179
+ def integer_like_primary_key_type(type, options)
180
+ if type == :bigint || options[:limit] == 8
181
+ :bigserial
182
+ else
183
+ :serial
184
+ end
185
+ end
186
+ end
187
+
188
+ class Table < ActiveRecord::ConnectionAdapters::Table
189
+ include ColumnMethods
190
+ end
191
+
192
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
193
+ attr_reader :constraint_validations
194
+
195
+ def initialize(td)
196
+ super
197
+ @constraint_validations = []
198
+ end
199
+
200
+ def validate_constraint(name)
201
+ @constraint_validations << name
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
+ private
8
+
9
+ def extensions(stream)
10
+ extensions = @connection.extensions
11
+ if extensions.any?
12
+ stream.puts " # These are extensions that must be enabled in order to support this database"
13
+ extensions.sort.each do |extension|
14
+ stream.puts " enable_extension #{extension.inspect}"
15
+ end
16
+ stream.puts
17
+ end
18
+ end
19
+
20
+ def prepare_column_options(column)
21
+ spec = super
22
+ spec[:array] = "true" if column.array?
23
+ spec
24
+ end
25
+
26
+ def default_primary_key?(column)
27
+ schema_type(column) == :bigserial
28
+ end
29
+
30
+ def explicit_primary_key_default?(column)
31
+ column.type == :uuid || (column.type == :integer && !column.serial?)
32
+ end
33
+
34
+ def schema_type(column)
35
+ return super unless column.serial?
36
+
37
+ if column.bigint?
38
+ :bigserial
39
+ else
40
+ :serial
41
+ end
42
+ end
43
+
44
+ def schema_expression(column)
45
+ super unless column.serial?
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,774 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module SchemaStatements
7
+ # Drops the database specified on the +name+ attribute
8
+ # and creates it again using the provided +options+.
9
+ def recreate_database(name, options = {}) #:nodoc:
10
+ drop_database(name)
11
+ create_database(name, options)
12
+ end
13
+
14
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
15
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
16
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
17
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
18
+ #
19
+ # Example:
20
+ # create_database config[:database], config
21
+ # create_database 'foo_development', encoding: 'unicode'
22
+ def create_database(name, options = {})
23
+ options = { encoding: "utf8" }.merge!(options.symbolize_keys)
24
+
25
+ option_string = options.inject("") do |memo, (key, value)|
26
+ memo += case key
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
+ ""
43
+ end
44
+ end
45
+
46
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
47
+ end
48
+
49
+ # Drops a PostgreSQL database.
50
+ #
51
+ # Example:
52
+ # drop_database 'matt_development'
53
+ def drop_database(name) #:nodoc:
54
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
+ end
56
+
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}"
59
+ end
60
+
61
+ # Returns true if schema exists.
62
+ def schema_exists?(name)
63
+ query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
64
+ end
65
+
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
72
+ SELECT COUNT(*)
73
+ FROM pg_class t
74
+ INNER JOIN pg_index d ON t.oid = d.indrelid
75
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
76
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
77
+ WHERE i.relkind = 'i'
78
+ AND i.relname = #{index[:name]}
79
+ AND t.relname = #{table[:name]}
80
+ AND n.nspname = #{index[:schema]}
81
+ SQL
82
+ end
83
+
84
+ # Returns an array of indexes for the given table.
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]}
99
+ ORDER BY i.relname
100
+ SQL
101
+
102
+ result.map do |row|
103
+ index_name = row[0]
104
+ unique = row[1]
105
+ indkey = row[2].split(" ").map(&:to_i)
106
+ inddef = row[3]
107
+ oid = row[4]
108
+ comment = row[5]
109
+
110
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
111
+
112
+ orders = {}
113
+ opclasses = {}
114
+
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
135
+ end
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
149
+ end
150
+
151
+ def table_options(table_name) # :nodoc:
152
+ if comment = table_comment(table_name)
153
+ { comment: comment }
154
+ end
155
+ end
156
+
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
170
+ end
171
+
172
+ # Returns the current database name.
173
+ def current_database
174
+ query_value("SELECT current_database()", "SCHEMA")
175
+ end
176
+
177
+ # Returns the current schema name.
178
+ def current_schema
179
+ query_value("SELECT current_schema", "SCHEMA")
180
+ end
181
+
182
+ # Returns the current database encoding format.
183
+ def encoding
184
+ query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
185
+ end
186
+
187
+ # Returns the current database collation.
188
+ def collation
189
+ query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
190
+ end
191
+
192
+ # Returns the current database ctype.
193
+ def ctype
194
+ query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
195
+ end
196
+
197
+ # Returns an array of schema names.
198
+ def schema_names
199
+ query_values(<<-SQL, "SCHEMA")
200
+ SELECT nspname
201
+ FROM pg_namespace
202
+ WHERE nspname !~ '^pg_.*'
203
+ AND nspname NOT IN ('information_schema')
204
+ ORDER by nspname;
205
+ SQL
206
+ end
207
+
208
+ # Creates a schema for the given schema name.
209
+ def create_schema(schema_name)
210
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
211
+ end
212
+
213
+ # Drops the schema for the given schema name.
214
+ def drop_schema(schema_name, options = {})
215
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
216
+ end
217
+
218
+ # Sets the schema search path to a string of comma-separated schema names.
219
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
220
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
221
+ #
222
+ # This should be not be called manually but set in database.yml.
223
+ def schema_search_path=(schema_csv)
224
+ if schema_csv
225
+ execute("SET search_path TO #{schema_csv}", "SCHEMA")
226
+ @schema_search_path = schema_csv
227
+ end
228
+ end
229
+
230
+ # Returns the active schema search path.
231
+ def schema_search_path
232
+ @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
233
+ end
234
+
235
+ # Returns the current client message level.
236
+ def client_min_messages
237
+ query_value("SHOW client_min_messages", "SCHEMA")
238
+ end
239
+
240
+ # Set the client message level.
241
+ def client_min_messages=(level)
242
+ execute("SET client_min_messages TO '#{level}'", "SCHEMA")
243
+ end
244
+
245
+ # Returns the sequence name for a table's primary key or some other specified key.
246
+ def default_sequence_name(table_name, pk = "id") #:nodoc:
247
+ result = serial_sequence(table_name, pk)
248
+ return nil unless result
249
+ Utils.extract_schema_qualified_name(result).to_s
250
+ rescue ActiveRecord::StatementInvalid
251
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
252
+ end
253
+
254
+ def serial_sequence(table, column)
255
+ query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
256
+ end
257
+
258
+ # Sets the sequence of a table's primary key to the specified value.
259
+ def set_pk_sequence!(table, value) #:nodoc:
260
+ pk, sequence = pk_and_sequence_for(table)
261
+
262
+ if pk
263
+ if sequence
264
+ quoted_sequence = quote_table_name(sequence)
265
+
266
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
267
+ else
268
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
269
+ end
270
+ end
271
+ end
272
+
273
+ # Resets the sequence of a table's primary key to the maximum value.
274
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
275
+ unless pk && sequence
276
+ default_pk, default_sequence = pk_and_sequence_for(table)
277
+
278
+ pk ||= default_pk
279
+ sequence ||= default_sequence
280
+ end
281
+
282
+ if @logger && pk && !sequence
283
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
284
+ end
285
+
286
+ if pk && sequence
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
296
+
297
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
298
+ end
299
+ end
300
+
301
+ # Returns a table's primary key and belonging sequence.
302
+ def pk_and_sequence_for(table) #:nodoc:
303
+ # First try looking for a sequence with a dependency on the
304
+ # given table's primary key.
305
+ result = query(<<-end_sql, "SCHEMA")[0]
306
+ SELECT attr.attname, nsp.nspname, seq.relname
307
+ FROM pg_class seq,
308
+ pg_attribute attr,
309
+ pg_depend dep,
310
+ pg_constraint cons,
311
+ pg_namespace nsp
312
+ WHERE seq.oid = dep.objid
313
+ AND seq.relkind = 'S'
314
+ AND attr.attrelid = dep.refobjid
315
+ AND attr.attnum = dep.refobjsubid
316
+ AND attr.attrelid = cons.conrelid
317
+ AND attr.attnum = cons.conkey[1]
318
+ AND seq.relnamespace = nsp.oid
319
+ AND cons.contype = 'p'
320
+ AND dep.classid = 'pg_class'::regclass
321
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
322
+ end_sql
323
+
324
+ if result.nil? || result.empty?
325
+ result = query(<<-end_sql, "SCHEMA")[0]
326
+ SELECT attr.attname, nsp.nspname,
327
+ CASE
328
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
329
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
330
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
331
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
332
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
333
+ END
334
+ FROM pg_class t
335
+ JOIN pg_attribute attr ON (t.oid = attrelid)
336
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
337
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
338
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
339
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
340
+ AND cons.contype = 'p'
341
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
342
+ end_sql
343
+ end
344
+
345
+ pk = result.shift
346
+ if result.last
347
+ [pk, PostgreSQL::Name.new(*result)]
348
+ else
349
+ [pk, nil]
350
+ end
351
+ rescue
352
+ nil
353
+ end
354
+
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
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
+ # Renames a table.
397
+ # Also renames a table's primary key sequence if the sequence name exists and
398
+ # matches the Active Record default.
399
+ #
400
+ # Example:
401
+ # rename_table('octopuses', 'octopi')
402
+ def rename_table(table_name, new_name)
403
+ clear_cache!
404
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
405
+ pk, seq = pk_and_sequence_for(new_name)
406
+ if pk
407
+ idx = "#{table_name}_pkey"
408
+ new_idx = "#{new_name}_pkey"
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
414
+ end
415
+ rename_table_indexes(table_name, new_name)
416
+ end
417
+
418
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
419
+ clear_cache!
420
+ super
421
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
422
+ end
423
+
424
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
425
+ clear_cache!
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)
429
+ end
430
+
431
+ # Changes the default value of a table column.
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)}"
434
+ end
435
+
436
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
437
+ clear_cache!
438
+ unless null || default.nil?
439
+ column = column_for(table_name, column_name)
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
441
+ end
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)}"
455
+ end
456
+
457
+ # Renames a column in a table.
458
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
459
+ clear_cache!
460
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
461
+ rename_column_indexes(table_name, column_name, new_column_name)
462
+ end
463
+
464
+ def add_index(table_name, column_name, options = {}) #:nodoc:
465
+ index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
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
469
+ end
470
+
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)}"
493
+ end
494
+
495
+ # Renames an index of a table. Raises error if length of new
496
+ # index name is greater than allowed limit.
497
+ def rename_index(table_name, old_name, new_name)
498
+ validate_index_length!(table_name, new_name)
499
+
500
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
501
+ end
502
+
503
+ def foreign_keys(table_name)
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
507
+ FROM pg_constraint c
508
+ JOIN pg_class t1 ON c.conrelid = t1.oid
509
+ JOIN pg_class t2 ON c.confrelid = t2.oid
510
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
511
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
512
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
513
+ WHERE c.contype = 'f'
514
+ AND t1.relname = #{scope[:name]}
515
+ AND t3.nspname = #{scope[:schema]}
516
+ ORDER BY c.conname
517
+ SQL
518
+
519
+ fk_info.map do |row|
520
+ options = {
521
+ column: row["column"],
522
+ name: row["name"],
523
+ primary_key: row["primary_key"]
524
+ }
525
+
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"]
529
+
530
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
531
+ end
532
+ end
533
+
534
+ def foreign_tables
535
+ query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
536
+ end
537
+
538
+ def foreign_table_exists?(table_name)
539
+ query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
540
+ end
541
+
542
+ # Maps logical Rails types to PostgreSQL-specific data types.
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
569
+ end
570
+
571
+ sql = "#{sql}[]" if array && type != :primary_key
572
+ sql
573
+ end
574
+
575
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
576
+ # requires that the ORDER BY include the distinct column.
577
+ def columns_for_distinct(columns, orders) #:nodoc:
578
+ order_columns = orders.reject(&:blank?).map { |s|
579
+ # Convert Arel node to string
580
+ s = s.to_sql unless s.is_a?(String)
581
+ # Remove any ASC/DESC modifiers
582
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
583
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
584
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
585
+
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)
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
771
+ end
772
+ end
773
+ end
774
+ end