activerecord 1.0.0 → 2.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 (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -1,177 +1,847 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
1
2
 
2
- # postgresql_adaptor.rb
3
- # author: Luke Holden <lholden@cablelan.net>
4
- # notes: Currently this adaptor does not pass the test_zero_date_fields
5
- # and test_zero_datetime_fields unit tests in the BasicsTest test
6
- # group.
7
- #
8
- # This is due to the fact that, in postgresql you can not have a
9
- # totally zero timestamp. Instead null/nil should be used to
10
- # represent no value.
11
- #
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)
12
8
 
13
- require 'active_record/connection_adapters/abstract_adapter'
14
- require 'parsedate'
15
-
16
- begin
17
- # Only include the PostgreSQL driver if one hasn't already been loaded
18
- require 'postgres' unless self.class.const_defined?(:PGconn)
19
-
20
- module ActiveRecord
21
- class Base
22
- # Establishes a connection to the database that's used by all Active Record objects
23
- def self.postgresql_connection(config) # :nodoc:
24
- symbolize_strings_in_hash(config)
25
- host = config[:host]
26
- port = config[:port] || 5432 unless host.nil?
27
- username = config[:username] || ""
28
- password = config[:password] || ""
29
-
30
- if config.has_key?(:database)
31
- database = config[:database]
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
+ if config.has_key?(:database)
16
+ database = config[:database]
17
+ else
18
+ raise ArgumentError, "No database specified. Missing argument: database."
19
+ end
20
+
21
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
22
+ # so just pass a nil connection object for the time being.
23
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
24
+ end
25
+ end
26
+
27
+ module ConnectionAdapters
28
+ # PostgreSQL-specific extensions to column definitions in a table.
29
+ class PostgreSQLColumn < Column #:nodoc:
30
+ # Instantiates a new PostgreSQL column definition in a table.
31
+ def initialize(name, default, sql_type = nil, null = true)
32
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
33
+ end
34
+
35
+ private
36
+ # Extracts the scale from PostgreSQL-specific data types.
37
+ def extract_scale(sql_type)
38
+ # Money type has a fixed scale of 2.
39
+ sql_type =~ /^money/ ? 2 : super
40
+ end
41
+
42
+ # Extracts the precision from PostgreSQL-specific data types.
43
+ def extract_precision(sql_type)
44
+ # Actual code is defined dynamically in PostgreSQLAdapter.connect
45
+ # depending on the server specifics
46
+ super
47
+ end
48
+
49
+ # Escapes binary strings for bytea input to the database.
50
+ def self.string_to_binary(value)
51
+ if PGconn.respond_to?(:escape_bytea)
52
+ self.class.module_eval do
53
+ define_method(:string_to_binary) do |value|
54
+ PGconn.escape_bytea(value) if value
55
+ end
56
+ end
57
+ else
58
+ self.class.module_eval do
59
+ define_method(:string_to_binary) do |value|
60
+ if value
61
+ result = ''
62
+ value.each_byte { |c| result << sprintf('\\\\%03o', c) }
63
+ result
64
+ end
65
+ end
66
+ end
67
+ end
68
+ self.class.string_to_binary(value)
69
+ end
70
+
71
+ # Unescapes bytea output from a database to the binary string it represents.
72
+ def self.binary_to_string(value)
73
+ # In each case, check if the value actually is escaped PostgreSQL bytea output
74
+ # or an unescaped Active Record attribute that was just written.
75
+ if PGconn.respond_to?(:unescape_bytea)
76
+ self.class.module_eval do
77
+ define_method(:binary_to_string) do |value|
78
+ if value =~ /\\\d{3}/
79
+ PGconn.unescape_bytea(value)
80
+ else
81
+ value
82
+ end
83
+ end
84
+ end
85
+ else
86
+ self.class.module_eval do
87
+ define_method(:binary_to_string) do |value|
88
+ if value =~ /\\\d{3}/
89
+ result = ''
90
+ i, max = 0, value.size
91
+ while i < max
92
+ char = value[i]
93
+ if char == ?\\
94
+ if value[i+1] == ?\\
95
+ char = ?\\
96
+ i += 1
97
+ else
98
+ char = value[i+1..i+3].oct
99
+ i += 3
100
+ end
101
+ end
102
+ result << char
103
+ i += 1
104
+ end
105
+ result
106
+ else
107
+ value
108
+ end
109
+ end
110
+ end
111
+ end
112
+ self.class.binary_to_string(value)
113
+ end
114
+
115
+ # Maps PostgreSQL-specific data types to logical Rails types.
116
+ def simplified_type(field_type)
117
+ case field_type
118
+ # Numeric and monetary types
119
+ when /^(?:real|double precision)$/
120
+ :float
121
+ # Monetary types
122
+ when /^money$/
123
+ :decimal
124
+ # Character types
125
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
126
+ :string
127
+ # Binary data types
128
+ when /^bytea$/
129
+ :binary
130
+ # Date/time types
131
+ when /^timestamp with(?:out)? time zone$/
132
+ :datetime
133
+ when /^interval$/
134
+ :string
135
+ # Geometric types
136
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
137
+ :string
138
+ # Network address types
139
+ when /^(?:cidr|inet|macaddr)$/
140
+ :string
141
+ # Bit strings
142
+ when /^bit(?: varying)?(?:\(\d+\))?$/
143
+ :string
144
+ # XML type
145
+ when /^xml$/
146
+ :string
147
+ # Arrays
148
+ when /^\D+\[\]$/
149
+ :string
150
+ # Object identifier types
151
+ when /^oid$/
152
+ :integer
153
+ # Pass through all types that are not specific to PostgreSQL.
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ # Extracts the value from a PostgreSQL column default definition.
160
+ def self.extract_value_from_default(default)
161
+ case default
162
+ # Numeric types
163
+ when /\A-?\d+(\.\d*)?\z/
164
+ default
165
+ # Character types
166
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
167
+ $1
168
+ # Character types (8.1 formatting)
169
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
170
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
171
+ # Binary data types
172
+ when /\A'(.*)'::bytea\z/m
173
+ $1
174
+ # Date/time types
175
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
176
+ $1
177
+ when /\A'(.*)'::interval\z/
178
+ $1
179
+ # Boolean type
180
+ when 'true'
181
+ true
182
+ when 'false'
183
+ false
184
+ # Geometric types
185
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
186
+ $1
187
+ # Network address types
188
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
189
+ $1
190
+ # Bit string types
191
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
192
+ $1
193
+ # XML type
194
+ when /\A'(.*)'::xml\z/m
195
+ $1
196
+ # Arrays
197
+ when /\A'(.*)'::"?\D+"?\[\]\z/
198
+ $1
199
+ # Object identifier types
200
+ when /\A-?\d+\z/
201
+ $1
202
+ else
203
+ # Anything else is blank, some user type, or some function
204
+ # and we can't know the value of that, so return nil.
205
+ nil
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ module ConnectionAdapters
212
+ # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
213
+ # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
214
+ #
215
+ # Options:
216
+ #
217
+ # * <tt>:host</tt> -- Defaults to localhost
218
+ # * <tt>:port</tt> -- Defaults to 5432
219
+ # * <tt>:username</tt> -- Defaults to nothing
220
+ # * <tt>:password</tt> -- Defaults to nothing
221
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
222
+ # * <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.
223
+ # * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
224
+ # * <tt>:min_messages</tt> -- An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.
225
+ # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
226
+ class PostgreSQLAdapter < AbstractAdapter
227
+ # Returns 'PostgreSQL' as adapter name for identification purposes.
228
+ def adapter_name
229
+ 'PostgreSQL'
230
+ end
231
+
232
+ # Initializes and connects a PostgreSQL adapter.
233
+ def initialize(connection, logger, connection_parameters, config)
234
+ super(connection, logger)
235
+ @connection_parameters, @config = connection_parameters, config
236
+
237
+ connect
238
+ end
239
+
240
+ # Is this connection alive and ready for queries?
241
+ def active?
242
+ if @connection.respond_to?(:status)
243
+ @connection.status == PGconn::CONNECTION_OK
32
244
  else
