activerecord 4.2.6 → 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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1307 -1105
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +3 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +11 -9
  11. data/lib/active_record/associations/association_scope.rb +73 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +50 -31
  21. data/lib/active_record/associations/collection_proxy.rb +69 -29
  22. data/lib/active_record/associations/foreign_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_association.rb +20 -71
  24. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  25. data/lib/active_record/associations/has_one_association.rb +12 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +29 -19
  28. data/lib/active_record/associations/preloader/association.rb +46 -52
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +14 -4
  34. data/lib/active_record/associations/singular_association.rb +7 -1
  35. data/lib/active_record/associations/through_association.rb +11 -3
  36. data/lib/active_record/associations.rb +317 -209
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +6 -4
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attributes.rb +199 -80
  54. data/lib/active_record/autosave_association.rb +49 -16
  55. data/lib/active_record/base.rb +32 -23
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -140
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -59
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +405 -362
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -176
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  100. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  105. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  106. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  107. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  108. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  110. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  111. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  112. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  113. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  114. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  115. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  117. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +148 -203
  118. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  119. data/lib/active_record/connection_handling.rb +37 -14
  120. data/lib/active_record/core.rb +89 -107
  121. data/lib/active_record/counter_cache.rb +13 -24
  122. data/lib/active_record/dynamic_matchers.rb +1 -20
  123. data/lib/active_record/enum.rb +113 -76
  124. data/lib/active_record/errors.rb +87 -48
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +26 -5
  128. data/lib/active_record/fixtures.rb +76 -40
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +32 -40
  131. data/lib/active_record/integration.rb +4 -4
  132. data/lib/active_record/internal_metadata.rb +56 -0
  133. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  134. data/lib/active_record/locale/en.yml +3 -2
  135. data/lib/active_record/locking/optimistic.rb +15 -15
  136. data/lib/active_record/locking/pessimistic.rb +1 -1
  137. data/lib/active_record/log_subscriber.rb +43 -21
  138. data/lib/active_record/migration/command_recorder.rb +59 -18
  139. data/lib/active_record/migration/compatibility.rb +126 -0
  140. data/lib/active_record/migration.rb +364 -109
  141. data/lib/active_record/model_schema.rb +128 -38
  142. data/lib/active_record/nested_attributes.rb +58 -29
  143. data/lib/active_record/null_relation.rb +16 -8
  144. data/lib/active_record/persistence.rb +121 -80
  145. data/lib/active_record/query_cache.rb +15 -18
  146. data/lib/active_record/querying.rb +10 -9
  147. data/lib/active_record/railtie.rb +27 -18
  148. data/lib/active_record/railties/controller_runtime.rb +1 -1
  149. data/lib/active_record/railties/databases.rake +58 -45
  150. data/lib/active_record/readonly_attributes.rb +1 -1
  151. data/lib/active_record/reflection.rb +282 -115
  152. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  153. data/lib/active_record/relation/batches.rb +139 -34
  154. data/lib/active_record/relation/calculations.rb +80 -102
  155. data/lib/active_record/relation/delegation.rb +7 -20
  156. data/lib/active_record/relation/finder_methods.rb +163 -81
  157. data/lib/active_record/relation/from_clause.rb +32 -0
  158. data/lib/active_record/relation/merger.rb +16 -42
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -15
  160. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  163. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  166. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  167. data/lib/active_record/relation/predicate_builder.rb +120 -107
  168. data/lib/active_record/relation/query_attribute.rb +19 -0
  169. data/lib/active_record/relation/query_methods.rb +308 -244
  170. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  171. data/lib/active_record/relation/spawn_methods.rb +4 -7
  172. data/lib/active_record/relation/where_clause.rb +174 -0
  173. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  174. data/lib/active_record/relation.rb +176 -116
  175. data/lib/active_record/result.rb +4 -3
  176. data/lib/active_record/runtime_registry.rb +1 -1
  177. data/lib/active_record/sanitization.rb +95 -66
  178. data/lib/active_record/schema.rb +26 -22
  179. data/lib/active_record/schema_dumper.rb +62 -38
  180. data/lib/active_record/schema_migration.rb +11 -17
  181. data/lib/active_record/scoping/default.rb +23 -9
  182. data/lib/active_record/scoping/named.rb +49 -28
  183. data/lib/active_record/scoping.rb +32 -15
  184. data/lib/active_record/secure_token.rb +38 -0
  185. data/lib/active_record/serialization.rb +2 -4
  186. data/lib/active_record/statement_cache.rb +16 -14
  187. data/lib/active_record/store.rb +8 -3
  188. data/lib/active_record/suppressor.rb +58 -0
  189. data/lib/active_record/table_metadata.rb +68 -0
  190. data/lib/active_record/tasks/database_tasks.rb +58 -41
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -20
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  194. data/lib/active_record/timestamp.rb +20 -9
  195. data/lib/active_record/touch_later.rb +58 -0
  196. data/lib/active_record/transactions.rb +138 -56
  197. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  198. data/lib/active_record/type/date.rb +2 -41
  199. data/lib/active_record/type/date_time.rb +2 -49
  200. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  201. data/lib/active_record/type/internal/timezone.rb +15 -0
  202. data/lib/active_record/type/serialized.rb +15 -14
  203. data/lib/active_record/type/time.rb +10 -16
  204. data/lib/active_record/type/type_map.rb +4 -4
  205. data/lib/active_record/type.rb +66 -17
  206. data/lib/active_record/type_caster/connection.rb +29 -0
  207. data/lib/active_record/type_caster/map.rb +19 -0
  208. data/lib/active_record/type_caster.rb +7 -0
  209. data/lib/active_record/validations/absence.rb +23 -0
  210. data/lib/active_record/validations/associated.rb +10 -3
  211. data/lib/active_record/validations/length.rb +24 -0
  212. data/lib/active_record/validations/presence.rb +11 -12
  213. data/lib/active_record/validations/uniqueness.rb +30 -29
  214. data/lib/active_record/validations.rb +33 -32
  215. data/lib/active_record.rb +7 -2
  216. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  217. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  218. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  219. data/lib/rails/generators/active_record/migration.rb +7 -0
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +58 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -50
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. data/lib/active_record/type/value.rb +0 -105
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
@@ -5,33 +7,15 @@ module ActiveRecord
5
7
  private
