activerecord 3.2.19 → 5.0.0

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