33
- raise ArgumentError, "No database specified. Missing argument: database."
245
+ # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
246
+ @connection.query 'SELECT 1'
247
+ true
34
248
  end
249
+ # postgres-pr raises a NoMethodError when querying if no connection is available.
250
+ rescue PGError, NoMethodError
251
+ false
252
+ end
35
253
 
36
- ConnectionAdapters::PostgreSQLAdapter.new(
37
- PGconn.connect(host, port, "", "", database, username, password), logger
38
- )
254
+ # Close then reopen the connection.
255
+ def reconnect!
256
+ if @connection.respond_to?(:reset)
257
+ @connection.reset
258
+ configure_connection
259
+ else
260
+ disconnect!
261
+ connect
262
+ end
39
263
  end
40
- end
41
264
 
42
- module ConnectionAdapters
43
- class PostgreSQLAdapter < AbstractAdapter # :nodoc:
265
+ # Close the connection.
266
+ def disconnect!
267
+ @connection.close rescue nil
268
+ end
269
+
270
+ def native_database_types #:nodoc:
271
+ {
272
+ :primary_key => "serial primary key",
273
+ :string => { :name => "character varying", :limit => 255 },
274
+ :text => { :name => "text" },
275
+ :integer => { :name => "integer" },
276
+ :float => { :name => "float" },
277
+ :decimal => { :name => "decimal" },
278
+ :datetime => { :name => "timestamp" },
279
+ :timestamp => { :name => "timestamp" },
280
+ :time => { :name => "time" },
281
+ :date => { :name => "date" },
282
+ :binary => { :name => "bytea" },
283
+ :boolean => { :name => "boolean" }
284
+ }
285
+ end
286
+
287
+ # Does PostgreSQL support migrations?
288
+ def supports_migrations?
289
+ true
290
+ end
291
+
292
+ # Does PostgreSQL support standard conforming strings?
293
+ def supports_standard_conforming_strings?
294
+ # Temporarily set the client message level above error to prevent unintentional
295
+ # error messages in the logs when working on a PostgreSQL database server that
296
+ # does not support standard conforming strings.
297
+ client_min_messages_old = client_min_messages
298
+ self.client_min_messages = 'panic'
299
+
300
+ # postgres-pr does not raise an exception when client_min_messages is set higher
301
+ # than error and "SHOW standard_conforming_strings" fails, but returns an empty
302
+ # PGresult instead.
303
+ has_support = execute('SHOW standard_conforming_strings')[0][0] rescue false
304
+ self.client_min_messages = client_min_messages_old
305
+ has_support
306
+ end
307
+
308
+ # Returns the configured supported identifier length supported by PostgreSQL,
309
+ # or report the default of 63 on PostgreSQL 7.x.
310
+ def table_alias_length
311
+ @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
312
+ end
44
313
 