6
8
 
7
9
  def visit_ColumnDefinition(o)
8
- sql = super
9
- if o.primary_key? && o.type != :primary_key
10
- sql << " PRIMARY KEY "
11
- add_column_options!(sql, column_options(o))
12
- end
13
- sql
10
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
11
+ super
14
12
  end
15
13
 
16
14
  def add_column_options!(sql, options)
17
- if options[:array] || options[:column].try(:array)
18
- sql << '[]'
19
- end
20
-
21
- column = options.fetch(:column) { return super }
22
- if column.type == :uuid && options[:default] =~ /\(\)/
23
- sql << " DEFAULT #{options[:default]}"
24
- else
25
- super
26
- end
27
- end
28
-
29
- def type_for_column(column)
30
- if column.array
31
- @conn.lookup_cast_type("#{column.sql_type}[]")
32
- else
33
- super
15
+ if options[:collation]
16
+ sql << " COLLATE \"#{options[:collation]}\""
34
17
  end
18
+ super
35
19
  end
36
20
  end
37
21
 
@@ -88,11 +72,13 @@ module ActiveRecord
88
72
 
89
73
  # Returns the list of all tables in the schema search path.
90
74
  def tables(name = nil)
91
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
92
- SELECT tablename
93
- FROM pg_tables
94
- WHERE schemaname = ANY (current_schemas(false))
95
- SQL
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')
96
82
  end
97
83
 
98
84
  def data_sources # :nodoc
@@ -109,10 +95,20 @@ module ActiveRecord
109
95
  # If the schema is not specified as part of +name+ then it will only find tables within
110
96
  # the current schema search path (regardless of permissions to access tables in other schemas)
111
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)
112
108
  name = Utils.extract_schema_qualified_name(name.to_s)
113
109
  return false unless name.identifier
114
110
 
115
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
116
112
  SELECT COUNT(*)
117
113
  FROM pg_class c
118
114
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -121,126 +117,170 @@ module ActiveRecord
121
117
  AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
122
118
  SQL
123
119
  end
124
- alias data_source_exists? table_exists?
125
120
 
126
- def drop_table(table_name, options = {})
127
- execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
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}"
128
147
  end
