activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,584 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # Establishes a connection to the database that's used by all Active Record objects
6
+ def self.postgresql_connection(config) # :nodoc:
7
+ require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)
8
+
9
+ config = config.symbolize_keys
10
+ host = config[:host]
11
+ port = config[:port] || 5432
12
+ username = config[:username].to_s
13
+ password = config[:password].to_s
14
+
15
+ min_messages = config[:min_messages]
16
+
17
+ if config.has_key?(:database)
18
+ database = config[:database]
19
+ else
20
+ raise ArgumentError, "No database specified. Missing argument: database."
21
+ end
22
+
23
+ pga = ConnectionAdapters::PostgreSQLAdapter.new(
24
+ PGconn.connect(host, port, "", "", database, username, password), logger, config
25
+ )
26
+
27
+ PGconn.translate_results = false if PGconn.respond_to? :translate_results=
28
+
29
+ pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
30
+
31
+ pga
32
+ end
33
+ end
34
+
35
+ module ConnectionAdapters
36
+ # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base
37
+ # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers.
38
+ #
39
+ # Options:
40
+ #
41
+ # * <tt>:host</tt> -- Defaults to localhost
42
+ # * <tt>:port</tt> -- Defaults to 5432
43
+ # * <tt>:username</tt> -- Defaults to nothing
44
+ # * <tt>:password</tt> -- Defaults to nothing
45
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
46
+ # * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
47
+ # * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
48
+ # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
49
+ # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
50
+ class PostgreSQLAdapter < AbstractAdapter
51
+ def adapter_name
52
+ 'PostgreSQL'
53
+ end
54
+
55
+ def initialize(connection, logger, config = {})
56
+ super(connection, logger)
57
+ @config = config
58
+ @async = config[:allow_concurrency]
59
+ configure_connection
60
+ end
61
+
62
+ # Is this connection alive and ready for queries?
63
+ def active?
64
+ if @connection.respond_to?(:status)
65
+ @connection.status == PGconn::CONNECTION_OK
66
+ else
67
+ @connection.query 'SELECT 1'
68
+ true
69
+ end
70
+ # postgres-pr raises a NoMethodError when querying if no conn is available
71
+ rescue PGError, NoMethodError
72
+ false
73
+ end
74
+
75
+ # Close then reopen the connection.
76
+ def reconnect!
77
+ # TODO: postgres-pr doesn't have PGconn#reset.
78
+ if @connection.respond_to?(:reset)
79
+ @connection.reset
80
+ configure_connection
81
+ end
82
+ end
83
+
84
+ def disconnect!
85
+ # Both postgres and postgres-pr respond to :close
86
+ @connection.close rescue nil
87
+ end
88
+
89
+ def native_database_types
90
+ {
91
+ :primary_key => "serial primary key",
92
+ :string => { :name => "character varying", :limit => 255 },
93
+ :text => { :name => "text" },
94
+ :integer => { :name => "integer" },
95
+ :float => { :name => "float" },
96
+ :decimal => { :name => "decimal" },
97
+ :datetime => { :name => "timestamp" },
98
+ :timestamp => { :name => "timestamp" },
99
+ :time => { :name => "time" },
100
+ :date => { :name => "date" },
101
+ :binary => { :name => "bytea" },
102
+ :boolean => { :name => "boolean" }
103
+ }
104
+ end
105
+
106
+ def supports_migrations?
107
+ true
108
+ end
109
+
110
+ def table_alias_length
111
+ 63
112
+ end
113
+
114
+ # QUOTING ==================================================
115
+
116
+ def quote(value, column = nil)
117
+ if value.kind_of?(String) && column && column.type == :binary
118
+ "'#{escape_bytea(value)}'"
119
+ else
120
+ super
121
+ end
122
+ end
123
+
124
+ def quote_column_name(name)
125
+ %("#{name}")
126
+ end
127
+
128
+ def quoted_date(value)
129
+ value.strftime("%Y-%m-%d %H:%M:%S.#{sprintf("%06d", value.usec)}")
130
+ end
131
+
132
+
133
+ # DATABASE STATEMENTS ======================================
134
+
135
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
136
+ execute(sql, name)
137
+ table = sql.split(" ", 4)[2]
138
+ id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
139
+ end
140
+
141
+ def query(sql, name = nil) #:nodoc:
142
+ log(sql, name) do
143
+ if @async
144
+ @connection.async_query(sql)
145
+ else
146
+ @connection.query(sql)
147
+ end
148
+ end
149
+ end
150
+
151
+ def execute(sql, name = nil) #:nodoc:
152
+ log(sql, name) do
153
+ if @async
154
+ @connection.async_exec(sql)
155
+ else
156
+ @connection.exec(sql)
157
+ end
158
+ end
159
+ end
160
+
161
+ def update(sql, name = nil) #:nodoc:
162
+ execute(sql, name).cmdtuples
163
+ end
164
+
165
+ def begin_db_transaction #:nodoc:
166
+ execute "BEGIN"
167
+ end
168
+
169
+ def commit_db_transaction #:nodoc:
170
+ execute "COMMIT"
171
+ end
172
+
173
+ def rollback_db_transaction #:nodoc:
174
+ execute "ROLLBACK"
175
+ end
176
+
177
+ # SCHEMA STATEMENTS ========================================
178
+
179
+ # Return the list of all tables in the schema search path.
180
+ def tables(name = nil) #:nodoc:
181
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
182
+ query(<<-SQL, name).map { |row| row[0] }
183
+ SELECT tablename
184
+ FROM pg_tables
185
+ WHERE schemaname IN (#{schemas})
186
+ SQL
187
+ end
188
+
189
+ def indexes(table_name, name = nil) #:nodoc:
190
+ result = query(<<-SQL, name)
191
+ SELECT i.relname, d.indisunique, a.attname
192
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a
193
+ WHERE i.relkind = 'i'
194
+ AND d.indexrelid = i.oid
195
+ AND d.indisprimary = 'f'
196
+ AND t.oid = d.indrelid
197
+ AND t.relname = '#{table_name}'
198
+ AND a.attrelid = t.oid
199
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
200
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
201
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
202
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
203
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
204
+ ORDER BY i.relname
205
+ SQL
206
+
207
+ current_index = nil
208
+ indexes = []
209
+
210
+ result.each do |row|
211
+ if current_index != row[0]
212
+ indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
213
+ current_index = row[0]
214
+ end
215
+
216
+ indexes.last.columns << row[2]
217
+ end
218
+
219
+ indexes
220
+ end
221
+
222
+ def columns(table_name, name = nil) #:nodoc:
223
+ column_definitions(table_name).collect do |name, type, default, notnull, typmod|
224
+ # typmod now unused as limit, precision, scale all handled by superclass
225
+ Column.new(name, default_value(default), translate_field_type(type), notnull == "f")
226
+ end
227
+ end
228
+
229
+ # Set the schema search path to a string of comma-separated schema names.
230
+ # Names beginning with $ are quoted (e.g. $user => '$user')
231
+ # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
232
+ def schema_search_path=(schema_csv) #:nodoc:
233
+ if schema_csv
234
+ execute "SET search_path TO #{schema_csv}"
235
+ @schema_search_path = nil
236
+ end
237
+ end
238
+
239
+ def schema_search_path #:nodoc:
240
+ @schema_search_path ||= query('SHOW search_path')[0][0]
241
+ end
242
+
243
+ def default_sequence_name(table_name, pk = nil)
244
+ default_pk, default_seq = pk_and_sequence_for(table_name)
245
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
246
+ end
247
+
248
+ # Resets sequence to the max value of the table's pk if present.
249
+ def reset_pk_sequence!(table, pk = nil, sequence = nil)
250
+ unless pk and sequence
251
+ default_pk, default_sequence = pk_and_sequence_for(table)
252
+ pk ||= default_pk
253
+ sequence ||= default_sequence
254
+ end
255
+ if pk
256
+ if sequence
257
+ select_value <<-end_sql, 'Reset sequence'
258
+ SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
259
+ end_sql
260
+ else
261
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
262
+ end
263
+ end
264
+ end
265
+
266
+ # Find a table's primary key and sequence.
267
+ def pk_and_sequence_for(table)
268
+ # First try looking for a sequence with a dependency on the
269
+ # given table's primary key.
270
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
271
+ SELECT attr.attname, name.nspname, seq.relname
272
+ FROM pg_class seq,
273
+ pg_attribute attr,
274
+ pg_depend dep,
275
+ pg_namespace name,
276
+ pg_constraint cons
277
+ WHERE seq.oid = dep.objid
278
+ AND seq.relnamespace = name.oid
279
+ AND seq.relkind = 'S'
280
+ AND attr.attrelid = dep.refobjid
281
+ AND attr.attnum = dep.refobjsubid
282
+ AND attr.attrelid = cons.conrelid
283
+ AND attr.attnum = cons.conkey[1]
284
+ AND cons.contype = 'p'
285
+ AND dep.refobjid = '#{table}'::regclass
286
+ end_sql
287
+
288
+ if result.nil? or result.empty?
289
+ # If that fails, try parsing the primary key's default value.
290
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
291
+ # the 8.1+ nextval('foo'::regclass).
292
+ # TODO: assumes sequence is in same schema as table.
293
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
294
+ SELECT attr.attname, name.nspname, split_part(def.adsrc, '''', 2)
295
+ FROM pg_class t
296
+ JOIN pg_namespace name ON (t.relnamespace = name.oid)
297
+ JOIN pg_attribute attr ON (t.oid = attrelid)
298
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
299
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
300
+ WHERE t.oid = '#{table}'::regclass
301
+ AND cons.contype = 'p'
302
+ AND def.adsrc ~* 'nextval'
303
+ end_sql
304
+ end
305
+ # check for existence of . in sequence name as in public.foo_sequence. if it does not exist, return unqualified sequence
306
+ # We cannot qualify unqualified sequences, as rails doesn't qualify any table access, using the search path
307
+ [result.first, result.last]
308
+ rescue
309
+ nil
310
+ end
311
+
312
+ def rename_table(name, new_name)
313
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
314
+ end
315
+
316
+ def add_column(table_name, column_name, type, options = {})
317
+ default = options[:default]
318
+ notnull = options[:null] == false
319
+
320
+ # Add the column.
321
+ execute("ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit])}")
322
+
323
+ # Set optional default. If not null, update nulls to the new default.
324
+ if options_include_default?(options)
325
+ change_column_default(table_name, column_name, default)
326
+ if notnull
327
+ execute("UPDATE #{table_name} SET #{column_name}=#{quote(default, options[:column])} WHERE #{column_name} IS NULL")
328
+ end
329
+ end
330
+
331
+ if notnull
332
+ execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL")
333
+ end
334
+ end
335
+
336
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
337
+ begin
338
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
339
+ rescue ActiveRecord::StatementInvalid
340
+ # This is PG7, so we use a more arcane way of doing it.
341
+ begin_db_transaction
342
+ add_column(table_name, "#{column_name}_ar_tmp", type, options)
343
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
344
+ remove_column(table_name, column_name)
345
+ rename_column(table_name, "#{column_name}_ar_tmp", column_name)
346
+ commit_db_transaction
347
+ end
348
+
349
+ if options_include_default?(options)
350
+ change_column_default(table_name, column_name, options[:default])
351
+ end
352
+ end
353
+
354
+ def change_column_default(table_name, column_name, default) #:nodoc:
355
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
356
+ end
357
+
358
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
359
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
360
+ end
361
+
362
+ def remove_index(table_name, options) #:nodoc:
363
+ execute "DROP INDEX #{index_name(table_name, options)}"
364
+ end
365
+
366
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
367
+ return super unless type.to_s == 'integer'
368
+
369
+ if limit.nil? || limit == 4
370
+ 'integer'
371
+ elsif limit < 4
372
+ 'smallint'
373
+ else
374
+ 'bigint'
375
+ end
376
+ end
377
+
378
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
379
+ #
380
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
381
+ # requires that the ORDER BY include the distinct column.
382
+ #
383
+ # distinct("posts.id", "posts.created_at desc")
384
+ def distinct(columns, order_by)
385
+ return "DISTINCT #{columns}" if order_by.blank?
386
+
387
+ # construct a clean list of column names from the ORDER BY clause, removing
388
+ # any asc/desc modifiers
389
+ order_columns = order_by.split(',').collect { |s| s.split.first }
390
+ order_columns.delete_if &:blank?
391
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
392
+
393
+ # return a DISTINCT ON() clause that's distinct on the columns we want but includes
394
+ # all the required columns for the ORDER BY to work properly
395
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
396
+ sql << order_columns * ', '
397
+ end
398
+
399
+ # ORDER BY clause for the passed order option.
400
+ #
401
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
402
+ # by wrapping the sql as a sub-select and ordering in that query.
403
+ def add_order_by_for_association_limiting!(sql, options)
404
+ return sql if options[:order].blank?
405
+
406
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
407
+ order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
408
+ order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
409
+
410
+ sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
411
+ end
412
+
413
+ private
414
+ BYTEA_COLUMN_TYPE_OID = 17
415
+ NUMERIC_COLUMN_TYPE_OID = 1700
416
+ TIMESTAMPOID = 1114
417
+ TIMESTAMPTZOID = 1184
418
+
419
+ def configure_connection
420
+ if @config[:encoding]
421
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
422
+ end
423
+ if @config[:min_messages]
424
+ execute("SET client_min_messages TO '#{@config[:min_messages]}'")
425
+ end
426
+ end
427
+
428
+ def last_insert_id(table, sequence_name)
429
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
430
+ end
431
+
432
+ def select(sql, name = nil)
433
+ res = execute(sql, name)
434
+ results = res.result
435
+ rows = []
436
+ if results.length > 0
437
+ fields = res.fields
438
+ results.each do |row|
439
+ hashed_row = {}
440
+ row.each_index do |cel_index|
441
+ column = row[cel_index]
442
+
443
+ case res.type(cel_index)
444
+ when BYTEA_COLUMN_TYPE_OID
445
+ column = unescape_bytea(column)
446
+ when TIMESTAMPTZOID, TIMESTAMPOID
447
+ column = cast_to_time(column)
448
+ when NUMERIC_COLUMN_TYPE_OID
449
+ column = column.to_d if column.respond_to?(:to_d)
450
+ end
451
+
452
+ hashed_row[fields[cel_index]] = column
453
+ end
454
+ rows << hashed_row
455
+ end
456
+ end
457
+ res.clear
458
+ return rows
459
+ end
460
+
461
+ def escape_bytea(s)
462
+ if PGconn.respond_to? :escape_bytea
463
+ self.class.send(:define_method, :escape_bytea) do |s|
464
+ PGconn.escape_bytea(s) if s
465
+ end
466
+ else
467
+ self.class.send(:define_method, :escape_bytea) do |s|
468
+ if s
469
+ result = ''
470
+ s.each_byte { |c| result << sprintf('\\\\%03o', c) }
471
+ result
472
+ end
473
+ end
474
+ end
475
+ escape_bytea(s)
476
+ end
477
+
478
+ def unescape_bytea(s)
479
+ if PGconn.respond_to? :unescape_bytea
480
+ self.class.send(:define_method, :unescape_bytea) do |s|
481
+ PGconn.unescape_bytea(s) if s
482
+ end
483
+ else
484
+ self.class.send(:define_method, :unescape_bytea) do |s|
485
+ if s
486
+ result = ''
487
+ i, max = 0, s.size
488
+ while i < max
489
+ char = s[i]
490
+ if char == ?\\
491
+ if s[i+1] == ?\\
492
+ char = ?\\
493
+ i += 1
494
+ else
495
+ char = s[i+1..i+3].oct
496
+ i += 3
497
+ end
498
+ end
499
+ result << char
500
+ i += 1
501
+ end
502
+ result
503
+ end
504
+ end
505
+ end
506
+ unescape_bytea(s)
507
+ end
508
+
509
+ # Query a table's column names, default values, and types.
510
+ #
511
+ # The underlying query is roughly:
512
+ # SELECT column.name, column.type, default.value
513
+ # FROM column LEFT JOIN default
514
+ # ON column.table_id = default.table_id
515
+ # AND column.num = default.column_num
516
+ # WHERE column.table_id = get_table_id('table_name')
517
+ # AND column.num > 0
518
+ # AND NOT column.is_dropped
519
+ # ORDER BY column.num
520
+ #
521
+ # If the table name is not prefixed with a schema, the database will
522
+ # take the first match from the schema search path.
523
+ #
524
+ # Query implementation notes:
525
+ # - format_type includes the column size constraint, e.g. varchar(50)
526
+ # - ::regclass is a function that gives the id for a table name
527
+ def column_definitions(table_name)
528
+ query <<-end_sql
529
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
530
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
531
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
532
+ WHERE a.attrelid = '#{table_name}'::regclass
533
+ AND a.attnum > 0 AND NOT a.attisdropped
534
+ ORDER BY a.attnum
535
+ end_sql
536
+ end
537
+
538
+ # Translate PostgreSQL-specific types into simplified SQL types.
539
+ # These are special cases; standard types are handled by
540
+ # ConnectionAdapters::Column#simplified_type.
541
+ def translate_field_type(field_type)
542
+ # Match the beginning of field_type since it may have a size constraint on the end.
543
+ case field_type
544
+ # PostgreSQL array data types.
545
+ when /\[\]$/i then 'string'
546
+ when /^timestamp/i then 'datetime'
547
+ when /^real|^money/i then 'float'
548
+ when /^interval/i then 'string'
549
+ # geometric types (the line type is currently not implemented in postgresql)
550
+ when /^(?:point|lseg|box|"?path"?|polygon|circle)/i then 'string'
551
+ when /^bytea/i then 'binary'
552
+ else field_type # Pass through standard types.
553
+ end
554
+ end
555
+
556
+ def default_value(value)
557
+ # Boolean types
558
+ return "t" if value =~ /true/i
559
+ return "f" if value =~ /false/i
560
+
561
+ # Char/String/Bytea type values
562
+ return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
563
+
564
+ # Numeric values
565
+ return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
566
+
567
+ # Fixed dates / times
568
+ return $1 if value =~ /^'(.+)'::(date|timestamp)/
569
+
570
+ # Anything else is blank, some user type, or some function
571
+ # and we can't know the value of that, so return nil.
572
+ return nil
573
+ end
574
+
575
+ # Only needed for DateTime instances
576
+ def cast_to_time(value)
577
+ return value unless value.class == DateTime
578
+ v = value
579
+ time_array = [v.year, v.month, v.day, v.hour, v.min, v.sec, v.usec]
580
+ Time.send(Base.default_timezone, *time_array) rescue nil
581
+ end
582
+ end
583
+ end
584
+ end