45
- def select_all(sql, name = nil)
46
- select(sql, name)
314
+ # QUOTING ==================================================
315
+
316
+ # Quotes PostgreSQL-specific data types for SQL input.
317
+ def quote(value, column = nil) #:nodoc:
318
+ if value.kind_of?(String) && column && column.type == :binary
319
+ "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
320
+ elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
321
+ "xml '#{quote_string(value)}'"
322
+ elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
323
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
324
+ "'#{value.to_s}'"
325
+ elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
326
+ case value
327
+ when /^[01]*$/
328
+ "B'#{value}'" # Bit-string notation
329
+ when /^[0-9A-F]*$/i
330
+ "X'#{value}'" # Hexadecimal notation
331
+ end
332
+ else
333
+ super
334
+ end
335
+ end
336
+
337
+ # Quotes strings for use in SQL input in the postgres driver for better performance.
338
+ def quote_string(s) #:nodoc:
339
+ if PGconn.respond_to?(:escape)
340
+ self.class.instance_eval do
341
+ define_method(:quote_string) do |s|
342
+ PGconn.escape(s)
343
+ end
344
+ end
345
+ else
346
+ # There are some incorrectly compiled postgres drivers out there
347
+ # that don't define PGconn.escape.
348
+ self.class.instance_eval do
349
+ undef_method(:quote_string)
350
+ end
47
351
  end