129
148
 
130
149
  # Returns true if schema exists.
131
150
  def schema_exists?(name)
132
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
133
- SELECT COUNT(*)
134
- FROM pg_namespace
135
- WHERE nspname = '#{name}'
136
- SQL
151
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
137
152
  end
138
153
 
154
+ # Verifies existence of an index with a given name.
139
155
  def index_name_exists?(table_name, index_name, default)
140
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
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
141
160
  SELECT COUNT(*)
142
161
  FROM pg_class t
143
162
  INNER JOIN pg_index d ON t.oid = d.indrelid
144
163
  INNER JOIN pg_class i ON d.indexrelid = i.oid
164
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
145
165
  WHERE i.relkind = 'i'
146
- AND i.relname = '#{index_name}'
147
- AND t.relname = '#{table_name}'
148
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
166
+ AND i.relname = '#{index.identifier}'
167
+ AND t.relname = '#{table.identifier}'
168
+ AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
149
169
  SQL
150
170
  end
151
171
 
152
172
  # Returns an array of indexes for the given table.
153
173
  def indexes(table_name, name = nil)
154
- result = query(<<-SQL, 'SCHEMA')
155
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
156
- FROM pg_class t
157
- INNER JOIN pg_index d ON t.oid = d.indrelid
158
- INNER JOIN pg_class i ON d.indexrelid = i.oid
159
- WHERE i.relkind = 'i'
160
- AND d.indisprimary = 'f'
161
- AND t.relname = '#{table_name}'
162
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
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))'}
163
190
  ORDER BY i.relname
164
191
  SQL
165
192
 
166
193
  result.map do |row|
167
194
  index_name = row[0]
168
- unique = row[1] == 't'
169
- indkey = row[2].split(" ")
195
+ unique = row[1]
196
+ indkey = row[2].split(" ").map(&:to_i)
170
197
  inddef = row[3]
171
198
  oid = row[4]
199
+ comment = row[5]
200
+ opclass = row[6]
172
201
 
173
- columns = Hash[query(<<-SQL, "SCHEMA")]
174
- SELECT a.attnum, a.attname
175
- FROM pg_attribute a
176
- WHERE a.attrelid = #{oid}
177
- AND a.attnum IN (#{indkey.join(",")})
178
- SQL
202
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
179
203
 
180
- column_names = columns.values_at(*indkey).compact
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
181
213
 
182
- unless column_names.empty?
183
214
  # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
184
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
185
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
186
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
187
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
188
-
189
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
215
+ orders = Hash[
216
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
217
+ ]
190
218
  end
219
+
220
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
191
221
  end.compact
192
222
  end
193
223
 
194
224
  # Returns the list of all column definitions for a table.
195
- def columns(table_name)
196
- # Limit, precision, and scale are all handled by the superclass.
197
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
198
- oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
199
- default_value = extract_value_from_default(oid, default)
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)
200
232
  default_function = extract_default_function(default_value, default)
201
- new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
233
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
202
234
  end
203
235
  end
204
236
 
205
- def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
206
- PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
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
207
254
  end
208
255
 
209
256
  # Returns the current database name.
210
257
  def current_database
211
- query('select current_database()', 'SCHEMA')[0][0]
258
+ select_value('select current_database()', 'SCHEMA')
212
259
  end
213
260
 
214
261
  # Returns the current schema name.
215
262
  def current_schema
216
- query('SELECT current_schema', 'SCHEMA')[0][0]
263
+ select_value('SELECT current_schema', 'SCHEMA')
217
264
  end
218
265
 
219
266
  # Returns the current database encoding format.
220
267
  def encoding
221
- query(<<-end_sql, 'SCHEMA')[0][0]
222
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
223
- WHERE pg_database.datname LIKE '#{current_database}'
224
- end_sql
268
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
225
269
  end
226
270
 
227
271
  # Returns the current database collation.
228
272
  def collation
229
- query(<<-end_sql, 'SCHEMA')[0][0]
230
- SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
231
- end_sql
273
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
232
274
  end
233
275
 
234
276
  # Returns the current database ctype.
235
277
  def ctype
236
- query(<<-end_sql, 'SCHEMA')[0][0]
237
- SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
238
- end_sql
278
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
239
279
  end
240
280
 
