activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,63 +1,148 @@
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
+ require 'active_record/connection_adapters/postgresql/oid'
4
+ require 'active_record/connection_adapters/postgresql/cast'
5
+ require 'active_record/connection_adapters/postgresql/array_parser'
6
+ require 'active_record/connection_adapters/postgresql/quoting'
7
+ require 'active_record/connection_adapters/postgresql/schema_statements'
8
+ require 'active_record/connection_adapters/postgresql/database_statements'
9
+ require 'active_record/connection_adapters/postgresql/referential_integrity'
4
10
  require 'arel/visitors/bind_visitor'
5
11
 
6
12
  # Make sure we're using pg high enough for PGResult#values
7
13
  gem 'pg', '~> 0.11'
8
14
  require 'pg'
9
15
 
16
+ require 'ipaddr'
17
+
10
18
  module ActiveRecord
11
- class Base
19
+ module ConnectionHandling
20
+ VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
21
+ :client_encoding, :options, :application_name, :fallback_application_name,
22
+ :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
23
+ :tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl,
24
+ :requirepeer, :krbsrvname, :gsslib, :service]
25
+
12
26
  # 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
27
+ def postgresql_connection(config) # :nodoc:
28
+ conn_params = config.symbolize_keys
29
+
30
+ conn_params.delete_if { |_, v| v.nil? }
31
+
32
+ # Map ActiveRecords param names to PGs.
33
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
34
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
35
+
36
+ # Forward only valid config params to PGconn.connect.
37
+ conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
25
38
 
26
39
  # The postgres drivers don't allow the creation of an unconnected PGconn object,
27
40
  # 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)
41
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
29
42
  end
30
43
  end
31
44
 
32
45
  module ConnectionAdapters
33
46
  # PostgreSQL-specific extensions to column definitions in a table.
34
47
  class PostgreSQLColumn < Column #:nodoc:
48
+ attr_accessor :array
35
49
  # 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)
50
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
51
+ @oid_type = oid_type
52
+ if sql_type =~ /\[\]$/
53
+ @array = true
54
+ super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
55
+ else
56
+ @array = false
57
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
58
+ end
38
59
  end
39
60
 
40
61
  # :stopdoc:
41
62
  class << self
63
+ include ConnectionAdapters::PostgreSQLColumn::Cast
64
+ include ConnectionAdapters::PostgreSQLColumn::ArrayParser
42
65
  attr_accessor :money_precision
43
- def string_to_time(string)
44
- return string unless String === string
66
+ end
67
+ # :startdoc:
45
68
 
46
- case string
47
- when 'infinity' then 1.0 / 0.0
48
- when '-infinity' then -1.0 / 0.0
69
+ # Extracts the value from a PostgreSQL column default definition.
70
+ def self.extract_value_from_default(default)
71
+ # This is a performance optimization for Ruby 1.9.2 in development.
72
+ # If the value is nil, we return nil straight away without checking
73
+ # the regular expressions. If we check each regular expression,
74
+ # Regexp#=== will call NilClass#to_str, which will trigger
75
+ # method_missing (defined by whiny nil in ActiveSupport) which
76
+ # makes this method very very slow.
77
+ return default unless default
78
+
79
+ case default
80
+ when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
81
+ $1
82
+ # Numeric types
83
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
84
+ $1
85
+ # Character types
86
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
87
+ $1
88
+ # Binary data types
89
+ when /\A'(.*)'::bytea\z/m
90
+ $1
91
+ # Date/time types
92
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
93
+ $1
94
+ when /\A'(.*)'::interval\z/
95
+ $1
96
+ # Boolean type
97
+ when 'true'
98
+ true
99
+ when 'false'
100
+ false
101
+ # Geometric types
102
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
103
+ $1
104
+ # Network address types
105
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
106
+ $1
107
+ # Bit string types
108
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
109
+ $1
110
+ # XML type
111
+ when /\A'(.*)'::xml\z/m
112
+ $1
113
+ # Arrays
114
+ when /\A'(.*)'::"?\D+"?\[\]\z/
115
+ $1
116
+ # Hstore
117
+ when /\A'(.*)'::hstore\z/
118
+ $1
119
+ # JSON
120
+ when /\A'(.*)'::json\z/
121
+ $1
122
+ # Object identifier types
123
+ when /\A-?\d+\z/
124
+ $1
49
125
  else
