activerecord_authorails 1.0.0

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.
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