activerecord 3.2.22.5 → 5.2.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -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