activerecord 3.2.22.5 → 4.2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,242 +1,142 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_support/core_ext/object/blank'
3
2
  require 'active_record/connection_adapters/statement_pool'
3
+
4
+ require 'active_record/connection_adapters/postgresql/utils'
5
+ require 'active_record/connection_adapters/postgresql/column'
6
+ require 'active_record/connection_adapters/postgresql/oid'
7
+ require 'active_record/connection_adapters/postgresql/quoting'
8
+ require 'active_record/connection_adapters/postgresql/referential_integrity'
9
+ require 'active_record/connection_adapters/postgresql/schema_definitions'
10
+ require 'active_record/connection_adapters/postgresql/schema_statements'
11
+ require 'active_record/connection_adapters/postgresql/database_statements'
12
+
4
13
  require 'arel/visitors/bind_visitor'
5
14
 
6
15
  # Make sure we're using pg high enough for PGResult#values
7
- gem 'pg', '~> 0.11'
16
+ gem 'pg', '~> 0.15'
8
17
  require 'pg'
9
18
 
19
+ require 'ipaddr'
20
+
10
21
  module ActiveRecord
11
- class Base
22
+ module ConnectionHandling # :nodoc:
23
+ VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
24
+ :client_encoding, :options, :application_name, :fallback_application_name,
25
+ :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
26
+ :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
27
+ :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
28
+
12
29
  # Establishes a connection to the database that's used by all Active Record objects
13
- def self.postgresql_connection(config) # :nodoc:
14
- config = config.symbolize_keys
15
- host = config[:host]
16
- port = config[:port] || 5432
17
- username = config[:username].to_s if config[:username]
18
- password = config[:password].to_s if config[:password]
19
-
20
- if config.key?(:database)
21
- database = config[:database]
22
- else
23
- raise ArgumentError, "No database specified. Missing argument: database."
24
- end
30
+ def postgresql_connection(config)
31
+ conn_params = config.symbolize_keys
32
+
33
+ conn_params.delete_if { |_, v| v.nil? }
34
+
35
+ # Map ActiveRecords param names to PGs.
36
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
37
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
38
+
39
+ # Forward only valid config params to PGconn.connect.
40
+ conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
25
41
 
26
42
  # The postgres drivers don't allow the creation of an unconnected PGconn object,
27
43
  # so just pass a nil connection object for the time being.
28
- ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
44
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
29
45
  end
30
46
  end
31
47
 
32
48
  module ConnectionAdapters
33
- # PostgreSQL-specific extensions to column definitions in a table.
34
- class PostgreSQLColumn < Column #:nodoc:
35
- # Instantiates a new PostgreSQL column definition in a table.
36
- def initialize(name, default, sql_type = nil, null = true)
37
- super(name, self.class.extract_value_from_default(default), sql_type, null)
38
- end
39
-
40
- # :stopdoc:
41
- class << self
42
- attr_accessor :money_precision
43
- def string_to_time(string)
44
- return string unless String === string
45
-
46
- case string
47
- when 'infinity' then 1.0 / 0.0
48
- when '-infinity' then -1.0 / 0.0
49
- else
50
- super
51
- end
52
- end
53
- end
54
- # :startdoc:
55
-
56
- private
57
- def extract_limit(sql_type)
58
- case sql_type
59
- when /^bigint/i; 8
60
- when /^smallint/i; 2
61
- else super
62
- end
63
- end
64
-
65
- # Extracts the scale from PostgreSQL-specific data types.
66
- def extract_scale(sql_type)
67
- # Money type has a fixed scale of 2.
68
- sql_type =~ /^money/ ? 2 : super
69
- end
70
-
71
- # Extracts the precision from PostgreSQL-specific data types.
72
- def extract_precision(sql_type)
73
- if sql_type == 'money'
74
- self.class.money_precision
75
- else
76
- super
77
- end
78
- end
79
-
80
- # Maps PostgreSQL-specific data types to logical Rails types.
81
- def simplified_type(field_type)
82
- case field_type
83
- # Numeric and monetary types
84
- when /^(?:real|double precision)$/
85
- :float
86
- # Monetary types
87
- when 'money'
88
- :decimal
89
- # Character types
90
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
91
- :string
92
- # Binary data types
93
- when 'bytea'
94
- :binary
95
- # Date/time types
96
- when /^timestamp with(?:out)? time zone$/
97
- :datetime
98
- when 'interval'
99
- :string
100
- # Geometric types
101
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
102
- :string
103
- # Network address types
104
- when /^(?:cidr|inet|macaddr)$/
105
- :string
106
- # Bit strings
107
- when /^bit(?: varying)?(?:\(\d+\))?$/
108
- :string
109
- # XML type
110
- when 'xml'
111
- :xml
112
- # tsvector type
113
- when 'tsvector'
114
- :tsvector
115
- # Arrays
116
- when /^\D+\[\]$/
117
- :string
118
- # Object identifier types
119
- when 'oid'
120
- :integer
121
- # UUID type
122
- when 'uuid'
123
- :string
124
- # Small and big integer types
125
- when /^(?:small|big)int$/
126
- :integer
127
- # Pass through all types that are not specific to PostgreSQL.
128
- else
129
- super
130
- end
131
- end
132
-
133
- # Extracts the value from a PostgreSQL column default definition.
134
- def self.extract_value_from_default(default)
135
- case default
136
- # This is a performance optimization for Ruby 1.9.2 in development.
137
- # If the value is nil, we return nil straight away without checking
138
- # the regular expressions. If we check each regular expression,
139
- # Regexp#=== will call NilClass#to_str, which will trigger
140
- # method_missing (defined by whiny nil in ActiveSupport) which
141
- # makes this method very very slow.
142
- when NilClass
143
- nil
144
- # Numeric types
145
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
146
- $1
147
- # Character types
148
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
149
- $1
150
- # Binary data types
151
- when /\A'(.*)'::bytea\z/m
152
- $1
153
- # Date/time types
154
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
155
- $1
156
- when /\A'(.*)'::interval\z/
157
- $1
158
- # Boolean type
159
- when 'true'
160
- true
161
- when 'false'
162
- false
163
- # Geometric types
164
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
165
- $1
166
- # Network address types
167
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
168
- $1
169
- # Bit string types
170
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
171
- $1
172
- # XML type
173
- when /\A'(.*)'::xml\z/m
174
- $1
175
- # Arrays
176
- when /\A'(.*)'::"?\D+"?\[\]\z/
177
- $1
178
- # Object identifier types
179
- when /\A-?\d+\z/
180
- $1
181
- else
182
- # Anything else is blank, some user type, or some function
183
- # and we can't know the value of that, so return nil.
184
- nil
185
- end
186
- end
187
- end
188
-
189
- # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
190
- # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
49
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
191
50
  #
192
51
  # Options:
193
52
  #