241
281
  # Returns an array of schema names.
242
282
  def schema_names
243
- query(<<-SQL, 'SCHEMA').flatten
283
+ select_values(<<-SQL, 'SCHEMA')
244
284
  SELECT nspname
245
285
  FROM pg_namespace
246
286
  WHERE nspname !~ '^pg_.*'
@@ -251,12 +291,12 @@ module ActiveRecord
251
291
 
252
292
  # Creates a schema for the given schema name.
253
293
  def create_schema schema_name
254
- execute "CREATE SCHEMA #{schema_name}"
294
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
255
295
  end
256
296
 
257
297
  # Drops the schema for the given schema name.
258
- def drop_schema schema_name
259
- execute "DROP SCHEMA #{schema_name} CASCADE"
298
+ def drop_schema(schema_name, options = {})
299
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
260
300
  end
261
301
 
262
302
  # Sets the schema search path to a string of comma-separated schema names.
@@ -273,12 +313,12 @@ module ActiveRecord
273
313
 
274
314
  # Returns the active schema search path.
275
315
  def schema_search_path
276
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
316
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
277
317
  end
278
318
 
279
319
  # Returns the current client message level.
280
320
  def client_min_messages
281
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
321
+ select_value('SHOW client_min_messages', 'SCHEMA')
282
322
  end
283
323
 
284
324
  # Set the client message level.
@@ -296,10 +336,7 @@ module ActiveRecord
296
336
  end
297
337
 
298
338
  def serial_sequence(table, column)
299
- result = exec_query(<<-eosql, 'SCHEMA')
300
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
301
- eosql
302
- result.rows.first.first
339
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
303
340
  end
304
341
 
305
342
  # Sets the sequence of a table's primary key to the specified value.
@@ -310,11 +347,9 @@ module ActiveRecord
310
347
  if sequence
311
348
  quoted_sequence = quote_table_name(sequence)
312
349
 
313
- select_value <<-end_sql, 'SCHEMA'
314
- SELECT setval('#{quoted_sequence}', #{value})
315
- end_sql
350
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
316
351
  else
317
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
352
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
318
353
  end
319
354
  end
320
355
  end
@@ -329,13 +364,13 @@ module ActiveRecord
329
364
  end
330
365
 
331
366
  if @logger && pk && !sequence
332
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
367
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
333
368
  end
334
369
 
335
370
  if pk && sequence
336
371
  quoted_sequence = quote_table_name(sequence)
337
372
 
338
- select_value <<-end_sql, 'SCHEMA'
373
+ select_value(<<-end_sql, 'SCHEMA')
339
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)
340
375
  end_sql
341
376
  end
@@ -395,17 +430,19 @@ module ActiveRecord
395
430
  nil
396
431
  end
397
432
 
398
- # Returns just a table's primary key
399
- def primary_key(table)
400
- pks = exec_query(<<-end_sql, 'SCHEMA').rows
401
- SELECT attr.attname
402
- FROM pg_attribute attr
403
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
404
- WHERE cons.contype = 'p'
405
- AND cons.conrelid = '#{quote_table_name(table)}'::regclass
406
- end_sql
407
- return nil unless pks.count == 1
408
- pks[0][0]
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
409
446
  end
410
447
 
411
448
  # Renames a table.
@@ -422,7 +459,7 @@ module ActiveRecord
422
459
  new_seq = "#{new_name}_#{pk}_seq"
423
460
  idx = "#{table_name}_pkey"
424
461
  new_idx = "#{new_name}_pkey"
425
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
462
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
426
463
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
427
464
  end
428
465
 
@@ -432,50 +469,69 @@ module ActiveRecord
432
469
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
433
470
  clear_cache!
434
471
  super
472
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
435
473
  end
436
474
 
437
- # Changes the column of a table.
438
- def change_column(table_name, column_name, type, options = {})
475
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
439
476
  clear_cache!
440
477
  quoted_table_name = quote_table_name(table_name)
441
- sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
442
- sql_type << "[]" if options[:array]
443
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
444
- sql << " USING #{options[:using]}" if options[:using]
445
- if options[:cast_as]
446
- sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
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})"
447
489
  end
448
490
  execute sql
449
491
 
450
492
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
451
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)
452
495
  end
453
496
 
