activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,596 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+
7
+ def visit_ColumnDefinition(o)
8
+ sql = super
9
+ if o.primary_key? && o.type != :primary_key
10
+ sql << " PRIMARY KEY "
11
+ add_column_options!(sql, column_options(o))
12
+ end
13
+ sql
14
+ end
15
+
16
+ def add_column_options!(sql, options)
17
+ if options[:array] || options[:column].try(:array)
18
+ sql << '[]'
19
+ end
20
+
21
+ column = options.fetch(:column) { return super }
22
+ if column.type == :uuid && options[:default] =~ /\(\)/
23
+ sql << " DEFAULT #{options[:default]}"
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def type_for_column(column)
30
+ if column.array
31
+ @conn.lookup_cast_type("#{column.sql_type}[]")
32
+ else
33
+ super
34
+ end
35
+ end
36
+ end
37
+
38
+ module SchemaStatements
39
+ # Drops the database specified on the +name+ attribute
40
+ # and creates it again using the provided +options+.
41
+ def recreate_database(name, options = {}) #:nodoc:
42
+ drop_database(name)
43
+ create_database(name, options)
44
+ end
45
+
46
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
47
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
48
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
49
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
50
+ #
51
+ # Example:
52
+ # create_database config[:database], config
53
+ # create_database 'foo_development', encoding: 'unicode'
54
+ def create_database(name, options = {})
55
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
56
+
57
+ option_string = options.inject("") do |memo, (key, value)|
58
+ memo += case key
59
+ when :owner
60
+ " OWNER = \"#{value}\""
61
+ when :template
62
+ " TEMPLATE = \"#{value}\""
63
+ when :encoding
64
+ " ENCODING = '#{value}'"
65
+ when :collation
66
+ " LC_COLLATE = '#{value}'"
67
+ when :ctype
68
+ " LC_CTYPE = '#{value}'"
69
+ when :tablespace
70
+ " TABLESPACE = \"#{value}\""
71
+ when :connection_limit
72
+ " CONNECTION LIMIT = #{value}"
73
+ else
74
+ ""
75
+ end
76
+ end
77
+
78
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
79
+ end
80
+
81
+ # Drops a PostgreSQL database.
82
+ #
83
+ # Example:
84
+ # drop_database 'matt_development'
85
+ def drop_database(name) #:nodoc:
86
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
87
+ end
88
+
89
+ # Returns the list of all tables in the schema search path.
90
+ def tables(name = nil)
91
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
92
+ SELECT tablename
93
+ FROM pg_tables
94
+ WHERE schemaname = ANY (current_schemas(false))
95
+ SQL
96
+ end
97
+
98
+ def data_sources # :nodoc
99
+ select_values(<<-SQL, 'SCHEMA')
100
+ SELECT c.relname
101
+ FROM pg_class c
102
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
103
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
104
+ AND n.nspname = ANY (current_schemas(false))
105
+ SQL
106
+ end
107
+
108
+ # Returns true if table exists.
109
+ # If the schema is not specified as part of +name+ then it will only find tables within
110
+ # the current schema search path (regardless of permissions to access tables in other schemas)
111
+ def table_exists?(name)
112
+ name = Utils.extract_schema_qualified_name(name.to_s)
113
+ return false unless name.identifier
114
+
115
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
116
+ SELECT COUNT(*)
117
+ FROM pg_class c
118
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
119
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
120
+ AND c.relname = '#{name.identifier}'
121
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
122
+ SQL
123
+ end
124
+ alias data_source_exists? table_exists?
125
+
126
+ def drop_table(table_name, options = {})
127
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
128
+ end
129
+
130
+ # Returns true if schema exists.
131
+ def schema_exists?(name)
132
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
133
+ SELECT COUNT(*)
134
+ FROM pg_namespace
135
+ WHERE nspname = '#{name}'
136
+ SQL
137
+ end
138
+
139
+ def index_name_exists?(table_name, index_name, default)
140
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
141
+ SELECT COUNT(*)
142
+ FROM pg_class t
143
+ INNER JOIN pg_index d ON t.oid = d.indrelid
144
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
145
+ WHERE i.relkind = 'i'
146
+ AND i.relname = '#{index_name}'
147
+ AND t.relname = '#{table_name}'
148
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
149
+ SQL
150
+ end
151
+
152
+ # Returns an array of indexes for the given table.
153
+ def indexes(table_name, name = nil)
154
+ result = query(<<-SQL, 'SCHEMA')
155
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
156
+ FROM pg_class t
157
+ INNER JOIN pg_index d ON t.oid = d.indrelid
158
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
159
+ WHERE i.relkind = 'i'
160
+ AND d.indisprimary = 'f'
161
+ AND t.relname = '#{table_name}'
162
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
163
+ ORDER BY i.relname
164
+ SQL
165
+
166
+ result.map do |row|
167
+ index_name = row[0]
168
+ unique = row[1] == 't'
169
+ indkey = row[2].split(" ")
170
+ inddef = row[3]
171
+ oid = row[4]
172
+
173
+ columns = Hash[query(<<-SQL, "SCHEMA")]
174
+ SELECT a.attnum, a.attname
175
+ FROM pg_attribute a
176
+ WHERE a.attrelid = #{oid}
177
+ AND a.attnum IN (#{indkey.join(",")})
178
+ SQL
179
+
180
+ column_names = columns.values_at(*indkey).compact
181
+
182
+ unless column_names.empty?
183
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
184
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
185
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
186
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
187
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
188
+
189
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
190
+ end
191
+ end.compact
192
+ end
193
+
194
+ # Returns the list of all column definitions for a table.
195
+ def columns(table_name)
196
+ # Limit, precision, and scale are all handled by the superclass.
197
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
198
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
199
+ default_value = extract_value_from_default(oid, default)
200
+ default_function = extract_default_function(default_value, default)
201
+ new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
202
+ end
203
+ end
204
+
205
+ def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
206
+ PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
207
+ end
208
+
209
+ # Returns the current database name.
210
+ def current_database
211
+ query('select current_database()', 'SCHEMA')[0][0]
212
+ end
213
+
214
+ # Returns the current schema name.
215
+ def current_schema
216
+ query('SELECT current_schema', 'SCHEMA')[0][0]
217
+ end
218
+
219
+ # Returns the current database encoding format.
220
+ def encoding
221
+ query(<<-end_sql, 'SCHEMA')[0][0]
222
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
223
+ WHERE pg_database.datname LIKE '#{current_database}'
224
+ end_sql
225
+ end
226
+
227
+ # Returns the current database collation.
228
+ def collation
229
+ query(<<-end_sql, 'SCHEMA')[0][0]
230
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
231
+ end_sql
232
+ end
233
+
234
+ # Returns the current database ctype.
235
+ def ctype
236
+ query(<<-end_sql, 'SCHEMA')[0][0]
237
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
238
+ end_sql
239
+ end
240
+
241
+ # Returns an array of schema names.
242
+ def schema_names
243
+ query(<<-SQL, 'SCHEMA').flatten
244
+ SELECT nspname
245
+ FROM pg_namespace
246
+ WHERE nspname !~ '^pg_.*'
247
+ AND nspname NOT IN ('information_schema')
248
+ ORDER by nspname;
249
+ SQL
250
+ end
251
+
252
+ # Creates a schema for the given schema name.
253
+ def create_schema schema_name
254
+ execute "CREATE SCHEMA #{schema_name}"
255
+ end
256
+
257
+ # Drops the schema for the given schema name.
258
+ def drop_schema schema_name
259
+ execute "DROP SCHEMA #{schema_name} CASCADE"
260
+ end
261
+
262
+ # Sets the schema search path to a string of comma-separated schema names.
263
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
264
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
265
+ #
266
+ # This should be not be called manually but set in database.yml.
267
+ def schema_search_path=(schema_csv)
268
+ if schema_csv
269
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
270
+ @schema_search_path = schema_csv
271
+ end
272
+ end
273
+
274
+ # Returns the active schema search path.
275
+ def schema_search_path
276
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
277
+ end
278
+
279
+ # Returns the current client message level.
280
+ def client_min_messages
281
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
282
+ end
283
+
284
+ # Set the client message level.
285
+ def client_min_messages=(level)
286
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
287
+ end
288
+
289
+ # Returns the sequence name for a table's primary key or some other specified key.
290
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
291
+ result = serial_sequence(table_name, pk || 'id')
292
+ return nil unless result
293
+ Utils.extract_schema_qualified_name(result).to_s
294
+ rescue ActiveRecord::StatementInvalid
295
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
296
+ end
297
+
298
+ def serial_sequence(table, column)
299
+ result = exec_query(<<-eosql, 'SCHEMA')
300
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
301
+ eosql
302
+ result.rows.first.first
303
+ end
304
+
305
+ # Sets the sequence of a table's primary key to the specified value.
306
+ def set_pk_sequence!(table, value) #:nodoc:
307
+ pk, sequence = pk_and_sequence_for(table)
308
+
309
+ if pk
310
+ if sequence
311
+ quoted_sequence = quote_table_name(sequence)
312
+
313
+ select_value <<-end_sql, 'SCHEMA'
314
+ SELECT setval('#{quoted_sequence}', #{value})
315
+ end_sql
316
+ else
317
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
318
+ end
319
+ end
320
+ end
321
+
322
+ # Resets the sequence of a table's primary key to the maximum value.
323
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
324
+ unless pk and sequence
325
+ default_pk, default_sequence = pk_and_sequence_for(table)
326
+
327
+ pk ||= default_pk
328
+ sequence ||= default_sequence
329
+ end
330
+
331
+ if @logger && pk && !sequence
332
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
333
+ end
334
+
335
+ if pk && sequence
336
+ quoted_sequence = quote_table_name(sequence)
337
+
338
+ select_value <<-end_sql, 'SCHEMA'
339
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
340
+ end_sql
341
+ end
342
+ end
343
+
344
+ # Returns a table's primary key and belonging sequence.
345
+ def pk_and_sequence_for(table) #:nodoc:
346
+ # First try looking for a sequence with a dependency on the
347
+ # given table's primary key.
348
+ result = query(<<-end_sql, 'SCHEMA')[0]
349
+ SELECT attr.attname, nsp.nspname, seq.relname
350
+ FROM pg_class seq,
351
+ pg_attribute attr,
352
+ pg_depend dep,
353
+ pg_constraint cons,
354
+ pg_namespace nsp
355
+ WHERE seq.oid = dep.objid
356
+ AND seq.relkind = 'S'
357
+ AND attr.attrelid = dep.refobjid
358
+ AND attr.attnum = dep.refobjsubid
359
+ AND attr.attrelid = cons.conrelid
360
+ AND attr.attnum = cons.conkey[1]
361
+ AND seq.relnamespace = nsp.oid
362
+ AND cons.contype = 'p'
363
+ AND dep.classid = 'pg_class'::regclass
364
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
365
+ end_sql
366
+
367
+ if result.nil? or result.empty?
368
+ result = query(<<-end_sql, 'SCHEMA')[0]
369
+ SELECT attr.attname, nsp.nspname,
370
+ CASE
371
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
372
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
373
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
374
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
375
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
376
+ END
377
+ FROM pg_class t
378
+ JOIN pg_attribute attr ON (t.oid = attrelid)
379
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
380
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
381
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
382
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
383
+ AND cons.contype = 'p'
384
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
385
+ end_sql
386
+ end
387
+
388
+ pk = result.shift
389
+ if result.last
390
+ [pk, PostgreSQL::Name.new(*result)]
391
+ else
392
+ [pk, nil]
393
+ end
394
+ rescue
395
+ nil
396
+ end
397
+
398
+ # Returns just a table's primary key
399
+ def primary_key(table)
400
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
401
+ SELECT attr.attname
402
+ FROM pg_attribute attr
403
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
404
+ WHERE cons.contype = 'p'
405
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
406
+ end_sql
407
+ return nil unless pks.count == 1
408
+ pks[0][0]
409
+ end
410
+
411
+ # Renames a table.
412
+ # Also renames a table's primary key sequence if the sequence name exists and
413
+ # matches the Active Record default.
414
+ #
415
+ # Example:
416
+ # rename_table('octopuses', 'octopi')
417
+ def rename_table(table_name, new_name)
418
+ clear_cache!
419
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
420
+ pk, seq = pk_and_sequence_for(new_name)
421
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
422
+ new_seq = "#{new_name}_#{pk}_seq"
423
+ idx = "#{table_name}_pkey"
424
+ new_idx = "#{new_name}_pkey"
425
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
426
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
427
+ end
428
+
429
+ rename_table_indexes(table_name, new_name)
430
+ end
431
+
432
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
433
+ clear_cache!
434
+ super
435
+ end
436
+
437
+ # Changes the column of a table.
438
+ def change_column(table_name, column_name, type, options = {})
439
+ clear_cache!
440
+ quoted_table_name = quote_table_name(table_name)
441
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
442
+ sql_type << "[]" if options[:array]
443
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
444
+ sql << " USING #{options[:using]}" if options[:using]
445
+ if options[:cast_as]
446
+ sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
447
+ end
448
+ execute sql
449
+
450
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
451
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
452
+ end
453
+
454
+ # Changes the default value of a table column.
455
+ def change_column_default(table_name, column_name, default)
456
+ clear_cache!
457
+ column = column_for(table_name, column_name)
458
+ return unless column
459
+
460
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
461
+ if default.nil?
462
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
463
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
464
+ execute alter_column_query % "DROP DEFAULT"
465
+ else
466
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
467
+ end
468
+ end
469
+
470
+ def change_column_null(table_name, column_name, null, default = nil)
471
+ clear_cache!
472
+ unless null || default.nil?
473
+ column = column_for(table_name, column_name)
474
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
475
+ end
476
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
477
+ end
478
+
479
+ # Renames a column in a table.
480
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
481
+ clear_cache!
482
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
483
+ rename_column_indexes(table_name, column_name, new_column_name)
484
+ end
485
+
486
+ def add_index(table_name, column_name, options = {}) #:nodoc:
487
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
488
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
489
+ end
490
+
491
+ def remove_index!(table_name, index_name) #:nodoc:
492
+ execute "DROP INDEX #{quote_table_name(index_name)}"
493
+ end
494
+
495
+ def rename_index(table_name, old_name, new_name)
496
+ validate_index_length!(table_name, new_name)
497
+
498
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
499
+ end
500
+
501
+ def foreign_keys(table_name)
502
+ fk_info = select_all <<-SQL.strip_heredoc
503
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
504
+ FROM pg_constraint c
505
+ JOIN pg_class t1 ON c.conrelid = t1.oid
506
+ JOIN pg_class t2 ON c.confrelid = t2.oid
507
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
508
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
509
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
510
+ WHERE c.contype = 'f'
511
+ AND t1.relname = #{quote(table_name)}
512
+ AND t3.nspname = ANY (current_schemas(false))
513
+ ORDER BY c.conname
514
+ SQL
515
+
516
+ fk_info.map do |row|
517
+ options = {
518
+ column: row['column'],
519
+ name: row['name'],
520
+ primary_key: row['primary_key']
521
+ }
522
+
523
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
524
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
525
+
526
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
527
+ end
528
+ end
529
+
530
+ def extract_foreign_key_action(specifier) # :nodoc:
531
+ case specifier
532
+ when 'c'; :cascade
533
+ when 'n'; :nullify
534
+ when 'r'; :restrict
535
+ end
536
+ end
537
+
538
+ def index_name_length
539
+ 63
540
+ end
541
+
542
+ # Maps logical Rails types to PostgreSQL-specific data types.
543
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
544
+ case type.to_s
545
+ when 'binary'
546
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
547
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
548
+ case limit
549
+ when nil, 0..0x3fffffff; super(type)
550
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
551
+ end
552
+ when 'text'
553
+ # PostgreSQL doesn't support limits on text columns.
554
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
555
+ case limit
556
+ when nil, 0..0x3fffffff; super(type)
557
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
558
+ end
559
+ when 'integer'
560
+ return 'integer' unless limit
561
+
562
+ case limit
563
+ when 1, 2; 'smallint'
564
+ when 3, 4; 'integer'
565
+ when 5..8; 'bigint'
566
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
567
+ end
568
+ when 'datetime'
569
+ return super unless precision
570
+
571
+ case precision
572
+ when 0..6; "timestamp(#{precision})"
573
+ else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
574
+ end
575
+ else
576
+ super
577
+ end
578
+ end
579
+
580
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
581
+ # requires that the ORDER BY include the distinct column.
582
+ def columns_for_distinct(columns, orders) #:nodoc:
583
+ order_columns = orders.reject(&:blank?).map{ |s|
584
+ # Convert Arel node to string
585
+ s = s.to_sql unless s.is_a?(String)
586
+ # Remove any ASC/DESC modifiers
587
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
588
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
589
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
590
+
591
+ [super, *order_columns].join(', ')
592
+ end
593
+ end
594
+ end
595
+ end
596
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ # Value Object to hold a schema qualified name.
5
+ # This is usually the name of a PostgreSQL relation but it can also represent
6
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
7
+ # double quoting.
8
+ class Name # :nodoc:
9
+ SEPARATOR = "."
10
+ attr_reader :schema, :identifier
11
+
12
+ def initialize(schema, identifier)
13
+ @schema, @identifier = unquote(schema), unquote(identifier)
14
+ end
15
+
16
+ def to_s
17
+ parts.join SEPARATOR
18
+ end
19
+
20
+ def quoted
21
+ if schema
22
+ PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
23
+ else
24
+ PGconn.quote_ident(identifier)
25
+ end
26
+ end
27
+
28
+ def ==(o)
29
+ o.class == self.class && o.parts == parts
30
+ end
31
+ alias_method :eql?, :==
32
+
33
+ def hash
34
+ parts.hash
35
+ end
36
+
37
+ protected
38
+ def unquote(part)
39
+ if part && part.start_with?('"')
40
+ part[1..-2]
41
+ else
42
+ part
43
+ end
44
+ end
45
+
46
+ def parts
47
+ @parts ||= [@schema, @identifier].compact
48
+ end
49
+ end
50
+
51
+ module Utils # :nodoc:
52
+ extend self
53
+
54
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
55
+ # extracted from +string+.
56
+ # +schema+ is nil if not specified in +string+.
57
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
58
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
59
+ #
60
+ # * <tt>table_name</tt>
61
+ # * <tt>"table.name"</tt>
62
+ # * <tt>schema_name.table_name</tt>
63
+ # * <tt>schema_name."table.name"</tt>
64
+ # * <tt>"schema_name".table_name</tt>
65
+ # * <tt>"schema.name"."table name"</tt>
66
+ def extract_schema_qualified_name(string)
67
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
68
+ if table.nil?
69
+ table = schema
70
+ schema = nil
71
+ end
72
+ PostgreSQL::Name.new(schema, table)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end