194
- # * <tt>:host</tt> - Defaults to "localhost".
53
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
54
+ # the default is to connect to localhost.
195
55
  # * <tt>:port</tt> - Defaults to 5432.
196
- # * <tt>:username</tt> - Defaults to nothing.
197
- # * <tt>:password</tt> - Defaults to nothing.
198
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
56
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
57
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
58
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
199
59
  # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
200
60
  # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
201
61
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
202
62
  # <encoding></tt> call on the connection.
203
63
  # * <tt>:min_messages</tt> - An optional client min messages that is used in a
204
64
  # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
65
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
66
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
67
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
68
+ # defaults to true.
69
+ #
70
+ # Any further options are used as connection parameters to libpq. See
71
+ # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
72
+ # list of parameters.
73
+ #
74
+ # In addition, default connection parameters of libpq can be set per environment variables.
75
+ # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
205
76
  class PostgreSQLAdapter < AbstractAdapter
206
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
207
- def xml(*args)
208
- options = args.extract_options!
209
- column(args[0], 'xml', options)
210
- end
211
-
212
- def tsvector(*args)
213
- options = args.extract_options!
214
- column(args[0], 'tsvector', options)
215
- end
216
- end
217
-
218
- ADAPTER_NAME = 'PostgreSQL'
77
+ ADAPTER_NAME = 'PostgreSQL'.freeze
219
78
 
220
79
  NATIVE_DATABASE_TYPES = {
221
- :primary_key => "serial primary key",
222
- :string => { :name => "character varying", :limit => 255 },
223
- :text => { :name => "text" },
224
- :integer => { :name => "integer" },
225
- :float => { :name => "float" },
226
- :decimal => { :name => "decimal" },
227
- :datetime => { :name => "timestamp" },
228
- :timestamp => { :name => "timestamp" },
229
- :time => { :name => "time" },
230
- :date => { :name => "date" },
231
- :binary => { :name => "bytea" },
232
- :boolean => { :name => "boolean" },
233
- :xml => { :name => "xml" },
234
- :tsvector => { :name => "tsvector" }
80
+ primary_key: "serial primary key",
81
+ bigserial: "bigserial",
82
+ string: { name: "character varying" },
83
+ text: { name: "text" },
84
+ integer: { name: "integer" },
85
+ float: { name: "float" },
86
+ decimal: { name: "decimal" },
87
+ datetime: { name: "timestamp" },
88
+ time: { name: "time" },
89
+ date: { name: "date" },
90
+ daterange: { name: "daterange" },
91
+ numrange: { name: "numrange" },
92
+ tsrange: { name: "tsrange" },
93
+ tstzrange: { name: "tstzrange" },
94
+ int4range: { name: "int4range" },
95
+ int8range: { name: "int8range" },
96
+ binary: { name: "bytea" },
97
+ boolean: { name: "boolean" },
98
+ bigint: { name: "bigint" },
99
+ xml: { name: "xml" },
100
+ tsvector: { name: "tsvector" },
101
+ hstore: { name: "hstore" },
102
+ inet: { name: "inet" },
103
+ cidr: { name: "cidr" },
104
+ macaddr: { name: "macaddr" },
105
+ uuid: { name: "uuid" },
106
+ json: { name: "json" },
107
+ jsonb: { name: "jsonb" },
108
+ ltree: { name: "ltree" },
109
+ citext: { name: "citext" },
110
+ point: { name: "point" },
111
+ bit: { name: "bit" },
112
+ bit_varying: { name: "bit varying" },
113
+ money: { name: "money" },
235
114
  }
236
115
 
237
- # Returns 'PostgreSQL' as adapter name for identification purposes.
238
- def adapter_name
239
- ADAPTER_NAME
116
+ OID = PostgreSQL::OID #:nodoc:
117
+
118
+ include PostgreSQL::Quoting
119
+ include PostgreSQL::ReferentialIntegrity
120
+ include PostgreSQL::SchemaStatements
121
+ include PostgreSQL::DatabaseStatements
122
+ include Savepoints
123
+
124
+ def schema_creation # :nodoc:
125
+ PostgreSQL::SchemaCreation.new self
126
+ end
127
+
128
+ # Adds +:array+ option to the default set provided by the
129
+ # AbstractAdapter
130
+ def prepare_column_options(column, types) # :nodoc:
131
+ spec = super
132
+ spec[:array] = 'true' if column.respond_to?(:array) && column.array
133
+ spec[:default] = "\"#{column.default_function}\"" if column.default_function
134
+ spec
135
+ end
136
+
137
+ # Adds +:array+ as a valid migration key
138
+ def migration_keys
139
+ super + [:array]
240
140
  end
241
141
 
242
142
  # Returns +true+, since this connection adapter supports prepared statement
@@ -249,6 +149,26 @@ module ActiveRecord
249
149
  true
250
150
  end
251
151
 
152
+ def supports_partial_index?
153
+ true
154
+ end
155
+
156
+ def supports_transaction_isolation?
157
+ true
158
+ end
159
+
160
+ def supports_foreign_keys?
161
+ true
162
+ end
163
+
164
+ def supports_views?
165
+ true
166
+ end
167
+
168
+ def index_algorithms
169
+ { concurrently: 'CONCURRENTLY' }
170
+ end
171
+
252
172
  class StatementPool < ConnectionAdapters::StatementPool
253
173
  def initialize(connection, max)
254
174
  super
@@ -286,33 +206,31 @@ module ActiveRecord
286
206
  end
287
207
 
288
208
  private
289
- def cache
290
- @cache[$$]
291
- end
292
209
 
293
- def dealloc(key)
294
- @connection.query "DEALLOCATE #{key}" if connection_active?
295
- end
210
+ def cache
211
+ @cache[Process.pid]
212
+ end
296
213
 
297
- def connection_active?
298
- @connection.status == PGconn::CONNECTION_OK
299
- rescue PGError
300
- false
301
- end
302
- end
214
+ def dealloc(key)
215
+ @connection.query "DEALLOCATE #{key}" if connection_active?
216
+ end
303
217
 
304
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
305
- include Arel::Visitors::BindVisitor
218
+ def connection_active?
219
+ @connection.status == PGconn::CONNECTION_OK
220
+ rescue PGError
221
+ false
222
+ end
306
223
  end
307
224
 
308
225
  # Initializes and connects a PostgreSQL adapter.
309
226
  def initialize(connection, logger, connection_parameters, config)
310
227
  super(connection, logger)
311
228
 
312
- if config.fetch(:prepared_statements) { true }
313
- @visitor = Arel::Visitors::PostgreSQL.new self
229
+ @visitor = Arel::Visitors::PostgreSQL.new self
230
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
231
+ @prepared_statements = true
314
232
  else
315
- @visitor = BindSubstitution.new self
233
+ @prepared_statements = false
316
234
  end
317
235
 