50
- super
51
- end
126
+ # Anything else is blank, some user type, or some function
127
+ # and we can't know the value of that, so return nil.
128
+ nil
52
129
  end
53
130
  end
54
- # :startdoc:
131
+
132
+ def type_cast(value)
133
+ return if value.nil?
134
+ return super if encoded?
135
+
136
+ @oid_type.type_cast value
137
+ end
55
138
 
56
139
  private
140
+
57
141
  def extract_limit(sql_type)
58
142
  case sql_type
59
143
  when /^bigint/i; 8
60
144
  when /^smallint/i; 2
145
+ when /^timestamp/i; nil
61
146
  else super
62
147
  end
63
148
  end
@@ -72,6 +157,8 @@ module ActiveRecord
72
157
  def extract_precision(sql_type)
73
158
  if sql_type == 'money'
74
159
  self.class.money_precision
160
+ elsif sql_type =~ /timestamp/i
161
+ $1.to_i if sql_type =~ /\((\d+)\)/
75
162
  else
76
163
  super
77
164
  end
@@ -80,129 +167,102 @@ module ActiveRecord
80
167
  # Maps PostgreSQL-specific data types to logical Rails types.
81
168
  def simplified_type(field_type)
82
169
  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
170
+ # Numeric and monetary types
171
+ when /^(?:real|double precision)$/
172
+ :float
173
+ # Monetary types
174
+ when 'money'
175
+ :decimal
176
+ when 'hstore'
177
+ :hstore
178
+ when 'ltree'
179
+ :ltree
180
+ # Network address types
181
+ when 'inet'
182
+ :inet
183
+ when 'cidr'
184
+ :cidr
185
+ when 'macaddr'
186
+ :macaddr
187
+ # Character types
188
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
189
+ :string
190
+ # Binary data types
191
+ when 'bytea'
192
+ :binary
193
+ # Date/time types
194
+ when /^timestamp with(?:out)? time zone$/
195
+ :datetime
196
+ when /^interval(?:|\(\d+\))$/
197
+ :string
198
+ # Geometric types
199
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
200
+ :string
201
+ # Bit strings
202
+ when /^bit(?: varying)?(?:\(\d+\))?$/
203
+ :string
204
+ # XML type
205
+ when 'xml'
206
+ :xml
207
+ # tsvector type
208
+ when 'tsvector'
209
+ :tsvector
210
+ # Arrays
211
+ when /^\D+\[\]$/
212
+ :string
213
+ # Object identifier types
214
+ when 'oid'
215
+ :integer
216
+ # UUID type
217
+ when 'uuid'
218
+ :uuid
219
+ # JSON type
220
+ when 'json'
221
+ :json
222
+ # Small and big integer types
223
+ when /^(?:small|big)int$/
224
+ :integer
225
+ when /(num|date|tstz|ts|int4|int8)range$/
226
+ field_type.to_sym
227
+ # Pass through all types that are not specific to PostgreSQL.
228
+ else
229
+ super
185
230
  end
186
231
  end
187
232
  end
188
233
 
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.
234
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
191
235
  #
192
236
  # Options:
193
237
  #
194
- # * <tt>:host</tt> - Defaults to "localhost".
238
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
239
+ # the default is to connect to localhost.
195
240
  # * <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.
241
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
242
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
243
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
199
244
  # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
200
245
  # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
201
246
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
202
247
  # <encoding></tt> call on the connection.
203
248
  # * <tt>:min_messages</tt> - An optional client min messages that is used in a
204
249
  # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
250
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
251
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
252
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
253
+ # defaults to true.
254
+ #
255
+ # Any further options are used as connection parameters to libpq. See
256
+ # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
257
+ # list of parameters.
258
+ #
259
+ # In addition, default connection parameters of libpq can be set per environment variables.
260
+ # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
205
261
  class PostgreSQLAdapter < AbstractAdapter
262
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
263
+ attr_accessor :array
264
+ end
265
+
206
266
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
207
267
  def xml(*args)