352
+ quote_string(s)
353
+ end
354
+
355
+ # Quotes column names for use in SQL queries.
356
+ def quote_column_name(name) #:nodoc:
357
+ %("#{name}")
358
+ end
48
359
 
49
- def select_one(sql, name = nil)
50
- result = select(sql, name)
51
- result.nil? ? nil : result.first
360
+ # Quote date/time values for use in SQL input. Includes microseconds
361
+ # if the value is a Time responding to usec.
362
+ def quoted_date(value) #:nodoc:
363
+ if value.acts_like?(:time) && value.respond_to?(:usec)
364
+ "#{super}.#{sprintf("%06d", value.usec)}"
365
+ else
366
+ super
52
367
  end
368
+ end
369
+
370
+ # REFERENTIAL INTEGRITY ====================================
371
+
372
+ def disable_referential_integrity(&block) #:nodoc:
373
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
374
+ yield
375
+ ensure
376
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
377
+ end
378
+
379
+ # DATABASE STATEMENTS ======================================
380
+
381
+ # Executes a SELECT query and returns an array of rows. Each row is an
382
+ # array of field values.
383
+ def select_rows(sql, name = nil)
384
+ select_raw(sql, name).last
385
+ end
386
+
387
+ # Executes an INSERT query and returns the new record's ID
388
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
389
+ table = sql.split(" ", 4)[2]
390
+ super || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
391
+ end
53
392
 
54
- def columns(table_name, name = nil)
55
- table_structure(table_name).inject([]) do |columns, field|
56
- columns << Column.new(field[0], field[2], field[1])
57
- columns
393
+ # Queries the database and returns the results in an Array or nil otherwise.
394
+ def query(sql, name = nil) #:nodoc:
395
+ log(sql, name) do
396
+ if @async
397
+ @connection.async_query(sql)
398
+ else
399
+ @connection.query(sql)
58
400
  end
59
401
  end
402
+ end
60
403
 
61
- def insert(sql, name = nil, pk = nil, id_value = nil)
62
- execute(sql, name = nil)
63
- table = sql.split(" ", 4)[2]
64
- return id_value || last_insert_id(table, pk)
404
+ # Executes an SQL statement, returning a PGresult object on success
405
+ # or raising a PGError exception otherwise.
406
+ def execute(sql, name = nil)
407
+ log(sql, name) do
408
+ if @async
409
+ @connection.async_exec(sql)
410
+ else
411
+ @connection.exec(sql)
412
+ end
65
413
  end
414
+ end
415
+
416
+ # Executes an UPDATE query and returns the number of affected tuples.
417
+ def update_sql(sql, name = nil)
418
+ super.cmdtuples
419
+ end
420
+
421
+ # Begins a transaction.
422
+ def begin_db_transaction
423
+ execute "BEGIN"
424
+ end
66
425
 