318
236
  @connection_parameters, @config = connection_parameters, config
@@ -323,13 +241,16 @@ module ActiveRecord
323
241
 
324
242
  connect
325
243
  @statements = StatementPool.new @connection,
326
- config.fetch(:statement_limit) { 1000 }
244
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
327
245
 
328
246
  if postgresql_version < 80200
329
247
  raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
330
248
  end
331
249
 
250
+ @type_map = Type::HashLookupTypeMap.new
251
+ initialize_type_map(type_map)
332
252
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
253
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
333
254
  end
334
255
 
335
256
  # Clears the prepared statements cache.
@@ -337,6 +258,10 @@ module ActiveRecord
337
258
  @statements.clear
338
259
  end
339
260
 
261
+ def truncate(table_name, name = nil)
262
+ exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
263
+ end
264
+
340
265
  # Is this connection alive and ready for queries?
341
266
  def active?
342
267
  @connection.query 'SELECT 1'
@@ -347,21 +272,25 @@ module ActiveRecord
347
272
 
348
273
  # Close then reopen the connection.
349
274
  def reconnect!
350
- clear_cache!
275
+ super
351
276
  @connection.reset
352
- @open_transactions = 0
353
277
  configure_connection
354
278
  end
355
279
 
356
280
  def reset!
357
281
  clear_cache!
358
- super
282
+ reset_transaction
283
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
284
+ @connection.query 'ROLLBACK'
285
+ end
286
+ @connection.query 'DISCARD ALL'
287
+ configure_connection
359
288
  end
360
289
 
361
290
  # Disconnects from the database if already connected. Otherwise, this
362
291
  # method does nothing.
363
292
  def disconnect!
364
- clear_cache!
293
+ super
365
294
  @connection.close rescue nil
366
295
  end
367
296
 
@@ -379,817 +308,316 @@ module ActiveRecord
379
308
  true
380
309
  end
381
310
 
382
- # Enable standard-conforming strings if available.
383
311
  def set_standard_conforming_strings
384
- old, self.client_min_messages = client_min_messages, 'panic'
385
- execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
386
- ensure
387
- self.client_min_messages = old
388
- end
389
-
390
- def supports_insert_with_returning?
391
- true
312
+ execute('SET standard_conforming_strings = on', 'SCHEMA')
392
313
  end
393
314
 
394
315
  def supports_ddl_transactions?
395
316
  true
396
317
  end
397
318
 
398
- # Returns true, since this connection adapter supports savepoints.
399
- def supports_savepoints?
400
- true
401
- end
402
-
403
- # Returns true.
404
319
  def supports_explain?
405
320
  true
406
321
  end
407
322
 
408
- # Returns the configured supported identifier length supported by PostgreSQL
409
- def table_alias_length
410
- @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
323
+ # Returns true if pg > 9.1
324
+ def supports_extensions?
325
+ postgresql_version >= 90100
411
326
  end
412
327
 
413
- # QUOTING ==================================================
414
-
415
- # Escapes binary strings for bytea input to the database.
416
- def escape_bytea(value)
417
- @connection.escape_bytea(value) if value
328
+ # Range datatypes weren't introduced until PostgreSQL 9.2
329
+ def supports_ranges?
330
+ postgresql_version >= 90200
418
331
  end
419
332
 
420
- # Unescapes bytea output from a database to the binary string it represents.
421
- # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
422
- # on escaped binary output from database drive.
423
- def unescape_bytea(value)
424
- @connection.unescape_bytea(value) if value
333
+ def supports_materialized_views?
334
+ postgresql_version >= 90300
425
335
  end
426
336
 
427
- # Quotes PostgreSQL-specific data types for SQL input.
428
- def quote(value, column = nil) #:nodoc:
429
- return super unless column
430
-
431
- case value
432
- when Float
433
- return super unless value.infinite? && column.type == :datetime
434
- "'#{value.to_s.downcase}'"
435
- when Numeric
436
- return super unless column.sql_type == 'money'
437
- # Not truly string input, so doesn't require (or allow) escape string syntax.
438
- "'#{value}'"
439
- when String
440
- case column.sql_type
441
- when 'bytea' then "'#{escape_bytea(value)}'"
442
- when 'xml' then "xml '#{quote_string(value)}'"
443
- when /^bit/
444
- case value
445
- when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
446
- when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
447
- end
448
- else
449
- super
450
- end
451
- else
452
- super
453
- end
454
- end
455
-
456
- def type_cast(value, column)
457
- return super unless column
458
-
459
- case value
460
- when String
461
- return super unless 'bytea' == column.sql_type
462
- { :value => value, :format => 1 }
463
- else
464
- super
465
- end
337
+ def enable_extension(name)
338
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
339
+ reload_type_map
340
+ }
466
341
  end
467
342
 
468
- # Quotes strings for use in SQL input.
469
- def quote_string(s) #:nodoc:
470
- @connection.escape(s)
343
+ def disable_extension(name)
344
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
345
+ reload_type_map
346
+ }
471
347
  end
472
348
 
473
- # Checks the following cases:
474
- #
475
- # - table_name
476
- # - "table.name"
477
- # - schema_name.table_name
478
- # - schema_name."table.name"
479
- # - "schema.name".table_name
480
- # - "schema.name"."table.name"
481
- def quote_table_name(name)
482
- schema, name_part = extract_pg_identifier_from_name(name.to_s)
483
-
484
- unless name_part
485
- quote_column_name(schema)
486
- else
487
- table_name, name_part = extract_pg_identifier_from_name(name_part)
488
- "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
349
+ def extension_enabled?(name)
350
+ if supports_extensions?
351
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
352
+ 'SCHEMA'
353
+ res.cast_values.first
489
354
  end
490
355
  end
491
356
 
492
- # Quotes column names for use in SQL queries.
493
- def quote_column_name(name) #:nodoc:
494
- PGconn.quote_ident(name.to_s)
495
- end
496
-
497
- # Quote date/time values for use in SQL input. Includes microseconds
498
- # if the value is a Time responding to usec.
499
- def quoted_date(value) #:nodoc:
500
- if value.acts_like?(:time) && value.respond_to?(:usec)
501
- "#{super}.#{sprintf("%06d", value.usec)}"
357
+ def extensions
358
+ if supports_extensions?
359
+ exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
502
360
  else
503
361
  super
504
362
  end
505
363
  end
506
364
 
365
+ # Returns the configured supported identifier length supported by PostgreSQL
366
+ def table_alias_length
367
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
368
+ end
369
+
507
370
  # Set the authorized user for this session
508
371
  def session_auth=(user)
509
372
  clear_cache!
510
373
  exec_query "SET SESSION AUTHORIZATION #{user}"
511
374
  end
512
375
 
513
- # REFERENTIAL INTEGRITY ====================================
514
-
515
- def supports_disable_referential_integrity? #:nodoc:
516
- true
376
+ def use_insert_returning?
377
+ @use_insert_returning
517
378
  end