208
268
  options = args.extract_options!
@@ -213,32 +273,132 @@ module ActiveRecord
213
273
  options = args.extract_options!
214
274
  column(args[0], 'tsvector', options)
215
275
  end
276
+
277
+ def int4range(name, options = {})
278
+ column(name, 'int4range', options)
279
+ end
280
+
281
+ def int8range(name, options = {})
282
+ column(name, 'int8range', options)
283
+ end
284
+
285
+ def tsrange(name, options = {})
286
+ column(name, 'tsrange', options)
287
+ end
288
+
289
+ def tstzrange(name, options = {})
290
+ column(name, 'tstzrange', options)
291
+ end
292
+
293
+ def numrange(name, options = {})
294
+ column(name, 'numrange', options)
295
+ end
296
+
297
+ def daterange(name, options = {})
298
+ column(name, 'daterange', options)
299
+ end
300
+
301
+ def hstore(name, options = {})
302
+ column(name, 'hstore', options)
303
+ end
304
+
305
+ def ltree(name, options = {})
306
+ column(name, 'ltree', options)
307
+ end
308
+
309
+ def inet(name, options = {})
310
+ column(name, 'inet', options)
311
+ end
312
+
313
+ def cidr(name, options = {})
314
+ column(name, 'cidr', options)
315
+ end
316
+
317
+ def macaddr(name, options = {})
318
+ column(name, 'macaddr', options)
319
+ end
320
+
321
+ def uuid(name, options = {})
322
+ column(name, 'uuid', options)
323
+ end
324
+
325
+ def json(name, options = {})
326
+ column(name, 'json', options)
327
+ end
328
+
329
+ def column(name, type = nil, options = {})
330
+ super
331
+ column = self[name]
332
+ column.array = options[:array]
333
+
334
+ self
335
+ end
336
+
337
+ private
338
+
339
+ def new_column_definition(base, name, type)
340
+ definition = ColumnDefinition.new base, name, type
341
+ @columns << definition
342
+ @columns_hash[name] = definition
343
+ definition
344
+ end
216
345
  end
217
346
 
218
347
  ADAPTER_NAME = 'PostgreSQL'
219
348
 
220
349
  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" }
350
+ primary_key: "serial primary key",
351
+ string: { name: "character varying", limit: 255 },
352
+ text: { name: "text" },
353
+ integer: { name: "integer" },
354
+ float: { name: "float" },
355
+ decimal: { name: "decimal" },
356
+ datetime: { name: "timestamp" },
357
+ timestamp: { name: "timestamp" },
358
+ time: { name: "time" },
359
+ date: { name: "date" },
360
+ daterange: { name: "daterange" },
361
+ numrange: { name: "numrange" },
362
+ tsrange: { name: "tsrange" },
363
+ tstzrange: { name: "tstzrange" },
364
+ int4range: { name: "int4range" },
365
+ int8range: { name: "int8range" },
366
+ binary: { name: "bytea" },
367
+ boolean: { name: "boolean" },
368
+ xml: { name: "xml" },
369
+ tsvector: { name: "tsvector" },
370
+ hstore: { name: "hstore" },
371
+ inet: { name: "inet" },
372
+ cidr: { name: "cidr" },
373
+ macaddr: { name: "macaddr" },
374
+ uuid: { name: "uuid" },
375
+ json: { name: "json" },
376
+ ltree: { name: "ltree" }
235
377
  }
236
378
 
379
+ include Quoting
380
+ include ReferentialIntegrity
381
+ include SchemaStatements
382
+ include DatabaseStatements
383
+
237
384
  # Returns 'PostgreSQL' as adapter name for identification purposes.
238
385
  def adapter_name
239
386
  ADAPTER_NAME
240
387
  end
241
388
 
389
+ # Adds `:array` option to the default set provided by the
390
+ # AbstractAdapter
391
+ def prepare_column_options(column, types)
392
+ spec = super
393
+ spec[:array] = 'true' if column.respond_to?(:array) && column.array
394
+ spec
395
+ end
396
+
397
+ # Adds `:array` as a valid migration key
398
+ def migration_keys
399
+ super + [:array]
400
+ end
401
+
242
402
  # Returns +true+, since this connection adapter supports prepared statement
