activerecord 5.2.3

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

Potentially problematic release.


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

Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
6
+ undef to_yaml if method_defined?(:to_yaml)
7
+
8
+ attr_reader :oid, :fmod, :array
9
+
10
+ def initialize(type_metadata, oid: nil, fmod: nil)
11
+ super(type_metadata)
12
+ @type_metadata = type_metadata
13
+ @oid = oid
14
+ @fmod = fmod
15
+ @array = /\[\]$/.match?(type_metadata.sql_type)
16
+ end
17
+
18
+ def sql_type
19
+ super.gsub(/\[\]$/, "".freeze)
20
+ end
21
+
22
+ def ==(other)
23
+ other.is_a?(PostgreSQLTypeMetadata) &&
24
+ attributes_for_hash == other.attributes_for_hash
25
+ end
26
+ alias eql? ==
27
+
28
+ def hash
29
+ attributes_for_hash.hash
30
+ end
31
+
32
+ protected
33
+
34
+ def attributes_for_hash
35
+ [self.class, @type_metadata, oid, fmod]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ # Value Object to hold a schema qualified name.
7
+ # This is usually the name of a PostgreSQL relation but it can also represent
8
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
9
+ # double quoting.
10
+ class Name # :nodoc:
11
+ SEPARATOR = "."
12
+ attr_reader :schema, :identifier
13
+
14
+ def initialize(schema, identifier)
15
+ @schema, @identifier = unquote(schema), unquote(identifier)
16
+ end
17
+
18
+ def to_s
19
+ parts.join SEPARATOR
20
+ end
21
+
22
+ def quoted
23
+ if schema
24
+ PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
25
+ else
26
+ PG::Connection.quote_ident(identifier)
27
+ end
28
+ end
29
+
30
+ def ==(o)
31
+ o.class == self.class && o.parts == parts
32
+ end
33
+ alias_method :eql?, :==
34
+
35
+ def hash
36
+ parts.hash
37
+ end
38
+
39
+ protected
40
+
41
+ def parts
42
+ @parts ||= [@schema, @identifier].compact
43
+ end
44
+
45
+ private
46
+ def unquote(part)
47
+ if part && part.start_with?('"')
48
+ part[1..-2]
49
+ else
50
+ part
51
+ end
52
+ end
53
+ end
54
+
55
+ module Utils # :nodoc:
56
+ extend self
57
+
58
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
59
+ # extracted from +string+.
60
+ # +schema+ is +nil+ if not specified in +string+.
61
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
62
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
63
+ #
64
+ # * <tt>table_name</tt>
65
+ # * <tt>"table.name"</tt>
66
+ # * <tt>schema_name.table_name</tt>
67
+ # * <tt>schema_name."table.name"</tt>
68
+ # * <tt>"schema_name".table_name</tt>
69
+ # * <tt>"schema.name"."table name"</tt>
70
+ def extract_schema_qualified_name(string)
71
+ schema, table = string.scan(/[^".]+|"[^"]*"/)
72
+ if table.nil?
73
+ table = schema
74
+ schema = nil
75
+ end
76
+ PostgreSQL::Name.new(schema, table)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,863 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
4
+ gem "pg", ">= 0.18", "< 2.0"
5
+ require "pg"
6
+
7
+ # Use async_exec instead of exec_params on pg versions before 1.1
8
+ class ::PG::Connection
9
+ unless self.public_method_defined?(:async_exec_params)
10
+ remove_method :exec_params
11
+ alias exec_params async_exec
12
+ end
13
+ end
14
+
15
+ require "active_record/connection_adapters/abstract_adapter"
16
+ require "active_record/connection_adapters/statement_pool"
17
+ require "active_record/connection_adapters/postgresql/column"
18
+ require "active_record/connection_adapters/postgresql/database_statements"
19
+ require "active_record/connection_adapters/postgresql/explain_pretty_printer"
20
+ require "active_record/connection_adapters/postgresql/oid"
21
+ require "active_record/connection_adapters/postgresql/quoting"
22
+ require "active_record/connection_adapters/postgresql/referential_integrity"
23
+ require "active_record/connection_adapters/postgresql/schema_creation"
24
+ require "active_record/connection_adapters/postgresql/schema_definitions"
25
+ require "active_record/connection_adapters/postgresql/schema_dumper"
26
+ require "active_record/connection_adapters/postgresql/schema_statements"
27
+ require "active_record/connection_adapters/postgresql/type_metadata"
28
+ require "active_record/connection_adapters/postgresql/utils"
29
+
30
+ module ActiveRecord
31
+ module ConnectionHandling # :nodoc:
32
+ # Establishes a connection to the database that's used by all Active Record objects
33
+ def postgresql_connection(config)
34
+ conn_params = config.symbolize_keys
35
+
36
+ conn_params.delete_if { |_, v| v.nil? }
37
+
38
+ # Map ActiveRecords param names to PGs.
39
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
40
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
41
+
42
+ # Forward only valid config params to PG::Connection.connect.
43
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
44
+ conn_params.slice!(*valid_conn_param_keys)
45
+
46
+ # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
47
+ # so just pass a nil connection object for the time being.
48
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
49
+ end
50
+ end
51
+
52
+ module ConnectionAdapters
53
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
54
+ #
55
+ # Options:
56
+ #
57
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
58
+ # the default is to connect to localhost.
59
+ # * <tt>:port</tt> - Defaults to 5432.
60
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
61
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
62
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
63
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
64
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
65
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
66
+ # <encoding></tt> call on the connection.
67
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
68
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
69
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
70
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
71
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
72
+ # defaults to true.
73
+ #
74
+ # Any further options are used as connection parameters to libpq. See
75
+ # https://www.postgresql.org/docs/current/static/libpq-connect.html for the
76
+ # list of parameters.
77
+ #
78
+ # In addition, default connection parameters of libpq can be set per environment variables.
79
+ # See https://www.postgresql.org/docs/current/static/libpq-envars.html .
80
+ class PostgreSQLAdapter < AbstractAdapter
81
+ ADAPTER_NAME = "PostgreSQL".freeze
82
+
83
+ NATIVE_DATABASE_TYPES = {
84
+ primary_key: "bigserial primary key",
85
+ string: { name: "character varying" },
86
+ text: { name: "text" },
87
+ integer: { name: "integer", limit: 4 },
88
+ float: { name: "float" },
89
+ decimal: { name: "decimal" },
90
+ datetime: { name: "timestamp" },
91
+ time: { name: "time" },
92
+ date: { name: "date" },
93
+ daterange: { name: "daterange" },
94
+ numrange: { name: "numrange" },
95
+ tsrange: { name: "tsrange" },
96
+ tstzrange: { name: "tstzrange" },
97
+ int4range: { name: "int4range" },
98
+ int8range: { name: "int8range" },
99
+ binary: { name: "bytea" },
100
+ boolean: { name: "boolean" },
101
+ xml: { name: "xml" },
102
+ tsvector: { name: "tsvector" },
103
+ hstore: { name: "hstore" },
104
+ inet: { name: "inet" },
105
+ cidr: { name: "cidr" },
106
+ macaddr: { name: "macaddr" },
107
+ uuid: { name: "uuid" },
108
+ json: { name: "json" },
109
+ jsonb: { name: "jsonb" },
110
+ ltree: { name: "ltree" },
111
+ citext: { name: "citext" },
112
+ point: { name: "point" },
113
+ line: { name: "line" },
114
+ lseg: { name: "lseg" },
115
+ box: { name: "box" },
116
+ path: { name: "path" },
117
+ polygon: { name: "polygon" },
118
+ circle: { name: "circle" },
119
+ bit: { name: "bit" },
120
+ bit_varying: { name: "bit varying" },
121
+ money: { name: "money" },
122
+ interval: { name: "interval" },
123
+ oid: { name: "oid" },
124
+ }
125
+
126
+ OID = PostgreSQL::OID #:nodoc:
127
+
128
+ include PostgreSQL::Quoting
129
+ include PostgreSQL::ReferentialIntegrity
130
+ include PostgreSQL::SchemaStatements
131
+ include PostgreSQL::DatabaseStatements
132
+
133
+ def supports_bulk_alter?
134
+ true
135
+ end
136
+
137
+ def supports_index_sort_order?
138
+ true
139
+ end
140
+
141
+ def supports_partial_index?
142
+ true
143
+ end
144
+
145
+ def supports_expression_index?
146
+ true
147
+ end
148
+
149
+ def supports_transaction_isolation?
150
+ true
151
+ end
152
+
153
+ def supports_foreign_keys?
154
+ true
155
+ end
156
+
157
+ def supports_validate_constraints?
158
+ true
159
+ end
160
+
161
+ def supports_views?
162
+ true
163
+ end
164
+
165
+ def supports_datetime_with_precision?
166
+ true
167
+ end
168
+
169
+ def supports_json?
170
+ postgresql_version >= 90200
171
+ end
172
+
173
+ def supports_comments?
174
+ true
175
+ end
176
+
177
+ def supports_savepoints?
178
+ true
179
+ end
180
+
181
+ def index_algorithms
182
+ { concurrently: "CONCURRENTLY" }
183
+ end
184
+
185
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
186
+ def initialize(connection, max)
187
+ super(max)
188
+ @connection = connection
189
+ @counter = 0
190
+ end
191
+
192
+ def next_key
193
+ "a#{@counter + 1}"
194
+ end
195
+
196
+ def []=(sql, key)
197
+ super.tap { @counter += 1 }
198
+ end
199
+
200
+ private
201
+ def dealloc(key)
202
+ @connection.query "DEALLOCATE #{key}" if connection_active?
203
+ rescue PG::Error
204
+ end
205
+
206
+ def connection_active?
207
+ @connection.status == PG::CONNECTION_OK
208
+ rescue PG::Error
209
+ false
210
+ end
211
+ end
212
+
213
+ # Initializes and connects a PostgreSQL adapter.
214
+ def initialize(connection, logger, connection_parameters, config)
215
+ super(connection, logger, config)
216
+
217
+ @connection_parameters = connection_parameters
218
+
219
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
220
+ @local_tz = nil
221
+ @max_identifier_length = nil
222
+
223
+ connect
224
+ add_pg_encoders
225
+ @statements = StatementPool.new @connection,
226
+ self.class.type_cast_config_to_integer(config[:statement_limit])
227
+
228
+ if postgresql_version < 90100
229
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
230
+ end
231
+
232
+ add_pg_decoders
233
+
234
+ @type_map = Type::HashLookupTypeMap.new
235
+ initialize_type_map
236
+ @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
237
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
238
+ end
239
+
240
+ # Clears the prepared statements cache.
241
+ def clear_cache!
242
+ @lock.synchronize do
243
+ @statements.clear
244
+ end
245
+ end
246
+
247
+ def truncate(table_name, name = nil)
248
+ exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
249
+ end
250
+
251
+ # Is this connection alive and ready for queries?
252
+ def active?
253
+ @lock.synchronize do
254
+ @connection.query "SELECT 1"
255
+ end
256
+ true
257
+ rescue PG::Error
258
+ false
259
+ end
260
+
261
+ # Close then reopen the connection.
262
+ def reconnect!
263
+ @lock.synchronize do
264
+ super
265
+ @connection.reset
266
+ configure_connection
267
+ end
268
+ end
269
+
270
+ def reset!
271
+ @lock.synchronize do
272
+ clear_cache!
273
+ reset_transaction
274
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
275
+ @connection.query "ROLLBACK"
276
+ end
277
+ @connection.query "DISCARD ALL"
278
+ configure_connection
279
+ end
280
+ end
281
+
282
+ # Disconnects from the database if already connected. Otherwise, this
283
+ # method does nothing.
284
+ def disconnect!
285
+ @lock.synchronize do
286
+ super
287
+ @connection.close rescue nil
288
+ end
289
+ end
290
+
291
+ def discard! # :nodoc:
292
+ @connection.socket_io.reopen(IO::NULL) rescue nil
293
+ @connection = nil
294
+ end
295
+
296
+ def native_database_types #:nodoc:
297
+ NATIVE_DATABASE_TYPES
298
+ end
299
+
300
+ def set_standard_conforming_strings
301
+ execute("SET standard_conforming_strings = on", "SCHEMA")
302
+ end
303
+
304
+ def supports_ddl_transactions?
305
+ true
306
+ end
307
+
308
+ def supports_advisory_locks?
309
+ true
310
+ end
311
+
312
+ def supports_explain?
313
+ true
314
+ end
315
+
316
+ def supports_extensions?
317
+ true
318
+ end
319
+
320
+ def supports_ranges?
321
+ # Range datatypes weren't introduced until PostgreSQL 9.2
322
+ postgresql_version >= 90200
323
+ end
324
+
325
+ def supports_materialized_views?
326
+ postgresql_version >= 90300
327
+ end
328
+
329
+ def supports_foreign_tables?
330
+ postgresql_version >= 90300
331
+ end
332
+
333
+ def supports_pgcrypto_uuid?
334
+ postgresql_version >= 90400
335
+ end
336
+
337
+ def get_advisory_lock(lock_id) # :nodoc:
338
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
339
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
340
+ end
341
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
342
+ end
343
+
344
+ def release_advisory_lock(lock_id) # :nodoc:
345
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
346
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
347
+ end
348
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
349
+ end
350
+
351
+ def enable_extension(name)
352
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
353
+ reload_type_map
354
+ }
355
+ end
356
+
357
+ def disable_extension(name)
358
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
359
+ reload_type_map
360
+ }
361
+ end
362
+
363
+ def extension_enabled?(name)
364
+ res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
365
+ res.cast_values.first
366
+ end
367
+
368
+ def extensions
369
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
370
+ end
371
+
372
+ # Returns the configured supported identifier length supported by PostgreSQL
373
+ def max_identifier_length
374
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
375
+ end
376
+ alias table_alias_length max_identifier_length
377
+ alias index_name_length max_identifier_length
378
+
379
+ # Set the authorized user for this session
380
+ def session_auth=(user)
381
+ clear_cache!
382
+ execute("SET SESSION AUTHORIZATION #{user}")
383
+ end
384
+
385
+ def use_insert_returning?
386
+ @use_insert_returning
387
+ end
388
+
389
+ def column_name_for_operation(operation, node) # :nodoc:
390
+ OPERATION_ALIASES.fetch(operation) { operation.downcase }
391
+ end
392
+
393
+ OPERATION_ALIASES = { # :nodoc:
394
+ "maximum" => "max",
395
+ "minimum" => "min",
396
+ "average" => "avg",
397
+ }
398
+
399
+ # Returns the version of the connected PostgreSQL server.
400
+ def postgresql_version
401
+ @connection.server_version
402
+ end
403
+
404
+ def default_index_type?(index) # :nodoc:
405
+ index.using == :btree || super
406
+ end
407
+
408
+ private
409
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
410
+ VALUE_LIMIT_VIOLATION = "22001"
411
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
412
+ NOT_NULL_VIOLATION = "23502"
413
+ FOREIGN_KEY_VIOLATION = "23503"
414
+ UNIQUE_VIOLATION = "23505"
415
+ SERIALIZATION_FAILURE = "40001"
416
+ DEADLOCK_DETECTED = "40P01"
417
+ LOCK_NOT_AVAILABLE = "55P03"
418
+ QUERY_CANCELED = "57014"
419
+
420
+ def translate_exception(exception, message)
421
+ return exception unless exception.respond_to?(:result)
422
+
423
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
424
+ when UNIQUE_VIOLATION
425
+ RecordNotUnique.new(message)
426
+ when FOREIGN_KEY_VIOLATION
427
+ InvalidForeignKey.new(message)
428
+ when VALUE_LIMIT_VIOLATION
429
+ ValueTooLong.new(message)
430
+ when NUMERIC_VALUE_OUT_OF_RANGE
431
+ RangeError.new(message)
432
+ when NOT_NULL_VIOLATION
433
+ NotNullViolation.new(message)
434
+ when SERIALIZATION_FAILURE
435
+ SerializationFailure.new(message)
436
+ when DEADLOCK_DETECTED
437
+ Deadlocked.new(message)
438
+ when LOCK_NOT_AVAILABLE
439
+ LockWaitTimeout.new(message)
440
+ when QUERY_CANCELED
441
+ QueryCanceled.new(message)
442
+ else
443
+ super
444
+ end
445
+ end
446
+
447
+ def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
448
+ if !type_map.key?(oid)
449
+ load_additional_types([oid])
450
+ end
451
+
452
+ type_map.fetch(oid, fmod, sql_type) {
453
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
454
+ Type.default_value.tap do |cast_type|
455
+ type_map.register_type(oid, cast_type)
456
+ end
457
+ }
458
+ end
459
+
460
+ def initialize_type_map(m = type_map)
461
+ m.register_type "int2", Type::Integer.new(limit: 2)
462
+ m.register_type "int4", Type::Integer.new(limit: 4)
463
+ m.register_type "int8", Type::Integer.new(limit: 8)
464
+ m.register_type "oid", OID::Oid.new
465
+ m.register_type "float4", Type::Float.new
466
+ m.alias_type "float8", "float4"
467
+ m.register_type "text", Type::Text.new
468
+ register_class_with_limit m, "varchar", Type::String
469
+ m.alias_type "char", "varchar"
470
+ m.alias_type "name", "varchar"
471
+ m.alias_type "bpchar", "varchar"
472
+ m.register_type "bool", Type::Boolean.new
473
+ register_class_with_limit m, "bit", OID::Bit
474
+ register_class_with_limit m, "varbit", OID::BitVarying
475
+ m.alias_type "timestamptz", "timestamp"
476
+ m.register_type "date", OID::Date.new
477
+
478
+ m.register_type "money", OID::Money.new
479
+ m.register_type "bytea", OID::Bytea.new
480
+ m.register_type "point", OID::Point.new
481
+ m.register_type "hstore", OID::Hstore.new
482
+ m.register_type "json", Type::Json.new
483
+ m.register_type "jsonb", OID::Jsonb.new
484
+ m.register_type "cidr", OID::Cidr.new
485
+ m.register_type "inet", OID::Inet.new
486
+ m.register_type "uuid", OID::Uuid.new
487
+ m.register_type "xml", OID::Xml.new
488
+ m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
489
+ m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
490
+ m.register_type "citext", OID::SpecializedString.new(:citext)
491
+ m.register_type "ltree", OID::SpecializedString.new(:ltree)
492
+ m.register_type "line", OID::SpecializedString.new(:line)
493
+ m.register_type "lseg", OID::SpecializedString.new(:lseg)
494
+ m.register_type "box", OID::SpecializedString.new(:box)
495
+ m.register_type "path", OID::SpecializedString.new(:path)
496
+ m.register_type "polygon", OID::SpecializedString.new(:polygon)
497
+ m.register_type "circle", OID::SpecializedString.new(:circle)
498
+
499
+ m.register_type "interval" do |_, _, sql_type|
500
+ precision = extract_precision(sql_type)
501
+ OID::SpecializedString.new(:interval, precision: precision)
502
+ end
503
+
504
+ register_class_with_precision m, "time", Type::Time
505
+ register_class_with_precision m, "timestamp", OID::DateTime
506
+
507
+ m.register_type "numeric" do |_, fmod, sql_type|
508
+ precision = extract_precision(sql_type)
509
+ scale = extract_scale(sql_type)
510
+
511
+ # The type for the numeric depends on the width of the field,
512
+ # so we'll do something special here.
513
+ #
514
+ # When dealing with decimal columns:
515
+ #
516
+ # places after decimal = fmod - 4 & 0xffff
517
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
518
+ if fmod && (fmod - 4 & 0xffff).zero?
519
+ # FIXME: Remove this class, and the second argument to
520
+ # lookups on PG
521
+ Type::DecimalWithoutScale.new(precision: precision)
522
+ else
523
+ OID::Decimal.new(precision: precision, scale: scale)
524
+ end
525
+ end
526
+
527
+ load_additional_types
528
+ end
529
+
530
+ # Extracts the value from a PostgreSQL column default definition.
531
+ def extract_value_from_default(default)
532
+ case default
533
+ # Quoted types
534
+ when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
535
+ # The default 'now'::date is CURRENT_DATE
536
+ if $1 == "now".freeze && $2 == "date".freeze
537
+ nil
538
+ else
539
+ $1.gsub("''".freeze, "'".freeze)
540
+ end
541
+ # Boolean types
542
+ when "true".freeze, "false".freeze
543
+ default
544
+ # Numeric types
545
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
546
+ $1
547
+ # Object identifier types
548
+ when /\A-?\d+\z/
549
+ $1
550
+ else
551
+ # Anything else is blank, some user type, or some function
552
+ # and we can't know the value of that, so return nil.
553
+ nil
554
+ end
555
+ end
556
+
557
+ def extract_default_function(default_value, default)
558
+ default if has_default_function?(default_value, default)
559
+ end
560
+
561
+ def has_default_function?(default_value, default)
562
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
563
+ end
564
+
565
+ def load_additional_types(oids = nil)
566
+ initializer = OID::TypeMapInitializer.new(type_map)
567
+
568
+ if supports_ranges?
569
+ query = <<-SQL
570
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
571
+ FROM pg_type as t
572
+ LEFT JOIN pg_range as r ON oid = rngtypid
573
+ SQL
574
+ else
575
+ query = <<-SQL
576
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
577
+ FROM pg_type as t
578
+ SQL
579
+ end
580
+
581
+ if oids
582
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
583
+ else
584
+ query += initializer.query_conditions_for_initial_load
585
+ end
586
+
587
+ execute_and_clear(query, "SCHEMA", []) do |records|
588
+ initializer.run(records)
589
+ end
590
+ end
591
+
592
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
593
+
594
+ def execute_and_clear(sql, name, binds, prepare: false)
595
+ if without_prepared_statement?(binds)
596
+ result = exec_no_cache(sql, name, [])
597
+ elsif !prepare
598
+ result = exec_no_cache(sql, name, binds)
599
+ else
600
+ result = exec_cache(sql, name, binds)
601
+ end
602
+ ret = yield result
603
+ result.clear
604
+ ret
605
+ end
606
+
607
+ def exec_no_cache(sql, name, binds)
608
+ type_casted_binds = type_casted_binds(binds)
609
+ log(sql, name, binds, type_casted_binds) do
610
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
611
+ @connection.exec_params(sql, type_casted_binds)
612
+ end
613
+ end
614
+ end
615
+
616
+ def exec_cache(sql, name, binds)
617
+ stmt_key = prepare_statement(sql)
618
+ type_casted_binds = type_casted_binds(binds)
619
+
620
+ log(sql, name, binds, type_casted_binds, stmt_key) do
621
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
622
+ @connection.exec_prepared(stmt_key, type_casted_binds)
623
+ end
624
+ end
625
+ rescue ActiveRecord::StatementInvalid => e
626
+ raise unless is_cached_plan_failure?(e)
627
+
628
+ # Nothing we can do if we are in a transaction because all commands
629
+ # will raise InFailedSQLTransaction
630
+ if in_transaction?
631
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
632
+ else
633
+ @lock.synchronize do
634
+ # outside of transactions we can simply flush this query and retry
635
+ @statements.delete sql_key(sql)
636
+ end
637
+ retry
638
+ end
639
+ end
640
+
641
+ # Annoyingly, the code for prepared statements whose return value may
642
+ # have changed is FEATURE_NOT_SUPPORTED.
643
+ #
644
+ # This covers various different error types so we need to do additional
645
+ # work to classify the exception definitively as a
646
+ # ActiveRecord::PreparedStatementCacheExpired
647
+ #
648
+ # Check here for more details:
649
+ # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
650
+ CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
651
+ def is_cached_plan_failure?(e)
652
+ pgerror = e.cause
653
+ code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
654
+ code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
655
+ rescue
656
+ false
657
+ end
658
+
659
+ def in_transaction?
660
+ open_transactions > 0
661
+ end
662
+
663
+ # Returns the statement identifier for the client side cache
664
+ # of statements
665
+ def sql_key(sql)
666
+ "#{schema_search_path}-#{sql}"
667
+ end
668
+
669
+ # Prepare the statement if it hasn't been prepared, return
670
+ # the statement key.
671
+ def prepare_statement(sql)
672
+ @lock.synchronize do
673
+ sql_key = sql_key(sql)
674
+ unless @statements.key? sql_key
675
+ nextkey = @statements.next_key
676
+ begin
677
+ @connection.prepare nextkey, sql
678
+ rescue => e
679
+ raise translate_exception_class(e, sql)
680
+ end
681
+ # Clear the queue
682
+ @connection.get_last_result
683
+ @statements[sql_key] = nextkey
684
+ end
685
+ @statements[sql_key]
686
+ end
687
+ end
688
+
689
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
690
+ # connected server's characteristics.
691
+ def connect
692
+ @connection = PG.connect(@connection_parameters)
693
+ configure_connection
694
+ rescue ::PG::Error => error
695
+ if error.message.include?("does not exist")
696
+ raise ActiveRecord::NoDatabaseError
697
+ else
698
+ raise
699
+ end
700
+ end
701
+
702
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
703
+ # This is called by #connect and should not be called manually.
704
+ def configure_connection
705
+ if @config[:encoding]
706
+ @connection.set_client_encoding(@config[:encoding])
707
+ end
708
+ self.client_min_messages = @config[:min_messages] || "warning"
709
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
710
+
711
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
712
+ set_standard_conforming_strings
713
+
714
+ variables = @config.fetch(:variables, {}).stringify_keys
715
+
716
+ # If using Active Record's time zone support configure the connection to return
717
+ # TIMESTAMP WITH ZONE types in UTC.
718
+ unless variables["timezone"]
719
+ if ActiveRecord::Base.default_timezone == :utc
720
+ variables["timezone"] = "UTC"
721
+ elsif @local_tz
722
+ variables["timezone"] = @local_tz
723
+ end
724
+ end
725
+
726
+ # SET statements from :variables config hash
727
+ # https://www.postgresql.org/docs/current/static/sql-set.html
728
+ variables.map do |k, v|
729
+ if v == ":default" || v == :default
730
+ # Sets the value to the global or compile default
731
+ execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
732
+ elsif !v.nil?
733
+ execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
734
+ end
735
+ end
736
+ end
737
+
738
+ # Returns the list of a table's column names, data types, and default values.
739
+ #
740
+ # The underlying query is roughly:
741
+ # SELECT column.name, column.type, default.value, column.comment
742
+ # FROM column LEFT JOIN default
743
+ # ON column.table_id = default.table_id
744
+ # AND column.num = default.column_num
745
+ # WHERE column.table_id = get_table_id('table_name')
746
+ # AND column.num > 0
747
+ # AND NOT column.is_dropped
748
+ # ORDER BY column.num
749
+ #
750
+ # If the table name is not prefixed with a schema, the database will
751
+ # take the first match from the schema search path.
752
+ #
753
+ # Query implementation notes:
754
+ # - format_type includes the column size constraint, e.g. varchar(50)
755
+ # - ::regclass is a function that gives the id for a table name
756
+ def column_definitions(table_name)
757
+ query(<<-end_sql, "SCHEMA")
758
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
759
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
760
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
761
+ FROM pg_attribute a
762
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
763
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
764
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
765
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
766
+ AND a.attnum > 0 AND NOT a.attisdropped
767
+ ORDER BY a.attnum
768
+ end_sql
769
+ end
770
+
771
+ def extract_table_ref_from_insert_sql(sql)
772
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
773
+ $1.strip if $1
774
+ end
775
+
776
+ def arel_visitor
777
+ Arel::Visitors::PostgreSQL.new(self)
778
+ end
779
+
780
+ def can_perform_case_insensitive_comparison_for?(column)
781
+ @case_insensitive_cache ||= {}
782
+ @case_insensitive_cache[column.sql_type] ||= begin
783
+ sql = <<-end_sql
784
+ SELECT exists(
785
+ SELECT * FROM pg_proc
786
+ WHERE proname = 'lower'
787
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
788
+ ) OR exists(
789
+ SELECT * FROM pg_proc
790
+ INNER JOIN pg_cast
791
+ ON ARRAY[casttarget]::oidvector = proargtypes
792
+ WHERE proname = 'lower'
793
+ AND castsource = #{quote column.sql_type}::regtype
794
+ )
795
+ end_sql
796
+ execute_and_clear(sql, "SCHEMA", []) do |result|
797
+ result.getvalue(0, 0)
798
+ end
799
+ end
800
+ end
801
+
802
+ def add_pg_encoders
803
+ map = PG::TypeMapByClass.new
804
+ map[Integer] = PG::TextEncoder::Integer.new
805
+ map[TrueClass] = PG::TextEncoder::Boolean.new
806
+ map[FalseClass] = PG::TextEncoder::Boolean.new
807
+ @connection.type_map_for_queries = map
808
+ end
809
+
810
+ def add_pg_decoders
811
+ coders_by_name = {
812
+ "int2" => PG::TextDecoder::Integer,
813
+ "int4" => PG::TextDecoder::Integer,
814
+ "int8" => PG::TextDecoder::Integer,
815
+ "oid" => PG::TextDecoder::Integer,
816
+ "float4" => PG::TextDecoder::Float,
817
+ "float8" => PG::TextDecoder::Float,
818
+ "bool" => PG::TextDecoder::Boolean,
819
+ }
820
+ known_coder_types = coders_by_name.keys.map { |n| quote(n) }
821
+ query = <<-SQL % known_coder_types.join(", ")
822
+ SELECT t.oid, t.typname
823
+ FROM pg_type as t
824
+ WHERE t.typname IN (%s)
825
+ SQL
826
+ coders = execute_and_clear(query, "SCHEMA", []) do |result|
827
+ result
828
+ .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
829
+ .compact
830
+ end
831
+
832
+ map = PG::TypeMapByOid.new
833
+ coders.each { |coder| map.add_coder(coder) }
834
+ @connection.type_map_for_results = map
835
+ end
836
+
837
+ def construct_coder(row, coder_class)
838
+ return unless coder_class
839
+ coder_class.new(oid: row["oid"].to_i, name: row["typname"])
840
+ end
841
+
842
+ ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
843
+ ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
844
+ ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
845
+ ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
846
+ ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
847
+ ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
848
+ ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
849
+ ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
850
+ ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
851
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
852
+ ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
853
+ ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
854
+ ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
855
+ ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
856
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
857
+ ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
858
+ ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
859
+ ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
860
+ ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
861
+ end
862
+ end
863
+ end