518
379
 
519
- def disable_referential_integrity #:nodoc:
520
- if supports_disable_referential_integrity? then
521
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
522
- end
523
- yield
524
- ensure
525
- if supports_disable_referential_integrity? then
526
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
527
- end
380
+ def valid_type?(type)
381
+ !native_database_types[type].nil?
528
382
  end
529
383
 
530
- # DATABASE STATEMENTS ======================================
531
-
532
- def explain(arel, binds = [])
533
- sql = "EXPLAIN #{to_sql(arel, binds)}"
534
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
384
+ def update_table_definition(table_name, base) #:nodoc:
385
+ PostgreSQL::Table.new(table_name, base)
535
386
  end
536
387
 
537
- class ExplainPrettyPrinter # :nodoc:
538
- # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
539
- # PostgreSQL shell:
540
- #
541
- # QUERY PLAN
542
- # ------------------------------------------------------------------------------
543
- # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
544
- # Join Filter: (posts.user_id = users.id)
545
- # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
546
- # Index Cond: (id = 1)
547
- # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
548
- # Filter: (posts.user_id = 1)
549
- # (6 rows)
550
- #
551
- def pp(result)
552
- header = result.columns.first
553
- lines = result.rows.map(&:first)
554
-
555
- # We add 2 because there's one char of padding at both sides, note
556
- # the extra hyphens in the example above.
557
- width = [header, *lines].map(&:length).max + 2
558
-
559
- pp = []
560
-
561
- pp << header.center(width).rstrip
562
- pp << '-' * width
563
-
564
- pp += lines.map {|line| " #{line}"}
565
-
566
- nrows = result.rows.length
567
- rows_label = nrows == 1 ? 'row' : 'rows'
568
- pp << "(#{nrows} #{rows_label})"
569
-
570
- pp.join("\n") + "\n"
571
- end
388
+ def lookup_cast_type(sql_type) # :nodoc:
389
+ oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
390
+ super(oid)
572
391
  end
573
392
 
574
- # Executes a SELECT query and returns an array of rows. Each row is an
575
- # array of field values.
576
- def select_rows(sql, name = nil)
577
- select_raw(sql, name).last
393
+ def column_name_for_operation(operation, node) # :nodoc:
394
+ OPERATION_ALIASES.fetch(operation) { operation.downcase }
578
395
  end
579
396
 
580
- # Executes an INSERT query and returns the new record's ID
581
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
582
- unless pk
583
- # Extract the table from the insert sql. Yuck.
584
- table_ref = extract_table_ref_from_insert_sql(sql)
585
- pk = primary_key(table_ref) if table_ref
586
- end
397
+ OPERATION_ALIASES = { # :nodoc:
398
+ "maximum" => "max",
399
+ "minimum" => "min",
400
+ "average" => "avg",
401
+ }
587
402
 
588
- if pk
589
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
590
- else
591
- super
592
- end
593
- end
594
- alias :create :insert
403
+ protected
595
404
 
596
- # create a 2D array representing the result set
597
- def result_as_array(res) #:nodoc:
598
- # check if we have any binary column and if they need escaping
599
- ftypes = Array.new(res.nfields) do |i|
600
- [i, res.ftype(i)]
405
+ # Returns the version of the connected PostgreSQL server.
406
+ def postgresql_version
407
+ @connection.server_version
601
408
  end
602
409
 
603
- rows = res.values
604
- return rows unless ftypes.any? { |_, x|
605
- x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
606
- }
607
-
608
- typehash = ftypes.group_by { |_, type| type }
609
- binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
610
- monies = typehash[MONEY_COLUMN_TYPE_OID] || []
410
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
411
+ FOREIGN_KEY_VIOLATION = "23503"
412
+ UNIQUE_VIOLATION = "23505"
611
413
 
612
- rows.each do |row|
613
- # unescape string passed BYTEA field (OID == 17)
614
- binaries.each do |index, _|
615
- row[index] = unescape_bytea(row[index])
616
- end
414
+ def translate_exception(exception, message)
415
+ return exception unless exception.respond_to?(:result)
617
416
 
618
- # If this is a money type column and there are any currency symbols,
619
- # then strip them off. Indeed it would be prettier to do this in
620
- # PostgreSQLColumn.string_to_decimal but would break form input
621
- # fields that call value_before_type_cast.
622
- monies.each do |index, _|
623
- data = row[index]
624
- # Because money output is formatted according to the locale, there are two
625
- # cases to consider (note the decimal separators):
626
- # (1) $12,345,678.12
627
- # (2) $12.345.678,12
628
- case data
629
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
630
- data.gsub!(/[^-\d.]/, '')
631
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
632
- data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
633
- end
417
+ case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
418
+ when UNIQUE_VIOLATION
419
+ RecordNotUnique.new(message, exception)
420
+ when FOREIGN_KEY_VIOLATION
421
+ InvalidForeignKey.new(message, exception)
422
+ else
423
+ super
634
424
  end
635
425
  end
636
- end
637
-
638
-
639
- # Queries the database and returns the results in an Array-like object
640
- def query(sql, name = nil) #:nodoc:
641
- log(sql, name) do
642
- result_as_array @connection.async_exec(sql)
643
- end
644
- end
645
-
646
- # Executes an SQL statement, returning a PGresult object on success
647
- # or raising a PGError exception otherwise.
648
- def execute(sql, name = nil)
649
- log(sql, name) do
650
- @connection.async_exec(sql)
651
- end
652
- end
653
426
 
654
- def substitute_at(column, index)
655
- Arel::Nodes::BindParam.new "$#{index + 1}"
656
- end
427
+ private
657
428
 
658
- def exec_query(sql, name = 'SQL', binds = [])
659
- log(sql, name, binds) do
660
- result = binds.empty? ? exec_no_cache(sql, binds) :
661
- exec_cache(sql, binds)
429
+ def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
430
+ if !type_map.key?(oid)
431
+ load_additional_types(type_map, [oid])
432
+ end
662
433
 