243
403
  # caching.
244
404
  def supports_statement_cache?
@@ -249,6 +409,14 @@ module ActiveRecord
249
409
  true
250
410
  end
251
411
 
412
+ def supports_partial_index?
413
+ true
414
+ end
415
+
416
+ def supports_transaction_isolation?
417
+ true
418
+ end
419
+
252
420
  class StatementPool < ConnectionAdapters::StatementPool
253
421
  def initialize(connection, max)
254
422
  super
@@ -286,19 +454,20 @@ module ActiveRecord
286
454
  end
287
455
 
288
456
  private
289
- def cache
290
- @cache[$$]
291
- end
292
457
 
293
- def dealloc(key)
294
- @connection.query "DEALLOCATE #{key}" if connection_active?
295
- end
458
+ def cache
459
+ @cache[Process.pid]
460
+ end
296
461
 
297
- def connection_active?
298
- @connection.status == PGconn::CONNECTION_OK
299
- rescue PGError
300
- false
301
- end
462
+ def dealloc(key)
463
+ @connection.query "DEALLOCATE #{key}" if connection_active?
464
+ end
465
+
466
+ def connection_active?
467
+ @connection.status == PGconn::CONNECTION_OK
468
+ rescue PGError
469
+ false
470
+ end
302
471
  end
303
472
 
304
473
  class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
@@ -309,7 +478,7 @@ module ActiveRecord
309
478
  def initialize(connection, logger, connection_parameters, config)
310
479
  super(connection, logger)
311
480
 
312
- if config.fetch(:prepared_statements) { true }
481
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
313
482
  @visitor = Arel::Visitors::PostgreSQL.new self
314
483
  else
315
484
  @visitor = BindSubstitution.new self
@@ -323,13 +492,15 @@ module ActiveRecord
323
492
 
324
493
  connect
325
494
  @statements = StatementPool.new @connection,
326
- config.fetch(:statement_limit) { 1000 }
495
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
327
496
 
328
497
  if postgresql_version < 80200
329
498
  raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
330
499
  end
331
500
 
501
+ initialize_type_map
332
502
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
503
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
333
504
  end
334
505
 
335
506
  # Clears the prepared statements cache.
@@ -347,9 +518,8 @@ module ActiveRecord
347
518
 
348
519
  # Close then reopen the connection.
349
520
  def reconnect!
350
- clear_cache!
521
+ super
351
522
  @connection.reset
352
- @open_transactions = 0
353
523
  configure_connection
354
524
  end
355
525
 
@@ -361,7 +531,7 @@ module ActiveRecord
361
531
  # Disconnects from the database if already connected. Otherwise, this
362
532
  # method does nothing.
363
533
  def disconnect!
364
- clear_cache!
534
+ super
365
535
  @connection.close rescue nil
366
536
  end
367
537
 
@@ -405,103 +575,55 @@ module ActiveRecord
405
575
  true
406
576
  end
407
577
 
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
578
+ # Returns true if pg > 9.2
579
+ def supports_extensions?
580
+ postgresql_version >= 90200
411
581
  end
412
582
 
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
583
+ # Range datatypes weren't introduced until PostgreSQL 9.2
584
+ def supports_ranges?
585
+ postgresql_version >= 90200
418
586
  end
419
587
 
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
588
+ def enable_extension(name)
589
+ exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap {
590
+ reload_type_map
591
+ }
425
592
  end
426
593
 
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
594
+ def disable_extension(name)
595
+ exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap {
596
+ reload_type_map
597
+ }
454
598
  end
455
599
 
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
600
+ def extension_enabled?(name)
601
+ if supports_extensions?
602
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
603
+ 'SCHEMA'
604
+ res.column_types['exists'].type_cast res.rows.first.first
465
605
  end
466
606
  end
467
607
 
468
- # Quotes strings for use in SQL input.
469
- def quote_string(s) #:nodoc:
470
- @connection.escape(s)
471
- end
472
-
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)
608
+ def extensions
609
+ if supports_extensions?
610
+ res = exec_query "SELECT extname from pg_extension", "SCHEMA"
611
+ res.rows.map { |r| res.column_types['extname'].type_cast r.first }
486
612
  else