454
497
  # Changes the default value of a table column.
455
- def change_column_default(table_name, column_name, default)
498
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
456
499
  clear_cache!
457
500
  column = column_for(table_name, column_name)
458
501
  return unless column
459
502
 
503
+ default = extract_new_default_value(default_or_changes)
460
504
  alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
461
505
  if default.nil?
462
506
  # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
463
507
  # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
464
508
  execute alter_column_query % "DROP DEFAULT"
465
509
  else
466
- execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
510
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
467
511
  end
468
512
  end
469
513
 
470
- def change_column_null(table_name, column_name, null, default = nil)
514
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
471
515
  clear_cache!
472
516
  unless null || default.nil?
473
517
  column = column_for(table_name, column_name)
474
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
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
475
519
  end
476
520
  execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
477
521
  end
478
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
+
479
535
  # Renames a column in a table.
480
536
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
481
537
  clear_cache!
@@ -484,14 +540,38 @@ module ActiveRecord
484
540
  end
485
541
 
486
542
  def add_index(table_name, column_name, options = {}) #:nodoc:
487
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
488
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
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
489
547
  end
490
548
 
491
- def remove_index!(table_name, index_name) #:nodoc:
492
- execute "DROP INDEX #{quote_table_name(index_name)}"
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)}"
493
571
  end
494
572
 
573
+ # Renames an index of a table. Raises error if length of new
574
+ # index name is greater than allowed limit.
495
575
  def rename_index(table_name, old_name, new_name)
496
576
  validate_index_length!(table_name, new_name)
497
577
 
@@ -540,41 +620,35 @@ module ActiveRecord
540
620
  end
541
621
 
542
622
  # Maps logical Rails types to PostgreSQL-specific data types.
543
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
544
- case type.to_s
623
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
624
+ sql = case type.to_s
545
625
  when 'binary'
546
626
  # PostgreSQL doesn't support limits on binary (bytea) columns.
547
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
627
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
548
628
  case limit
549
629
  when nil, 0..0x3fffffff; super(type)
550
630
  else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
551
631
  end
552
632
  when 'text'
553
633
  # PostgreSQL doesn't support limits on text columns.
554
- # The hard limit is 1Gb, according to section 8.3 in the manual.
634
+ # The hard limit is 1GB, according to section 8.3 in the manual.
555
635
  case limit
556
636
  when nil, 0..0x3fffffff; super(type)
557
637
  else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
558
638
  end
559
639
  when 'integer'
560
- return 'integer' unless limit
561
-
562
640
  case limit
563
- when 1, 2; 'smallint'
564
- when 3, 4; 'integer'
565
- when 5..8; 'bigint'
566
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
567
- end
568
- when 'datetime'
569
- return super unless precision
570
-
571
- case precision
572
- when 0..6; "timestamp(#{precision})"
573
- else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
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.")
574
645
  end
575
646
  else
576
- super
647
+ super(type, limit, precision, scale)
577
648
  end
649
+
650
+ sql << '[]' if array && type != :primary_key
651
+ sql
578
652
  end
579
653
 
580
654
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -590,6 +664,18 @@ module ActiveRecord
590
664
 
591
665
  [super, *order_columns].join(', ')
592
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
593
679
  end
594
680
  end
595
681
  end
@@ -0,0 +1,35 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
4
+ attr_reader :oid, :fmod, :array
5
+
6
+ def initialize(type_metadata, oid: nil, fmod: nil)
7
+ super(type_metadata)
8
+ @type_metadata = type_metadata
9
+ @oid = oid
10
+ @fmod = fmod
11
+ @array = /\[\]$/ === type_metadata.sql_type
12
+ end
13
+
14
+ def sql_type
15
+ super.gsub(/\[\]$/, "".freeze)
16
+ end
17
+
18
+ def ==(other)
19
+ other.is_a?(PostgreSQLTypeMetadata) &&
20
+ attributes_for_hash == other.attributes_for_hash
21
+ end
22
+ alias eql? ==
23
+
24
+ def hash
25
+ attributes_for_hash.hash
26
+ end
27
+
28
+ protected
29
+
30
+ def attributes_for_hash
31
+ [self.class, @type_metadata, oid, fmod]
32
+ end
33
+ end
34
+ end
35
+ end