activerecord 1.0.0 → 4.0.0

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

Potentially problematic release.


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

Files changed (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,489 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+
7
+ def visit_AddColumn(o)
8
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
9
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
10
+ add_column_options!(sql, column_options(o))
11
+ end
12
+
13
+ def visit_ColumnDefinition(o)
14
+ sql = super
15
+ if o.primary_key? && o.type == :uuid
16
+ sql << " PRIMARY KEY "
17
+ add_column_options!(sql, column_options(o))
18
+ end
19
+ sql
20
+ end
21
+
22
+ def add_column_options!(sql, options)
23
+ if options[:array] || options[:column].try(:array)
24
+ sql << '[]'
25
+ end
26
+
27
+ column = options.fetch(:column) { return super }
28
+ if column.type == :uuid && options[:default] =~ /\(\)/
29
+ sql << " DEFAULT #{options[:default]}"
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+
36
+ def schema_creation
37
+ SchemaCreation.new self
38
+ end
39
+
40
+ module SchemaStatements
41
+ # Drops the database specified on the +name+ attribute
42
+ # and creates it again using the provided +options+.
43
+ def recreate_database(name, options = {}) #:nodoc:
44
+ drop_database(name)
45
+ create_database(name, options)
46
+ end
47
+
48
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
49
+ # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
50
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
51
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
52
+ #
53
+ # Example:
54
+ # create_database config[:database], config
55
+ # create_database 'foo_development', encoding: 'unicode'
56
+ def create_database(name, options = {})
57
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
58
+
59
+ option_string = options.sum do |key, value|
60
+ case key
61
+ when :owner
62
+ " OWNER = \"#{value}\""
63
+ when :template
64
+ " TEMPLATE = \"#{value}\""
65
+ when :encoding
66
+ " ENCODING = '#{value}'"
67
+ when :collation
68
+ " LC_COLLATE = '#{value}'"
69
+ when :ctype
70
+ " LC_CTYPE = '#{value}'"
71
+ when :tablespace
72
+ " TABLESPACE = \"#{value}\""
73
+ when :connection_limit
74
+ " CONNECTION LIMIT = #{value}"
75
+ else
76
+ ""
77
+ end
78
+ end
79
+
80
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
81
+ end
82
+
83
+ # Drops a PostgreSQL database.
84
+ #
85
+ # Example:
86
+ # drop_database 'matt_development'
87
+ def drop_database(name) #:nodoc:
88
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
89
+ end
90
+
91
+ # Returns the list of all tables in the schema search path or a specified schema.
92
+ def tables(name = nil)
93
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
94
+ SELECT tablename
95
+ FROM pg_tables
96
+ WHERE schemaname = ANY (current_schemas(false))
97
+ SQL
98
+ end
99
+
100
+ # Returns true if table exists.
101
+ # If the schema is not specified as part of +name+ then it will only find tables within
102
+ # the current schema search path (regardless of permissions to access tables in other schemas)
103
+ def table_exists?(name)
104
+ schema, table = Utils.extract_schema_and_table(name.to_s)
105
+ return false unless table
106
+
107
+ binds = [[nil, table]]
108
+ binds << [nil, schema] if schema
109
+
110
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
+ SELECT COUNT(*)
112
+ FROM pg_class c
113
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
114
+ WHERE c.relkind in ('v','r')
115
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
116
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
117
+ SQL
118
+ end
119
+
120
+ # Returns true if schema exists.
121
+ def schema_exists?(name)
122
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
123
+ SELECT COUNT(*)
124
+ FROM pg_namespace
125
+ WHERE nspname = '#{name}'
126
+ SQL
127
+ end
128
+
129
+ # Returns an array of indexes for the given table.
130
+ def indexes(table_name, name = nil)
131
+ result = query(<<-SQL, 'SCHEMA')
132
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
133
+ FROM pg_class t
134
+ INNER JOIN pg_index d ON t.oid = d.indrelid
135
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
136
+ WHERE i.relkind = 'i'
137
+ AND d.indisprimary = 'f'
138
+ AND t.relname = '#{table_name}'
139
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
140
+ ORDER BY i.relname
141
+ SQL
142
+
143
+ result.map do |row|
144
+ index_name = row[0]
145
+ unique = row[1] == 't'
146
+ indkey = row[2].split(" ")
147
+ inddef = row[3]
148
+ oid = row[4]
149
+
150
+ columns = Hash[query(<<-SQL, "SCHEMA")]
151
+ SELECT a.attnum, a.attname
152
+ FROM pg_attribute a
153
+ WHERE a.attrelid = #{oid}
154
+ AND a.attnum IN (#{indkey.join(",")})
155
+ SQL
156
+
157
+ column_names = columns.values_at(*indkey).compact
158
+
159
+ unless column_names.empty?
160
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
161
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
162
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
163
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
164
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
165
+
166
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
167
+ end
168
+ end.compact
169
+ end
170
+
171
+ # Returns the list of all column definitions for a table.
172
+ def columns(table_name)
173
+ # Limit, precision, and scale are all handled by the superclass.
174
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
175
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
176
+ OID::Identity.new
177
+ }
178
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
179
+ end
180
+ end
181
+
182
+ # Returns the current database name.
183
+ def current_database
184
+ query('select current_database()', 'SCHEMA')[0][0]
185
+ end
186
+
187
+ # Returns the current schema name.
188
+ def current_schema
189
+ query('SELECT current_schema', 'SCHEMA')[0][0]
190
+ end
191
+
192
+ # Returns the current database encoding format.
193
+ def encoding
194
+ query(<<-end_sql, 'SCHEMA')[0][0]
195
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
196
+ WHERE pg_database.datname LIKE '#{current_database}'
197
+ end_sql
198
+ end
199
+
200
+ # Returns the current database collation.
201
+ def collation
202
+ query(<<-end_sql, 'SCHEMA')[0][0]
203
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
204
+ end_sql
205
+ end
206
+
207
+ # Returns the current database ctype.
208
+ def ctype
209
+ query(<<-end_sql, 'SCHEMA')[0][0]
210
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
211
+ end_sql
212
+ end
213
+
214
+ # Returns an array of schema names.
215
+ def schema_names
216
+ query(<<-SQL, 'SCHEMA').flatten
217
+ SELECT nspname
218
+ FROM pg_namespace
219
+ WHERE nspname !~ '^pg_.*'
220
+ AND nspname NOT IN ('information_schema')
221
+ ORDER by nspname;
222
+ SQL
223
+ end
224
+
225
+ # Creates a schema for the given schema name.
226
+ def create_schema schema_name
227
+ execute "CREATE SCHEMA #{schema_name}"
228
+ end
229
+
230
+ # Drops the schema for the given schema name.
231
+ def drop_schema schema_name
232
+ execute "DROP SCHEMA #{schema_name} CASCADE"
233
+ end
234
+
235
+ # Sets the schema search path to a string of comma-separated schema names.
236
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
237
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
238
+ #
239
+ # This should be not be called manually but set in database.yml.
240
+ def schema_search_path=(schema_csv)
241
+ if schema_csv
242
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
243
+ @schema_search_path = schema_csv
244
+ end
245
+ end
246
+
247
+ # Returns the active schema search path.
248
+ def schema_search_path
249
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
250
+ end
251
+
252
+ # Returns the current client message level.
253
+ def client_min_messages
254
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
255
+ end
256
+
257
+ # Set the client message level.
258
+ def client_min_messages=(level)
259
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
260
+ end
261
+
262
+ # Returns the sequence name for a table's primary key or some other specified key.
263
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
264
+ result = serial_sequence(table_name, pk || 'id')
265
+ return nil unless result
266
+ result.split('.').last
267
+ rescue ActiveRecord::StatementInvalid
268
+ "#{table_name}_#{pk || 'id'}_seq"
269
+ end
270
+
271
+ def serial_sequence(table, column)
272
+ result = exec_query(<<-eosql, 'SCHEMA')
273
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
274
+ eosql
275
+ result.rows.first.first
276
+ end
277
+
278
+ # Resets the sequence of a table's primary key to the maximum value.
279
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
280
+ unless pk and sequence
281
+ default_pk, default_sequence = pk_and_sequence_for(table)
282
+
283
+ pk ||= default_pk
284
+ sequence ||= default_sequence
285
+ end
286
+
287
+ if @logger && pk && !sequence
288
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
289
+ end
290
+
291
+ if pk && sequence
292
+ quoted_sequence = quote_table_name(sequence)
293
+
294
+ select_value <<-end_sql, 'SCHEMA'
295
+ 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)
296
+ end_sql
297
+ end
298
+ end
299
+
300
+ # Returns a table's primary key and belonging sequence.
301
+ def pk_and_sequence_for(table) #:nodoc:
302
+ # First try looking for a sequence with a dependency on the
303
+ # given table's primary key.
304
+ result = query(<<-end_sql, 'SCHEMA')[0]
305
+ SELECT attr.attname, seq.relname
306
+ FROM pg_class seq,
307
+ pg_attribute attr,
308
+ pg_depend dep,
309
+ pg_constraint cons
310
+ WHERE seq.oid = dep.objid
311
+ AND seq.relkind = 'S'
312
+ AND attr.attrelid = dep.refobjid
313
+ AND attr.attnum = dep.refobjsubid
314
+ AND attr.attrelid = cons.conrelid
315
+ AND attr.attnum = cons.conkey[1]
316
+ AND cons.contype = 'p'
317
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
318
+ end_sql
319
+
320
+ if result.nil? or result.empty?
321
+ result = query(<<-end_sql, 'SCHEMA')[0]
322
+ SELECT attr.attname,
323
+ CASE
324
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
325
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
326
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
327
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
328
+ END
329
+ FROM pg_class t
330
+ JOIN pg_attribute attr ON (t.oid = attrelid)
331
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
332
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
333
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
334
+ AND cons.contype = 'p'
335
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
336
+ end_sql
337
+ end
338
+
339
+ [result.first, result.last]
340
+ rescue
341
+ nil
342
+ end
343
+
344
+ # Returns just a table's primary key
345
+ def primary_key(table)
346
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
347
+ SELECT attr.attname
348
+ FROM pg_attribute attr
349
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
350
+ WHERE cons.contype = 'p'
351
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
352
+ end_sql
353
+
354
+ row && row.first
355
+ end
356
+
357
+ # Renames a table.
358
+ # Also renames a table's primary key sequence if the sequence name matches the
359
+ # Active Record default.
360
+ #
361
+ # Example:
362
+ # rename_table('octopuses', 'octopi')
363
+ def rename_table(table_name, new_name)
364
+ clear_cache!
365
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
366
+ pk, seq = pk_and_sequence_for(new_name)
367
+ if seq == "#{table_name}_#{pk}_seq"
368
+ new_seq = "#{new_name}_#{pk}_seq"
369
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
370
+ end
371
+
372
+ rename_table_indexes(table_name, new_name)
373
+ end
374
+
375
+ # Adds a new column to the named table.
376
+ # See TableDefinition#column for details of the options you can use.
377
+ def add_column(table_name, column_name, type, options = {})
378
+ clear_cache!
379
+ super
380
+ end
381
+
382
+ # Changes the column of a table.
383
+ def change_column(table_name, column_name, type, options = {})
384
+ clear_cache!
385
+ quoted_table_name = quote_table_name(table_name)
386
+
387
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
388
+
389
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
390
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
391
+ end
392
+
393
+ # Changes the default value of a table column.
394
+ def change_column_default(table_name, column_name, default)
395
+ clear_cache!
396
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
397
+ end
398
+
399
+ def change_column_null(table_name, column_name, null, default = nil)
400
+ clear_cache!
401
+ unless null || default.nil?
402
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
403
+ end
404
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
405
+ end
406
+
407
+ # Renames a column in a table.
408
+ def rename_column(table_name, column_name, new_column_name)
409
+ clear_cache!
410
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
411
+ rename_column_indexes(table_name, column_name, new_column_name)
412
+ end
413
+
414
+ def add_index(table_name, column_name, options = {}) #:nodoc:
415
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
416
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
417
+ end
418
+
419
+ def remove_index!(table_name, index_name) #:nodoc:
420
+ execute "DROP INDEX #{quote_table_name(index_name)}"
421
+ end
422
+
423
+ def rename_index(table_name, old_name, new_name)
424
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
425
+ end
426
+
427
+ def index_name_length
428
+ 63
429
+ end
430
+
431
+ # Maps logical Rails types to PostgreSQL-specific data types.
432
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
433
+ case type.to_s
434
+ when 'binary'
435
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
436
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
437
+ case limit
438
+ when nil, 0..0x3fffffff; super(type)
439
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
440
+ end
441
+ when 'text'
442
+ # PostgreSQL doesn't support limits on text columns.
443
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
444
+ case limit
445
+ when nil, 0..0x3fffffff; super(type)
446
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
447
+ end
448
+ when 'integer'
449
+ return 'integer' unless limit
450
+
451
+ case limit
452
+ when 1, 2; 'smallint'
453
+ when 3, 4; 'integer'
454
+ when 5..8; 'bigint'
455
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
456
+ end
457
+ when 'datetime'
458
+ return super unless precision
459
+
460
+ case precision
461
+ when 0..6; "timestamp(#{precision})"
462
+ else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
463
+ end
464
+ else
465
+ super
466
+ end
467
+ end
468
+
469
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
470
+ #
471
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
472
+ # requires that the ORDER BY include the distinct column.
473
+ #
474
+ # distinct("posts.id", ["posts.created_at desc"])
475
+ # # => "DISTINCT posts.id, posts.created_at AS alias_0"
476
+ def distinct(columns, orders) #:nodoc:
477
+ order_columns = orders.map{ |s|
478
+ # Convert Arel node to string
479
+ s = s.to_sql unless s.is_a?(String)
480
+ # Remove any ASC/DESC modifiers
481
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
482
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
483
+
484
+ [super].concat(order_columns).join(', ')
485
+ end
486
+ end
487
+ end
488
+ end
489
+ end