487
- table_name, name_part = extract_pg_identifier_from_name(name_part)
488
- "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
613
+ super
489
614
  end
490
615
  end
491
616
 
492
- # Quotes column names for use in SQL queries.
493
- def quote_column_name(name) #:nodoc:
494
- PGconn.quote_ident(name.to_s)
617
+ # Returns the configured supported identifier length supported by PostgreSQL
618
+ def table_alias_length
619
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
495
620
  end
496
621
 
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)}"
502
- else
503
- super
622
+ def add_column_options!(sql, options)
623
+ if options[:array] || options[:column].try(:array)
624
+ sql << '[]'
504
625
  end
626
+ super
505
627
  end
506
628
 
507
629
  # Set the authorized user for this session
@@ -510,610 +632,6 @@ module ActiveRecord
510
632
  exec_query "SET SESSION AUTHORIZATION #{user}"
511
633
  end
512
634
 
513
- # REFERENTIAL INTEGRITY ====================================
514
-
515
- def supports_disable_referential_integrity? #:nodoc:
516
- true
517
- end
518
-
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
528
- end
529
-
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))
535
- end
536
-
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
572
- end
573
-
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
578
- end
579
-
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
587
-
588
- if pk
589
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
590
- else
591
- super
592
- end
593
- end
594
- alias :create :insert
595
-
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)]
601
- end
602
-
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] || []
611
-
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
617
-
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
634
- end
635
- 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
-
654
- def substitute_at(column, index)
655
- Arel::Nodes::BindParam.new "$#{index + 1}"
656
- end
657
-
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)
662
-
663
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
664
- result.clear
665
- return ret
666
- end
667
- end
668
-
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
679
-
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
685
- end
686
-
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}"
759
- else
760
- ""
761
- end
762
- end
763
-
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
979
- end
980
-
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)}"
1012
- 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
-
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")
1046
- 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
-
1056
- def remove_index!(table_name, index_name) #:nodoc:
1057
- execute "DROP INDEX #{quote_table_name(index_name)}"
1058
- end
1059
-
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}.")
1077
- 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.")
1084
- 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.")
1093
- end
1094
- else
1095
- super
1096
- end
1097
- end
1098
-
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
1116
-
1117
635
  module Utils
1118
636
  extend self
1119
637
 
@@ -1133,7 +651,12 @@ module ActiveRecord
1133
651
  end
1134
652
  end
1135
653
 
654
+ def use_insert_returning?
655
+ @use_insert_returning
656
+ end
657
+
1136
658
  protected
659
+
1137
660
  # Returns the version of the connected PostgreSQL server.
1138
661
  def postgresql_version
1139
662
  @connection.server_version
@@ -1144,8 +667,6 @@ module ActiveRecord
1144
667
  UNIQUE_VIOLATION = "23505"
1145
668
 
1146
669
  def translate_exception(exception, message)
1147
- return exception unless exception.respond_to?(:result)
1148
-
1149
670
  case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
1150
671
  when UNIQUE_VIOLATION
1151
672
  RecordNotUnique.new(message, exception)
@@ -1157,39 +678,67 @@ module ActiveRecord
1157
678
  end
1158
679
 
1159
680
  private
681
+
682
+ def reload_type_map
683
+ OID::TYPE_MAP.clear
684
+ initialize_type_map
685
+ end
686
+
687
+ def initialize_type_map
688
+ result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
689
+ leaves, nodes = result.partition { |row| row['typelem'] == '0' }
690
+
691
+ # populate the leaf nodes
692
+ leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
693
+ OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
694
+ end
695
+
696
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
697
+
698
+ # populate composite types
699
+ nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
700
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
701
+ OID::TYPE_MAP[row['oid'].to_i] = vector
702
+ end
703
+
704
+ # populate array types
705
+ arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
706
+ array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
707
+ OID::TYPE_MAP[row['oid'].to_i] = array
708
+ end
709
+ end
710
+
1160
711
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1161
712
 
1162
713
  def exec_no_cache(sql, binds)