663
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
664
- result.clear
665
- return ret
666
- end
667
- end
434
+ type_map.fetch(oid, fmod, sql_type) {
435
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
436
+ Type::Value.new.tap do |cast_type|
437
+ type_map.register_type(oid, cast_type)
438
+ end
439
+ }
440
+ end
441
+
442
+ def initialize_type_map(m) # :nodoc:
443
+ register_class_with_limit m, 'int2', OID::Integer
444
+ register_class_with_limit m, 'int4', OID::Integer
445
+ register_class_with_limit m, 'int8', OID::Integer
446
+ m.alias_type 'oid', 'int2'
447
+ m.register_type 'float4', OID::Float.new
448
+ m.alias_type 'float8', 'float4'
449
+ m.register_type 'text', Type::Text.new
450
+ register_class_with_limit m, 'varchar', Type::String
451
+ m.alias_type 'char', 'varchar'
452
+ m.alias_type 'name', 'varchar'
453
+ m.alias_type 'bpchar', 'varchar'
454
+ m.register_type 'bool', Type::Boolean.new
455
+ register_class_with_limit m, 'bit', OID::Bit
456
+ register_class_with_limit m, 'varbit', OID::BitVarying
457
+ m.alias_type 'timestamptz', 'timestamp'
458
+ m.register_type 'date', OID::Date.new
459
+ m.register_type 'time', OID::Time.new
460
+
461
+ m.register_type 'money', OID::Money.new
462
+ m.register_type 'bytea', OID::Bytea.new
463
+ m.register_type 'point', OID::Point.new
464
+ m.register_type 'hstore', OID::Hstore.new
465
+ m.register_type 'json', OID::Json.new
466
+ m.register_type 'jsonb', OID::Jsonb.new
467
+ m.register_type 'cidr', OID::Cidr.new
468
+ m.register_type 'inet', OID::Inet.new
469
+ m.register_type 'uuid', OID::Uuid.new
470
+ m.register_type 'xml', OID::Xml.new
471
+ m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
472
+ m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
473
+ m.register_type 'citext', OID::SpecializedString.new(:citext)
474
+ m.register_type 'ltree', OID::SpecializedString.new(:ltree)
475
+
476
+ # FIXME: why are we keeping these types as strings?
477
+ m.alias_type 'interval', 'varchar'
478
+ m.alias_type 'path', 'varchar'
479
+ m.alias_type 'line', 'varchar'
480
+ m.alias_type 'polygon', 'varchar'
481
+ m.alias_type 'circle', 'varchar'
482
+ m.alias_type 'lseg', 'varchar'
483
+ m.alias_type 'box', 'varchar'
484
+
485
+ m.register_type 'timestamp' do |_, _, sql_type|
486
+ precision = extract_precision(sql_type)
487
+ OID::DateTime.new(precision: precision)
488
+ end
668
489
 
669
- def exec_delete(sql, name = 'SQL', binds = [])
670
- log(sql, name, binds) do
671
- result = binds.empty? ? exec_no_cache(sql, binds) :
672
- exec_cache(sql, binds)
673
- affected = result.cmd_tuples
674
- result.clear
675
- affected
676
- end
677
- end
678
- alias :exec_update :exec_delete
490
+ m.register_type 'numeric' do |_, fmod, sql_type|
491
+ precision = extract_precision(sql_type)
492
+ scale = extract_scale(sql_type)
493
+
494
+ # The type for the numeric depends on the width of the field,
495
+ # so we'll do something special here.
496
+ #
497
+ # When dealing with decimal columns:
498
+ #
499
+ # places after decimal = fmod - 4 & 0xffff
500
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
501
+ if fmod && (fmod - 4 & 0xffff).zero?
502
+ # FIXME: Remove this class, and the second argument to
503
+ # lookups on PG
504
+ Type::DecimalWithoutScale.new(precision: precision)
505
+ else
506
+ OID::Decimal.new(precision: precision, scale: scale)
507
+ end
508
+ end
679
509
 
680
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
681
- unless pk
682
- # Extract the table from the insert sql. Yuck.
683
- table_ref = extract_table_ref_from_insert_sql(sql)
684
- pk = primary_key(table_ref) if table_ref
510
+ load_additional_types(m)
685
511
  end
686
512
 
687
- sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
688
-
689
- [sql, binds]
690
- end
691
-
692
- # Executes an UPDATE query and returns the number of affected tuples.
693
- def update_sql(sql, name = nil)
694
- super.cmd_tuples
695
- end
696
-
697
- # Begins a transaction.
698
- def begin_db_transaction
699
- execute "BEGIN"
700
- end
701
-
702
- # Commits a transaction.
703
- def commit_db_transaction
704
- execute "COMMIT"
705
- end
706
-
707
- # Aborts a transaction.
708
- def rollback_db_transaction
709
- execute "ROLLBACK"
710
- end
711
-
712
- def outside_transaction?
713
- @connection.transaction_status == PGconn::PQTRANS_IDLE
714
- end
715
-
716
- def create_savepoint
717
- execute("SAVEPOINT #{current_savepoint_name}")
718
- end
719
-
720
- def rollback_to_savepoint
721
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
722
- end
723
-
724
- def release_savepoint
725
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
726
- end
727
-
728
- # SCHEMA STATEMENTS ========================================
729
-
730
- # Drops the database specified on the +name+ attribute
731
- # and creates it again using the provided +options+.
732
- def recreate_database(name, options = {}) #:nodoc:
733
- drop_database(name)
734
- create_database(name, options)
735
- end
736
-
737
- # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
738
- # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
739
- # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
740
- #
741
- # Example:
742
- # create_database config[:database], config
743
- # create_database 'foo_development', :encoding => 'unicode'
744
- def create_database(name, options = {})
745
- options = options.reverse_merge(:encoding => "utf8")
746
-
747
- option_string = options.symbolize_keys.sum do |key, value|
748
- case key
749
- when :owner
750
- " OWNER = \"#{value}\""
751
- when :template
752
- " TEMPLATE = \"#{value}\""
753
- when :encoding
754
- " ENCODING = '#{value}'"
755
- when :tablespace
756
- " TABLESPACE = \"#{value}\""
757
- when :connection_limit
758
- " CONNECTION LIMIT = #{value}"
513
+ def extract_limit(sql_type) # :nodoc:
514
+ case sql_type
515
+ when /^bigint/i, /^int8/i
516
+ 8
517
+ when /^smallint/i
518
+ 2
759
519
  else
760
- ""
520
+ super
761
521
  end
762
522
  end
763
523
 