67
- def execute(sql, name = nil)
68
- log(sql, name, @connection) { |connection| connection.query(sql) }
426
+ # Commits a transaction.
427
+ def commit_db_transaction
428
+ execute "COMMIT"
429
+ end
430
+
431
+ # Aborts a transaction.
432
+ def rollback_db_transaction
433
+ execute "ROLLBACK"
434
+ end
435
+
436
+ # SCHEMA STATEMENTS ========================================
437
+
438
+ # Returns the list of all tables in the schema search path or a specified schema.
439
+ def tables(name = nil)
440
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
441
+ query(<<-SQL, name).map { |row| row[0] }
442
+ SELECT tablename
443
+ FROM pg_tables
444
+ WHERE schemaname IN (#{schemas})
445
+ SQL
446
+ end
447
+
448
+ # Returns the list of all indexes for a table.
449
+ def indexes(table_name, name = nil)
450
+ result = query(<<-SQL, name)
451
+ SELECT i.relname, d.indisunique, a.attname
452
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a
453
+ WHERE i.relkind = 'i'
454
+ AND d.indexrelid = i.oid
455
+ AND d.indisprimary = 'f'
456
+ AND t.oid = d.indrelid
457
+ AND t.relname = '#{table_name}'
458
+ AND a.attrelid = t.oid
459
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
460
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
461
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
462
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
463
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
464
+ ORDER BY i.relname
465
+ SQL
466
+
467
+ current_index = nil
468
+ indexes = []
469
+
470
+ result.each do |row|
471
+ if current_index != row[0]
472
+ indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
473
+ current_index = row[0]
474
+ end
475
+
476
+ indexes.last.columns << row[2]
69
477
  end
70
478
 
71
- alias_method :update, :execute
72
- alias_method :delete, :execute
479
+ indexes
480
+ end
73
481
 
74
- def begin_db_transaction() execute "BEGIN" end
75
- def commit_db_transaction() execute "COMMIT" end
76
- def rollback_db_transaction() execute "ROLLBACK" end
482
+ # Returns the list of all column definitions for a table.
483
+ def columns(table_name, name = nil)
484
+ # Limit, precision, and scale are all handled by the superclass.
485
+ column_definitions(table_name).collect do |name, type, default, notnull|
486
+ PostgreSQLColumn.new(name, default, type, notnull == 'f')
487
+ end
488
+ end
77
489
 
78
- def quote_column_name(name)
79
- return "\"#{name}\""
490
+ # Sets the schema search path to a string of comma-separated schema names.
491
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
492
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
493
+ #
494
+ # This should be not be called manually but set in database.yml.
495
+ def schema_search_path=(schema_csv)
496
+ if schema_csv
497
+ execute "SET search_path TO #{schema_csv}"
498
+ @schema_search_path = schema_csv
80
499
  end
500
+ end
501
+
502
+ # Returns the active schema search path.
503
+ def schema_search_path
504
+ @schema_search_path ||= query('SHOW search_path')[0][0]
505
+ end
506
+
507
+ # Returns the current client message level.
508
+ def client_min_messages
509
+ query('SHOW client_min_messages')[0][0]
510
+ end
511
+
512
+ # Set the client message level.
513
+ def client_min_messages=(level)
514
+ execute("SET client_min_messages TO '#{level}'")
515
+ end
81
516
 
82
- private
83
- def last_insert_id(table, column = "id")
84
- sequence_name = "#{table}_#{column || 'id'}_seq"
85
- @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
517
+ # Returns the sequence name for a table's primary key or some other specified key.
518
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
519
+ default_pk, default_seq = pk_and_sequence_for(table_name)
520
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
521
+ end
522
+
523
+ # Resets the sequence of a table's primary key to the maximum value.
524
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
525
+ unless pk and sequence
526
+ default_pk, default_sequence = pk_and_sequence_for(table)
527
+ pk ||= default_pk
528
+ sequence ||= default_sequence
529
+ end
530
+ if pk
531
+ if sequence
532
+ select_value <<-end_sql, 'Reset sequence'
533
+ SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
534
+ end_sql
535
+ else
536
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
86
537
  end
538
+ end
539
+ end
540
+
541
+ # Returns a table's primary key and belonging sequence.
542
+ def pk_and_sequence_for(table) #:nodoc:
543
+ # First try looking for a sequence with a dependency on the
544
+ # given table's primary key.
545
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
546
+ SELECT attr.attname, seq.relname
547
+ FROM pg_class seq,
548
+ pg_attribute attr,
549
+ pg_depend dep,
550
+ pg_namespace name,
551
+ pg_constraint cons
552
+ WHERE seq.oid = dep.objid
553
+ AND seq.relkind = 'S'
554
+ AND attr.attrelid = dep.refobjid
555
+ AND attr.attnum = dep.refobjsubid
556
+ AND attr.attrelid = cons.conrelid
557
+ AND attr.attnum = cons.conkey[1]
558
+ AND cons.contype = 'p'
559
+ AND dep.refobjid = '#{table}'::regclass
560
+ end_sql
561
+
562
+ if result.nil? or result.empty?
563
+ # If that fails, try parsing the primary key's default value.
564
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
565
+ # the 8.1+ nextval('foo'::regclass).
566
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
567
+ SELECT attr.attname, split_part(def.adsrc, '''', 2)
568
+ FROM pg_class t
569
+ JOIN pg_attribute attr ON (t.oid = attrelid)
570
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
571
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
572
+ WHERE t.oid = '#{table}'::regclass
573
+ AND cons.contype = 'p'
574
+ AND def.adsrc ~* 'nextval'
575
+ end_sql
576
+ end
577
+ # [primary_key, sequence]
578
+ [result.first, result.last]
579
+ rescue
580
+ nil
581
+ end
582
+
583
+ # Renames a table.
584
+ def rename_table(name, new_name)
585
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
586
+ end
587
+
588
+ # Adds a column to a table.
589
+ def add_column(table_name, column_name, type, options = {})
590
+ default = options[:default]
591
+ notnull = options[:null] == false
592
+
593
+ # Add the column.
594
+ execute("ALTER TABLE #{table_name} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
595
+
596
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
597
+ change_column_null(table_name, column_name, false, default) if notnull
598
+ end
87
599
 
88
- def select(sql, name = nil)
89
- res = nil
90
- log(sql, name, @connection) { |connection| res = connection.exec(sql) }
91
-
92
- results = res.result
93
- rows = []
94
- if results.length > 0
95
- fields = res.fields
96
- results.each do |row|
97
- hashed_row = {}
98
- row.each_index { |cel_index| hashed_row[fields[cel_index]] = row[cel_index] }
99
- rows << hashed_row
600
+ # Changes the column of a table.
601
+ def change_column(table_name, column_name, type, options = {})
602
+ begin
603
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
604
+ rescue ActiveRecord::StatementInvalid
605
+ # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
606
+ begin_db_transaction
607
+ tmp_column_name = "#{column_name}_ar_tmp"
608
+ add_column(table_name, tmp_column_name, type, options)
609
+ execute "UPDATE #{table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
610
+ remove_column(table_name, column_name)
611
+ rename_column(table_name, tmp_column_name, column_name)
612
+ commit_db_transaction
613
+ end
614
+
615
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
616
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
617
+ end
618
+
619
+ # Changes the default value of a table column.
620
+ def change_column_default(table_name, column_name, default)
621
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
622
+ end
623
+
624
+ def change_column_null(table_name, column_name, null, default = nil)
625
+ unless null || default.nil?
626
+ execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
627
+ end
628
+ execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
629
+ end
630
+
631
+ # Renames a column in a table.
632
+ def rename_column(table_name, column_name, new_column_name)
633
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
634
+ end
635
+
636
+ # Drops an index from a table.
637
+ def remove_index(table_name, options = {})
638
+ execute "DROP INDEX #{index_name(table_name, options)}"
639
+ end
640
+
641
+ # Maps logical Rails types to PostgreSQL-specific data types.
642
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
643
+ return super unless type.to_s == 'integer'
644
+
645
+ if limit.nil? || limit == 4
646
+ 'integer'
647
+ elsif limit < 4
648
+ 'smallint'
649
+ else
650
+ 'bigint'
651
+ end
652
+ end
653
+
654
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
655
+ #
656
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
657
+ # requires that the ORDER BY include the distinct column.
658
+ #
659
+ # distinct("posts.id", "posts.created_at desc")
660
+ def distinct(columns, order_by) #:nodoc:
661
+ return "DISTINCT #{columns}" if order_by.blank?
662
+
663
+ # Construct a clean list of column names from the ORDER BY clause, removing
664
+ # any ASC/DESC modifiers
665
+ order_columns = order_by.split(',').collect { |s| s.split.first }
666
+ order_columns.delete_if &:blank?
667
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
668
+
669
+ # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
670
+ # all the required columns for the ORDER BY to work properly.
671
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
672
+ sql << order_columns * ', '
673
+ end
674
+
675
+ # Returns an ORDER BY clause for the passed order option.
676
+ #
677
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
678
+ # by wrapping the sql as a sub-select and ordering in that query.
679
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
680
+ return sql if options[:order].blank?
681
+
682
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
683
+ order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
684
+ order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
685
+
686
+ sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
687
+ end
688
+
689
+ protected
690
+ # Returns the version of the connected PostgreSQL version.
691
+ def postgresql_version
692
+ @postgresql_version ||=
693
+ if @connection.respond_to?(:server_version)
694
+ @connection.server_version
695
+ else
696
+ # Mimic PGconn.server_version behavior
697
+ begin
698
+ query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
699
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
700
+ rescue
701
+ 0
100
702
  end
101
703
  end
102
- return rows
103
- end
704
+ end
705
+
706
+ private
707
+ # The internal PostgreSQL identifer of the money data type.
708
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
709
+
710
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
711
+ # connected server's characteristics.
712
+ def connect
713
+ @connection = PGconn.connect(*@connection_parameters)
714
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
104
715
 
105
- def split_table_schema(table_name)
106
- schema_split = table_name.split('.')
107
- schema_name = "public"
108
- if schema_split.length > 1
109
- schema_name = schema_split.first.strip
110
- table_name = schema_split.last.strip
716
+ # Ignore async_exec and async_query when using postgres-pr.
717
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
718
+
719
+ # Use escape string syntax if available. We cannot do this lazily when encountering
720
+ # the first string, because that could then break any transactions in progress.
721
+ # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
722
+ # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
723
+ # support escape string syntax. Don't override the inherited quoted_string_prefix.
724
+ if supports_standard_conforming_strings?
725
+ self.class.instance_eval do
726
+ define_method(:quoted_string_prefix) { 'E' }
111
727
  end
112
- return [schema_name, table_name]
113
728
  end
114
729
 
115
- def table_structure(table_name)
116
- database_name = @connection.db
117
- schema_name, table_name = split_table_schema(table_name)
118
-
119
- # Grab a list of all the default values for the columns.
120
- sql = "SELECT column_name, column_default, character_maximum_length, data_type "
121
- sql << " FROM information_schema.columns "
122
- sql << " WHERE table_catalog = '#{database_name}' "
123
- sql << " AND table_schema = '#{schema_name}' "
124
- sql << " AND table_name = '#{table_name}';"
125
-
126
- column_defaults = nil
127
- log(sql, nil, @connection) { |connection| column_defaults = connection.query(sql) }
128
- column_defaults.collect do |row|
129
- field = row[0]
130
- type = type_as_string(row[3], row[2])
131
- default = default_value(row[1])
132
- length = row[2]
133
-
134
- [field, type, default, length]
730
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
731
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
732
+ # should know about this but can't detect it there, so deal with it here.
733
+ money_precision = (postgresql_version >= 80300) ? 19 : 10
734
+ PostgreSQLColumn.module_eval(<<-end_eval)
735
+ def extract_precision(sql_type)
736
+ if sql_type =~ /^money$/
737
+ #{money_precision}
738
+ else
739
+ super
740
+ end
135
741
  end
136
- end
742
+ end_eval
743
+
744
+ configure_connection
745
+ end
137
746
 
138
- def type_as_string(field_type, field_length)
139
- type = case field_type
140
- when 'numeric', 'real', 'money' then 'float'
141
- when 'character varying', 'interval' then 'string'
142
- when 'timestamp without time zone' then 'datetime'
143
- else field_type
747
+ # Configures the encoding, verbosity, and schema search path of the connection.
748
+ # This is called by #connect and should not be called manually.
749
+ def configure_connection
750
+ if @config[:encoding]
751
+ if @connection.respond_to?(:set_client_encoding)
752
+ @connection.set_client_encoding(@config[:encoding])
753
+ else
754
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
144
755
  end
756
+ end
757
+ self.client_min_messages = @config[:min_messages] if @config[:min_messages]
758
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
759
+ end
145
760
 
146
- size = field_length.nil? ? "" : "(#{field_length})"
761
+ # Returns the current ID of a table's sequence.
762
+ def last_insert_id(table, sequence_name) #:nodoc:
763
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
764
+ end
147
765
 
148
- return type + size
766
+ # Executes a SELECT query and returns the results, performing any data type
767
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
768
+ def select(sql, name = nil)
769
+ fields, rows = select_raw(sql, name)
770
+ result = []
771
+ for row in rows
772
+ row_hash = {}
773
+ fields.each_with_index do |f, i|
774
+ row_hash[f] = row[i]
775
+ end
776
+ result << row_hash
149
777
  end
778
+ result
779
+ end
150
780
 
151
- def default_value(value)
152
- # Boolean types
153
- return "t" if value =~ /true/i
154
- return "f" if value =~ /false/i
155
-
156
- # Char/String type values
157
- return $1 if value =~ /^'(.*)'::(bpchar|text|character varying)$/
158
-
159
- # Numeric values
160
- return value if value =~ /^[0-9]+(\.[0-9]*)?/
161
-
162
- # Date / Time magic values
163
- return Time.now.to_s if value =~ /^\('now'::text\)::(date|timestamp)/
164
-
165
- # Fixed dates / times
166
- return $1 if value =~ /^'(.+)'::(date|timestamp)/
167
-
168
- # Anything else is blank, some user type, or some function
169
- # and we can't know the value of that, so return nil.
170
- return nil
781
+ def select_raw(sql, name = nil)
782
+ res = execute(sql, name)
783
+ results = res.result
784
+ fields = []
785
+ rows = []
786
+ if results.length > 0
787
+ fields = res.fields
788
+ results.each do |row|
789
+ hashed_row = {}
790
+ row.each_index do |cell_index|
791
+ # If this is a money type column and there are any currency symbols,
792
+ # then strip them off. Indeed it would be prettier to do this in
793
+ # PostgreSQLColumn.string_to_decimal but would break form input
794
+ # fields that call value_before_type_cast.
795
+ if res.type(cell_index) == MONEY_COLUMN_TYPE_OID
796
+ # Because money output is formatted according to the locale, there are two
797
+ # cases to consider (note the decimal separators):
798
+ # (1) $12,345,678.12
799
+ # (2) $12.345.678,12
800
+ case column = row[cell_index]
801
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
802
+ row[cell_index] = column.gsub(/[^-\d\.]/, '')
803
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
804
+ row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
805
+ end
806
+ end
807
+
808
+ hashed_row[fields[cell_index]] = column
809
+ end
810
+ rows << row
811
+ end
171
812
  end
172
- end
813
+ res.clear
814
+ return fields, rows
815
+ end
816
+
817
+ # Returns the list of a table's column names, data types, and default values.
818
+ #
819
+ # The underlying query is roughly:
820
+ # SELECT column.name, column.type, default.value
821
+ # FROM column LEFT JOIN default
822
+ # ON column.table_id = default.table_id
823
+ # AND column.num = default.column_num
824
+ # WHERE column.table_id = get_table_id('table_name')
825
+ # AND column.num > 0
826
+ # AND NOT column.is_dropped
827
+ # ORDER BY column.num
828
+ #
829
+ # If the table name is not prefixed with a schema, the database will
830
+ # take the first match from the schema search path.
831
+ #
832
+ # Query implementation notes:
833
+ # - format_type includes the column size constraint, e.g. varchar(50)
834
+ # - ::regclass is a function that gives the id for a table name
835
+ def column_definitions(table_name) #:nodoc:
836
+ query <<-end_sql
837
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
838
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
839
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
840
+ WHERE a.attrelid = '#{table_name}'::regclass
841
+ AND a.attnum > 0 AND NOT a.attisdropped
842
+ ORDER BY a.attnum
843
+ end_sql
844
+ end
173
845
  end
174
846
  end
175
- rescue LoadError
176
- # PostgreSQL driver is not availible
177
847
  end