1163
- @connection.async_exec(sql, [])
714
+ @connection.async_exec(sql)
1164
715
  end
1165
716
 
1166
717
  def exec_cache(sql, binds)
718
+ stmt_key = prepare_statement sql
719
+
720
+ # Clear the queue
721
+ @connection.get_last_result
722
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
723
+ type_cast(val, col)
724
+ })
725
+ @connection.block
726
+ @connection.get_last_result
727
+ rescue PGError => e
728
+ # Get the PG code for the failure. Annoyingly, the code for
729
+ # prepared statements whose return value may have changed is
730
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
731
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1167
732
  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
733
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
734
+ rescue
735
+ raise e
736
+ end
737
+ if FEATURE_NOT_SUPPORTED == code
738
+ @statements.delete sql_key(sql)
739
+ retry
740
+ else
741
+ raise e
1193
742
  end
1194
743
  end
1195
744
 
@@ -1219,7 +768,7 @@ module ActiveRecord
1219
768
  # Connects to a PostgreSQL server and sets up the adapter depending on the
1220
769
  # connected server's characteristics.
1221
770
  def connect
1222
- @connection = PGconn.connect(*@connection_parameters)
771
+ @connection = PGconn.connect(@connection_parameters)
1223
772
 
1224
773
  # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
1225
774
  # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
@@ -1235,7 +784,7 @@ module ActiveRecord
1235
784
  if @config[:encoding]
1236
785
  @connection.set_client_encoding(@config[:encoding])
1237
786
  end
1238
- self.client_min_messages = @config[:min_messages] if @config[:min_messages]
787
+ self.client_min_messages = @config[:min_messages] || 'warning'
1239
788
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
1240
789
 
1241
790
  # Use standard-conforming strings if available so we don't have to do the E'...' dance.
@@ -1243,23 +792,43 @@ module ActiveRecord
1243
792
 
1244
793
  # If using Active Record's time zone support configure the connection to return
1245
794
  # TIMESTAMP WITH ZONE types in UTC.
795
+ # (SET TIME ZONE does not use an equals sign like other SET variables)
1246
796
  if ActiveRecord::Base.default_timezone == :utc
1247
797
  execute("SET time zone 'UTC'", 'SCHEMA')
1248
798
  elsif @local_tz
1249
799
  execute("SET time zone '#{@local_tz}'", 'SCHEMA')
1250
800
  end
801
+
802
+ # SET statements from :variables config hash
803
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
804
+ variables = @config[:variables] || {}
805
+ variables.map do |k, v|
806
+ if v == ':default' || v == :default
807
+ # Sets the value to the global or compile default
808
+ execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
809
+ elsif !v.nil?
810
+ execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
811
+ end
812
+ end
1251
813
  end
1252
814
 
1253
815
  # Returns the current ID of a table's sequence.
1254
816
  def last_insert_id(sequence_name) #:nodoc:
1255
- r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1256
- Integer(r.rows.first.first)
817
+ Integer(last_insert_id_value(sequence_name))
818
+ end
819
+
820
+ def last_insert_id_value(sequence_name)
821
+ last_insert_id_result(sequence_name).rows.first.first
822
+ end
823
+
824
+ def last_insert_id_result(sequence_name) #:nodoc:
825
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1257
826
  end
1258
827
 
1259
828
  # Executes a SELECT query and returns the results, performing any data type
1260
829
  # conversions that are required to be performed here instead of in PostgreSQLColumn.
1261
830
  def select(sql, name = nil, binds = [])
1262
- exec_query(sql, name, binds).to_a
831
+ exec_query(sql, name, binds)
1263
832
  end
1264
833
 
1265
834
  def select_raw(sql, name = nil)
@@ -1290,13 +859,13 @@ module ActiveRecord
1290
859
  # - ::regclass is a function that gives the id for a table name
1291
860
  def column_definitions(table_name) #:nodoc:
1292
861
  exec_query(<<-end_sql, 'SCHEMA').rows
1293
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
862
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1294
863
  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
864
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
865
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
866
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
867
+ AND a.attnum > 0 AND NOT a.attisdropped
868
+ ORDER BY a.attnum
1300
869
  end_sql
1301
870
  end
1302
871