764
- execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
765
- end
766
-
767
- # Drops a PostgreSQL database.
768
- #
769
- # Example:
770
- # drop_database 'matt_development'
771
- def drop_database(name) #:nodoc:
772
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
773
- end
774
-
775
- # Returns the list of all tables in the schema search path or a specified schema.
776
- def tables(name = nil)
777
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
778
- SELECT tablename
779
- FROM pg_tables
780
- WHERE schemaname = ANY (current_schemas(false))
781
- SQL
782
- end
783
-
784
- # Returns true if table exists.
785
- # If the schema is not specified as part of +name+ then it will only find tables within
786
- # the current schema search path (regardless of permissions to access tables in other schemas)
787
- def table_exists?(name)
788
- schema, table = Utils.extract_schema_and_table(name.to_s)
789
- return false unless table
790
-
791
- binds = [[nil, table]]
792
- binds << [nil, schema] if schema
793
-
794
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
795
- SELECT COUNT(*)
796
- FROM pg_class c
797
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
798
- WHERE c.relkind in ('v','r')
799
- AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
800
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
801
- SQL
802
- end
803
-
804
- # Returns true if schema exists.
805
- def schema_exists?(name)
806
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
807
- SELECT COUNT(*)
808
- FROM pg_namespace
809
- WHERE nspname = '#{name}'
810
- SQL
811
- end
812
-
813
- # Returns an array of indexes for the given table.
814
- def indexes(table_name, name = nil)
815
- result = query(<<-SQL, 'SCHEMA')
816
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
817
- FROM pg_class t
818
- INNER JOIN pg_index d ON t.oid = d.indrelid
819
- INNER JOIN pg_class i ON d.indexrelid = i.oid
820
- WHERE i.relkind = 'i'
821
- AND d.indisprimary = 'f'
822
- AND t.relname = '#{table_name}'
823
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
824
- ORDER BY i.relname
825
- SQL
826
-
827
-
828
- result.map do |row|
829
- index_name = row[0]
830
- unique = row[1] == 't'
831
- indkey = row[2].split(" ")
832
- inddef = row[3]
833
- oid = row[4]
834
-
835
- columns = Hash[query(<<-SQL, "SCHEMA")]
836
- SELECT a.attnum, a.attname
837
- FROM pg_attribute a
838
- WHERE a.attrelid = #{oid}
839
- AND a.attnum IN (#{indkey.join(",")})
840
- SQL
841
-
842
- column_names = columns.values_at(*indkey).compact
843
-
844
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
845
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
846
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
847
-
848
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
849
- end.compact
850
- end
851
-
852
- # Returns the list of all column definitions for a table.
853
- def columns(table_name, name = nil)
854
- # Limit, precision, and scale are all handled by the superclass.
855
- column_definitions(table_name).collect do |column_name, type, default, notnull|
856
- PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
857
- end
858
- end
859
-
860
- # Returns the current database name.
861
- def current_database
862
- query('select current_database()', 'SCHEMA')[0][0]
863
- end
864
-
865
- # Returns the current schema name.
866
- def current_schema
867
- query('SELECT current_schema', 'SCHEMA')[0][0]
868
- end
869
-
870
- # Returns the current database encoding format.
871
- def encoding
872
- query(<<-end_sql, 'SCHEMA')[0][0]
873
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
874
- WHERE pg_database.datname LIKE '#{current_database}'
875
- end_sql
876
- end
877
-
878
- # Sets the schema search path to a string of comma-separated schema names.
879
- # Names beginning with $ have to be quoted (e.g. $user => '$user').
880
- # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
881
- #
882
- # This should be not be called manually but set in database.yml.
883
- def schema_search_path=(schema_csv)
884
- if schema_csv
885
- execute("SET search_path TO #{schema_csv}", 'SCHEMA')
886
- @schema_search_path = schema_csv
887
- end
888
- end
889
-
890
- # Returns the active schema search path.
891
- def schema_search_path
892
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
893
- end
894
-
895
- # Returns the current client message level.
896
- def client_min_messages
897
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
898
- end
899
-
900
- # Set the client message level.
901
- def client_min_messages=(level)
902
- execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
903
- end
904
-
905
- # Returns the sequence name for a table's primary key or some other specified key.
906
- def default_sequence_name(table_name, pk = nil) #:nodoc:
907
- serial_sequence(table_name, pk || 'id').split('.').last
908
- rescue ActiveRecord::StatementInvalid
909
- "#{table_name}_#{pk || 'id'}_seq"
910
- end
911
-
912
- def serial_sequence(table, column)
913
- result = exec_query(<<-eosql, 'SCHEMA')
914
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
915
- eosql
916
- result.rows.first.first
917
- end
918
-
919
- # Resets the sequence of a table's primary key to the maximum value.
920
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
921
- unless pk and sequence
922
- default_pk, default_sequence = pk_and_sequence_for(table)
923
-
924
- pk ||= default_pk
925
- sequence ||= default_sequence
926
- end
927
-
928
- if @logger && pk && !sequence
929
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
930
- end
931
-
932
- if pk && sequence
933
- quoted_sequence = quote_table_name(sequence)
934
-
935
- select_value <<-end_sql, 'SCHEMA'
936
- 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)
937
- end_sql
938
- end
939
- end
940
-
941
- # Returns a table's primary key and belonging sequence.
942
- def pk_and_sequence_for(table) #:nodoc:
943
- # First try looking for a sequence with a dependency on the
944
- # given table's primary key.
945
- result = query(<<-end_sql, 'SCHEMA')[0]
946
- SELECT attr.attname, seq.relname
947
- FROM pg_class seq,
948
- pg_attribute attr,
949
- pg_depend dep,
950
- pg_namespace name,
951
- pg_constraint cons
952
- WHERE seq.oid = dep.objid
953
- AND seq.relkind = 'S'
954
- AND attr.attrelid = dep.refobjid
955
- AND attr.attnum = dep.refobjsubid
956
- AND attr.attrelid = cons.conrelid
957
- AND attr.attnum = cons.conkey[1]
958
- AND cons.contype = 'p'
959
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
960
- end_sql
961
-
962
- if result.nil? or result.empty?
963
- result = query(<<-end_sql, 'SCHEMA')[0]
964
- SELECT attr.attname,
965
- CASE
966
- WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
967
- substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
968
- strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
969
- ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
970
- END
971
- FROM pg_class t
972
- JOIN pg_attribute attr ON (t.oid = attrelid)
973
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
974
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
975
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
976
- AND cons.contype = 'p'
977
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
978
- end_sql
524
+ # Extracts the value from a PostgreSQL column default definition.
525
+ def extract_value_from_default(oid, default) # :nodoc:
526
+ case default
527
+ # Quoted types
528
+ when /\A[\(B]?'(.*)'::/m
529
+ $1.gsub(/''/, "'")
530
+ # Boolean types
531
+ when 'true', 'false'
532
+ default
533
+ # Numeric types
534
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
535
+ $1
536
+ # Object identifier types
537
+ when /\A-?\d+\z/
538
+ $1
539
+ else
540
+ # Anything else is blank, some user type, or some function
541
+ # and we can't know the value of that, so return nil.
542
+ nil
543
+ end
979
544
  end
980
545
 
981
- [result.first, result.last]
982
- rescue
983
- nil
984
- end
985
-
986
- # Returns just a table's primary key
987
- def primary_key(table)
988
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
989
- SELECT attr.attname
990
- FROM pg_attribute attr
991
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
992
- WHERE cons.contype = 'p'
993
- AND cons.conrelid = '#{quote_table_name(table)}'::regclass
994
- end_sql
995
-
996
- row && row.first
997
- end
998
-
999
- # Renames a table.
1000
- # Also renames a table's primary key sequence if the sequence name matches the
1001
- # Active Record default.
1002
- #
1003
- # Example:
1004
- # rename_table('octopuses', 'octopi')
1005
- def rename_table(name, new_name)
1006
- clear_cache!
1007
- execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
1008
- pk, seq = pk_and_sequence_for(new_name)
1009
- if seq == "#{name}_#{pk}_seq"
1010
- new_seq = "#{new_name}_#{pk}_seq"
1011
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
546
+ def extract_default_function(default_value, default) # :nodoc:
547
+ default if has_default_function?(default_value, default)
1012
548
  end
1013
- end
1014
-
1015
- # Adds a new column to the named table.
1016
- # See TableDefinition#column for details of the options you can use.
1017
- def add_column(table_name, column_name, type, options = {})
1018
- clear_cache!
1019
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1020
- add_column_options!(add_column_sql, options)
1021
549
 
1022
- execute add_column_sql
1023
- end
1024
-
1025
- # Changes the column of a table.
1026
- def change_column(table_name, column_name, type, options = {})
1027
- clear_cache!
1028
- quoted_table_name = quote_table_name(table_name)
1029
-
1030
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1031
-
1032
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
1033
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
1034
- end
1035
-
1036
- # Changes the default value of a table column.
1037
- def change_column_default(table_name, column_name, default)
1038
- clear_cache!
1039
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
1040
- end
1041
-
1042
- def change_column_null(table_name, column_name, null, default = nil)
1043
- clear_cache!
1044
- unless null || default.nil?
1045
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
550
+ def has_default_function?(default_value, default) # :nodoc:
551
+ !default_value && (%r{\w+\(.*\)} === default)
1046
552
  end
1047
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
1048
- end
1049
-
1050
- # Renames a column in a table.
1051
- def rename_column(table_name, column_name, new_column_name)
1052
- clear_cache!
1053
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1054
- end
1055
553
 
1056
- def remove_index!(table_name, index_name) #:nodoc:
1057
- execute "DROP INDEX #{quote_table_name(index_name)}"
1058
- end
554
+ def load_additional_types(type_map, oids = nil) # :nodoc:
555
+ initializer = OID::TypeMapInitializer.new(type_map)
1059
556
 
1060
- def rename_index(table_name, old_name, new_name)
1061
- execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
1062
- end
1063
-
1064
- def index_name_length
1065
- 63
1066
- end
1067
-
1068
- # Maps logical Rails types to PostgreSQL-specific data types.
1069
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1070
- case type.to_s
1071
- when 'binary'
1072
- # PostgreSQL doesn't support limits on binary (bytea) columns.
1073
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
1074
- case limit
1075
- when nil, 0..0x3fffffff; super(type)
1076
- else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
557
+ if supports_ranges?
558
+ query = <<-SQL
559
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
560
+ FROM pg_type as t
561
+ LEFT JOIN pg_range as r ON oid = rngtypid
562
+ SQL
563
+ else
564
+ query = <<-SQL
565
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
566
+ FROM pg_type as t
567
+ SQL
1077
568
  end
1078
- when 'text'
1079
- # PostgreSQL doesn't support limits on text columns.
1080
- # The hard limit is 1Gb, according to section 8.3 in the manual.
1081
- case limit
1082
- when nil, 0..0x3fffffff; super(type)
1083
- else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
569
+
570
+ if oids
571
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
572
+ else
573
+ query += initializer.query_conditions_for_initial_load(type_map)
1084
574
  end
1085
- when 'integer'
1086
- return 'integer' unless limit
1087
-
1088
- case limit
1089
- when 1, 2; 'smallint'
1090
- when 3, 4; 'integer'
1091
- when 5..8; 'bigint'
1092
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
575
+
576
+ execute_and_clear(query, 'SCHEMA', []) do |records|
577
+ initializer.run(records)
1093
578
  end
1094
- else
1095
- super
1096
579
  end
1097
- end
1098
580
 
1099
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1100
- #
1101
- # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
1102
- # requires that the ORDER BY include the distinct column.
1103
- #
1104
- # distinct("posts.id", "posts.created_at desc")
1105
- def distinct(columns, orders) #:nodoc:
1106
- return "DISTINCT #{columns}" if orders.empty?
1107
-
1108
- # Construct a clean list of column names from the ORDER BY clause, removing
1109
- # any ASC/DESC modifiers
1110
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
1111
- order_columns.delete_if { |c| c.blank? }
1112
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
1113
-
1114
- "DISTINCT #{columns}, #{order_columns * ', '}"
1115
- end
581
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
1116
582
 
1117
- module Utils
1118
- extend self
1119
-
1120
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1121
- # +schema_name+ is nil if not specified in +name+.
1122
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1123
- # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
1124
- #
1125
- # * <tt>table_name</tt>
1126
- # * <tt>"table.name"</tt>
1127
- # * <tt>schema_name.table_name</tt>
1128
- # * <tt>schema_name."table.name"</tt>
1129
- # * <tt>"schema.name"."table name"</tt>
1130
- def extract_schema_and_table(name)
1131
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1132
- [schema, table]
583
+ def execute_and_clear(sql, name, binds)
584
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
585
+ exec_cache(sql, name, binds)
586
+ ret = yield result
587
+ result.clear
588
+ ret
1133
589
  end
1134
- end
1135
590
 
1136
- protected
1137
- # Returns the version of the connected PostgreSQL server.
1138
- def postgresql_version
1139
- @connection.server_version
591
+ def exec_no_cache(sql, name, binds)
592
+ log(sql, name, binds) { @connection.async_exec(sql, []) }
1140
593
  end
1141
594
 
1142
- # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
1143
- FOREIGN_KEY_VIOLATION = "23503"
1144
- UNIQUE_VIOLATION = "23505"
1145
-
1146
- def translate_exception(exception, message)
1147
- return exception unless exception.respond_to?(:result)
595
+ def exec_cache(sql, name, binds)
596
+ stmt_key = prepare_statement(sql)
597
+ type_casted_binds = binds.map { |col, val|
598
+ [col, type_cast(val, col)]
599
+ }
1148
600
 
1149
- case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
1150
- when UNIQUE_VIOLATION
1151
- RecordNotUnique.new(message, exception)
1152
- when FOREIGN_KEY_VIOLATION
1153
- InvalidForeignKey.new(message, exception)
1154
- else
1155
- super
601
+ log(sql, name, type_casted_binds, stmt_key) do
602
+ @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
1156
603
  end
1157
- end
604
+ rescue ActiveRecord::StatementInvalid => e
605
+ pgerror = e.original_exception
1158
606
 
1159
- private
1160
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1161
-
1162
- def exec_no_cache(sql, binds)
1163
- @connection.async_exec(sql, [])
1164
- end
1165
-
1166
- def exec_cache(sql, binds)
607
+ # Get the PG code for the failure. Annoyingly, the code for
608
+ # prepared statements whose return value may have changed is
609
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
610
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1167
611
  begin
1168
- stmt_key = prepare_statement sql
1169
-
1170
- # Clear the queue
1171
- @connection.get_last_result
1172
- @connection.send_query_prepared(stmt_key, binds.map { |col, val|
1173
- type_cast(val, col)
1174
- })
1175
- @connection.block
1176
- @connection.get_last_result
1177
- rescue PGError => e
1178
- # Get the PG code for the failure. Annoyingly, the code for
1179
- # prepared statements whose return value may have changed is
1180
- # FEATURE_NOT_SUPPORTED. Check here for more details:
1181
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1182
- begin
1183
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
1184
- rescue
1185
- raise e
1186
- end
1187
- if FEATURE_NOT_SUPPORTED == code
1188
- @statements.delete sql_key(sql)
1189
- retry
1190
- else
1191
- raise e
1192
- end
612
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
613
+ rescue
614
+ raise e
615
+ end
616
+ if FEATURE_NOT_SUPPORTED == code
617
+ @statements.delete sql_key(sql)
618
+ retry
619
+ else
620
+ raise e
1193
621
  end
1194
622
  end
1195
623
 
@@ -1205,28 +633,35 @@ module ActiveRecord
1205
633
  sql_key = sql_key(sql)
1206
634
  unless @statements.key? sql_key
1207
635
  nextkey = @statements.next_key
1208
- @connection.prepare nextkey, sql
636
+ begin
637
+ @connection.prepare nextkey, sql
638
+ rescue => e
639
+ raise translate_exception_class(e, sql)
640
+ end
641
+ # Clear the queue
642
+ @connection.get_last_result
1209
643
  @statements[sql_key] = nextkey
1210
644
  end
1211
645
  @statements[sql_key]
1212
646
  end
1213
647
 
1214
- # The internal PostgreSQL identifier of the money data type.
1215
- MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
1216
- # The internal PostgreSQL identifier of the BYTEA data type.
1217
- BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
1218
-
1219
648
  # Connects to a PostgreSQL server and sets up the adapter depending on the
1220
649
  # connected server's characteristics.
1221
650
  def connect
1222
- @connection = PGconn.connect(*@connection_parameters)
651
+ @connection = PGconn.connect(@connection_parameters)
1223
652
 
1224
653
  # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
1225
654
  # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
1226
655
  # should know about this but can't detect it there, so deal with it here.
1227
- PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
656
+ OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
1228
657
 
1229
658
  configure_connection
659
+ rescue ::PG::Error => error
660
+ if error.message.include?("does not exist")
661
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
662
+ else
663
+ raise
664
+ end
1230
665
  end
1231
666
 
1232
667
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -1235,39 +670,45 @@ module ActiveRecord
1235
670
  if @config[:encoding]
1236
671
  @connection.set_client_encoding(@config[:encoding])
1237
672
  end
1238
- self.client_min_messages = @config[:min_messages] if @config[:min_messages]
673
+ self.client_min_messages = @config[:min_messages] || 'warning'
1239
674
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
1240
675
 
1241
- # Use standard-conforming strings if available so we don't have to do the E'...' dance.
676
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
1242
677
  set_standard_conforming_strings
1243
678
 
1244
679
  # If using Active Record's time zone support configure the connection to return
1245
680
  # TIMESTAMP WITH ZONE types in UTC.
681
+ # (SET TIME ZONE does not use an equals sign like other SET variables)
1246
682
  if ActiveRecord::Base.default_timezone == :utc
1247
683
  execute("SET time zone 'UTC'", 'SCHEMA')
1248
684
  elsif @local_tz
1249
685
  execute("SET time zone '#{@local_tz}'", 'SCHEMA')
1250
686
  end
687
+
688
+ # SET statements from :variables config hash
689
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
690
+ variables = @config[:variables] || {}
691
+ variables.map do |k, v|
692
+ if v == ':default' || v == :default
693
+ # Sets the value to the global or compile default
694
+ execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
695
+ elsif !v.nil?
696
+ execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
697
+ end
698
+ end
1251
699
  end
1252
700
 
1253
701
  # Returns the current ID of a table's sequence.
1254
702
  def last_insert_id(sequence_name) #:nodoc:
1255
- r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1256
- Integer(r.rows.first.first)
703
+ Integer(last_insert_id_value(sequence_name))
1257
704
  end
1258
705
 
1259
- # Executes a SELECT query and returns the results, performing any data type
1260
- # conversions that are required to be performed here instead of in PostgreSQLColumn.
1261
- def select(sql, name = nil, binds = [])
1262
- exec_query(sql, name, binds).to_a
706
+ def last_insert_id_value(sequence_name)
707
+ last_insert_id_result(sequence_name).rows.first.first
1263
708
  end
1264
709
 
1265
- def select_raw(sql, name = nil)
1266
- res = execute(sql, name)
1267
- results = result_as_array(res)
1268
- fields = res.fields
1269
- res.clear
1270
- return fields, results
710
+ def last_insert_id_result(sequence_name) #:nodoc:
711
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1271
712
  end
1272
713
 
1273
714
  # Returns the list of a table's column names, data types, and default values.
@@ -1288,35 +729,25 @@ module ActiveRecord
1288
729
  # Query implementation notes:
1289
730
  # - format_type includes the column size constraint, e.g. varchar(50)
1290
731
  # - ::regclass is a function that gives the id for a table name
1291
- def column_definitions(table_name) #:nodoc:
732
+ def column_definitions(table_name) # :nodoc:
1292
733
  exec_query(<<-end_sql, 'SCHEMA').rows
1293
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
734
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1294
735
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
1295
- FROM pg_attribute a LEFT JOIN pg_attrdef d
1296
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1297
- WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1298
- AND a.attnum > 0 AND NOT a.attisdropped
1299
- ORDER BY a.attnum
736
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
737
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
738
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
739
+ AND a.attnum > 0 AND NOT a.attisdropped
740
+ ORDER BY a.attnum
1300
741
  end_sql
1301
742
  end
1302
743
 
1303
- def extract_pg_identifier_from_name(name)
1304
- match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1305
-
1306
- if match_data
1307
- rest = name[match_data[0].length, name.length]
1308
- rest = rest[1, rest.length] if rest.start_with? "."
1309
- [match_data[1], (rest.length > 0 ? rest : nil)]
1310
- end
1311
- end
1312
-
1313
- def extract_table_ref_from_insert_sql(sql)
1314
- sql[/into\s+([^\(]*).*values\s*\(/i]
744
+ def extract_table_ref_from_insert_sql(sql) # :nodoc:
745
+ sql[/into\s+([^\(]*).*values\s*\(/im]
1315
746
  $1.strip if $1
1316
747
  end
1317
748
 
1318
- def table_definition
1319
- TableDefinition.new(self)
749
+ def create_table_definition(name, temporary, options, as = nil) # :nodoc:
750
+ PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
1320
751
  end
1321
752
  end
1322
753
  end