activerecord 2.3.18 → 3.2.22

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 (454) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1014 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +222 -0
  5. data/examples/performance.rb +100 -126
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +93 -99
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +247 -0
  10. data/lib/active_record/associations/association_scope.rb +134 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +54 -61
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +17 -59
  13. data/lib/active_record/associations/builder/association.rb +55 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +88 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  17. data/lib/active_record/associations/builder/has_many.rb +71 -0
  18. data/lib/active_record/associations/builder/has_one.rb +62 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +580 -0
  21. data/lib/active_record/associations/collection_proxy.rb +133 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +39 -119
  23. data/lib/active_record/associations/has_many_association.rb +60 -79
  24. data/lib/active_record/associations/has_many_through_association.rb +127 -206
  25. data/lib/active_record/associations/has_one_association.rb +55 -114
  26. data/lib/active_record/associations/has_one_through_association.rb +25 -26
  27. data/lib/active_record/associations/join_dependency/join_association.rb +159 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +214 -0
  31. data/lib/active_record/associations/join_helper.rb +55 -0
  32. data/lib/active_record/associations/preloader/association.rb +125 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/preloader.rb +181 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +693 -1337
  46. data/lib/active_record/attribute_assignment.rb +221 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  48. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  49. data/lib/active_record/attribute_methods/dirty.rb +111 -0
  50. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  51. data/lib/active_record/attribute_methods/query.rb +39 -0
  52. data/lib/active_record/attribute_methods/read.rb +136 -0
  53. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  54. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  55. data/lib/active_record/attribute_methods/write.rb +70 -0
  56. data/lib/active_record/attribute_methods.rb +211 -339
  57. data/lib/active_record/autosave_association.rb +179 -149
  58. data/lib/active_record/base.rb +401 -2907
  59. data/lib/active_record/callbacks.rb +91 -176
  60. data/lib/active_record/coders/yaml_column.rb +41 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +236 -119
  62. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +110 -58
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -74
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -35
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +71 -21
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +81 -311
  68. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +194 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +130 -83
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  71. data/lib/active_record/connection_adapters/column.rb +296 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +280 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +272 -493
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +650 -405
  75. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  76. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +30 -9
  77. data/lib/active_record/connection_adapters/sqlite_adapter.rb +276 -147
  78. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  79. data/lib/active_record/counter_cache.rb +123 -0
  80. data/lib/active_record/dynamic_finder_match.rb +41 -14
  81. data/lib/active_record/dynamic_matchers.rb +84 -0
  82. data/lib/active_record/dynamic_scope_match.rb +13 -15
  83. data/lib/active_record/errors.rb +195 -0
  84. data/lib/active_record/explain.rb +86 -0
  85. data/lib/active_record/explain_subscriber.rb +25 -0
  86. data/lib/active_record/fixtures/file.rb +65 -0
  87. data/lib/active_record/fixtures.rb +695 -770
  88. data/lib/active_record/identity_map.rb +162 -0
  89. data/lib/active_record/inheritance.rb +174 -0
  90. data/lib/active_record/integration.rb +60 -0
  91. data/lib/active_record/locale/en.yml +9 -27
  92. data/lib/active_record/locking/optimistic.rb +76 -73
  93. data/lib/active_record/locking/pessimistic.rb +32 -10
  94. data/lib/active_record/log_subscriber.rb +72 -0
  95. data/lib/active_record/migration/command_recorder.rb +105 -0
  96. data/lib/active_record/migration.rb +415 -205
  97. data/lib/active_record/model_schema.rb +368 -0
  98. data/lib/active_record/nested_attributes.rb +153 -63
  99. data/lib/active_record/observer.rb +27 -103
  100. data/lib/active_record/persistence.rb +376 -0
  101. data/lib/active_record/query_cache.rb +49 -8
  102. data/lib/active_record/querying.rb +58 -0
  103. data/lib/active_record/railtie.rb +131 -0
  104. data/lib/active_record/railties/console_sandbox.rb +6 -0
  105. data/lib/active_record/railties/controller_runtime.rb +49 -0
  106. data/lib/active_record/railties/databases.rake +659 -0
  107. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  108. data/lib/active_record/readonly_attributes.rb +26 -0
  109. data/lib/active_record/reflection.rb +269 -120
  110. data/lib/active_record/relation/batches.rb +90 -0
  111. data/lib/active_record/relation/calculations.rb +372 -0
  112. data/lib/active_record/relation/delegation.rb +49 -0
  113. data/lib/active_record/relation/finder_methods.rb +402 -0
  114. data/lib/active_record/relation/predicate_builder.rb +63 -0
  115. data/lib/active_record/relation/query_methods.rb +417 -0
  116. data/lib/active_record/relation/spawn_methods.rb +180 -0
  117. data/lib/active_record/relation.rb +537 -0
  118. data/lib/active_record/result.rb +40 -0
  119. data/lib/active_record/sanitization.rb +194 -0
  120. data/lib/active_record/schema.rb +9 -6
  121. data/lib/active_record/schema_dumper.rb +55 -32
  122. data/lib/active_record/scoping/default.rb +142 -0
  123. data/lib/active_record/scoping/named.rb +200 -0
  124. data/lib/active_record/scoping.rb +152 -0
  125. data/lib/active_record/serialization.rb +8 -91
  126. data/lib/active_record/serializers/xml_serializer.rb +43 -197
  127. data/lib/active_record/session_store.rb +129 -103
  128. data/lib/active_record/store.rb +52 -0
  129. data/lib/active_record/test_case.rb +30 -23
  130. data/lib/active_record/timestamp.rb +95 -52
  131. data/lib/active_record/transactions.rb +212 -66
  132. data/lib/active_record/translation.rb +22 -0
  133. data/lib/active_record/validations/associated.rb +43 -0
  134. data/lib/active_record/validations/uniqueness.rb +180 -0
  135. data/lib/active_record/validations.rb +43 -1106
  136. data/lib/active_record/version.rb +5 -4
  137. data/lib/active_record.rb +121 -48
  138. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  139. data/lib/rails/generators/active_record/migration/templates/migration.rb +34 -0
  140. data/lib/rails/generators/active_record/migration.rb +15 -0
  141. data/lib/rails/generators/active_record/model/model_generator.rb +47 -0
  142. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  143. data/lib/rails/generators/active_record/model/templates/model.rb +12 -0
  144. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  145. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  146. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  147. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  148. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  149. data/lib/rails/generators/active_record.rb +25 -0
  150. metadata +187 -363
  151. data/CHANGELOG +0 -5904
  152. data/README +0 -351
  153. data/RUNNING_UNIT_TESTS +0 -36
  154. data/Rakefile +0 -268
  155. data/install.rb +0 -30
  156. data/lib/active_record/association_preload.rb +0 -406
  157. data/lib/active_record/associations/association_collection.rb +0 -533
  158. data/lib/active_record/associations/association_proxy.rb +0 -288
  159. data/lib/active_record/batches.rb +0 -85
  160. data/lib/active_record/calculations.rb +0 -321
  161. data/lib/active_record/dirty.rb +0 -183
  162. data/lib/active_record/named_scope.rb +0 -197
  163. data/lib/active_record/serializers/json_serializer.rb +0 -91
  164. data/lib/activerecord.rb +0 -2
  165. data/test/assets/example.log +0 -1
  166. data/test/assets/flowers.jpg +0 -0
  167. data/test/cases/aaa_create_tables_test.rb +0 -24
  168. data/test/cases/active_schema_test_mysql.rb +0 -122
  169. data/test/cases/active_schema_test_postgresql.rb +0 -24
  170. data/test/cases/adapter_test.rb +0 -144
  171. data/test/cases/aggregations_test.rb +0 -167
  172. data/test/cases/ar_schema_test.rb +0 -32
  173. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  174. data/test/cases/associations/callbacks_test.rb +0 -161
  175. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  176. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  177. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  178. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  179. data/test/cases/associations/eager_singularization_test.rb +0 -145
  180. data/test/cases/associations/eager_test.rb +0 -852
  181. data/test/cases/associations/extension_test.rb +0 -62
  182. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  183. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  184. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  185. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  186. data/test/cases/associations/has_one_associations_test.rb +0 -330
  187. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  188. data/test/cases/associations/inner_join_association_test.rb +0 -93
  189. data/test/cases/associations/inverse_associations_test.rb +0 -566
  190. data/test/cases/associations/join_model_test.rb +0 -712
  191. data/test/cases/associations_test.rb +0 -282
  192. data/test/cases/attribute_methods_test.rb +0 -305
  193. data/test/cases/autosave_association_test.rb +0 -1218
  194. data/test/cases/base_test.rb +0 -2166
  195. data/test/cases/batches_test.rb +0 -81
  196. data/test/cases/binary_test.rb +0 -30
  197. data/test/cases/calculations_test.rb +0 -360
  198. data/test/cases/callbacks_observers_test.rb +0 -38
  199. data/test/cases/callbacks_test.rb +0 -438
  200. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  201. data/test/cases/column_alias_test.rb +0 -17
  202. data/test/cases/column_definition_test.rb +0 -70
  203. data/test/cases/connection_pool_test.rb +0 -25
  204. data/test/cases/connection_test_firebird.rb +0 -8
  205. data/test/cases/connection_test_mysql.rb +0 -65
  206. data/test/cases/copy_table_test_sqlite.rb +0 -80
  207. data/test/cases/counter_cache_test.rb +0 -84
  208. data/test/cases/database_statements_test.rb +0 -12
  209. data/test/cases/datatype_test_postgresql.rb +0 -204
  210. data/test/cases/date_time_test.rb +0 -37
  211. data/test/cases/default_test_firebird.rb +0 -16
  212. data/test/cases/defaults_test.rb +0 -111
  213. data/test/cases/deprecated_finder_test.rb +0 -30
  214. data/test/cases/dirty_test.rb +0 -316
  215. data/test/cases/finder_respond_to_test.rb +0 -76
  216. data/test/cases/finder_test.rb +0 -1098
  217. data/test/cases/fixtures_test.rb +0 -661
  218. data/test/cases/helper.rb +0 -68
  219. data/test/cases/i18n_test.rb +0 -46
  220. data/test/cases/inheritance_test.rb +0 -262
  221. data/test/cases/invalid_date_test.rb +0 -24
  222. data/test/cases/json_serialization_test.rb +0 -219
  223. data/test/cases/lifecycle_test.rb +0 -193
  224. data/test/cases/locking_test.rb +0 -350
  225. data/test/cases/method_scoping_test.rb +0 -704
  226. data/test/cases/migration_test.rb +0 -1649
  227. data/test/cases/migration_test_firebird.rb +0 -124
  228. data/test/cases/mixin_test.rb +0 -96
  229. data/test/cases/modules_test.rb +0 -109
  230. data/test/cases/multiple_db_test.rb +0 -85
  231. data/test/cases/named_scope_test.rb +0 -372
  232. data/test/cases/nested_attributes_test.rb +0 -840
  233. data/test/cases/pk_test.rb +0 -119
  234. data/test/cases/pooled_connections_test.rb +0 -103
  235. data/test/cases/query_cache_test.rb +0 -129
  236. data/test/cases/readonly_test.rb +0 -107
  237. data/test/cases/reflection_test.rb +0 -234
  238. data/test/cases/reload_models_test.rb +0 -22
  239. data/test/cases/repair_helper.rb +0 -50
  240. data/test/cases/reserved_word_test_mysql.rb +0 -176
  241. data/test/cases/sanitize_test.rb +0 -25
  242. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  243. data/test/cases/schema_dumper_test.rb +0 -211
  244. data/test/cases/schema_test_postgresql.rb +0 -178
  245. data/test/cases/serialization_test.rb +0 -47
  246. data/test/cases/sp_test_mysql.rb +0 -16
  247. data/test/cases/synonym_test_oracle.rb +0 -17
  248. data/test/cases/timestamp_test.rb +0 -75
  249. data/test/cases/transactions_test.rb +0 -543
  250. data/test/cases/unconnected_test.rb +0 -32
  251. data/test/cases/validations_i18n_test.rb +0 -925
  252. data/test/cases/validations_test.rb +0 -1684
  253. data/test/cases/xml_serialization_test.rb +0 -240
  254. data/test/cases/yaml_serialization_test.rb +0 -11
  255. data/test/config.rb +0 -5
  256. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  257. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  258. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  259. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  260. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  261. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  262. data/test/connections/native_db2/connection.rb +0 -25
  263. data/test/connections/native_firebird/connection.rb +0 -26
  264. data/test/connections/native_frontbase/connection.rb +0 -27
  265. data/test/connections/native_mysql/connection.rb +0 -25
  266. data/test/connections/native_openbase/connection.rb +0 -21
  267. data/test/connections/native_oracle/connection.rb +0 -27
  268. data/test/connections/native_postgresql/connection.rb +0 -21
  269. data/test/connections/native_sqlite/connection.rb +0 -25
  270. data/test/connections/native_sqlite3/connection.rb +0 -25
  271. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  272. data/test/connections/native_sybase/connection.rb +0 -23
  273. data/test/fixtures/accounts.yml +0 -29
  274. data/test/fixtures/all/developers.yml +0 -0
  275. data/test/fixtures/all/people.csv +0 -0
  276. data/test/fixtures/all/tasks.yml +0 -0
  277. data/test/fixtures/author_addresses.yml +0 -5
  278. data/test/fixtures/author_favorites.yml +0 -4
  279. data/test/fixtures/authors.yml +0 -9
  280. data/test/fixtures/binaries.yml +0 -132
  281. data/test/fixtures/books.yml +0 -7
  282. data/test/fixtures/categories/special_categories.yml +0 -9
  283. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  284. data/test/fixtures/categories.yml +0 -14
  285. data/test/fixtures/categories_ordered.yml +0 -7
  286. data/test/fixtures/categories_posts.yml +0 -23
  287. data/test/fixtures/categorizations.yml +0 -17
  288. data/test/fixtures/clubs.yml +0 -6
  289. data/test/fixtures/comments.yml +0 -59
  290. data/test/fixtures/companies.yml +0 -56
  291. data/test/fixtures/computers.yml +0 -4
  292. data/test/fixtures/courses.yml +0 -7
  293. data/test/fixtures/customers.yml +0 -26
  294. data/test/fixtures/developers.yml +0 -21
  295. data/test/fixtures/developers_projects.yml +0 -17
  296. data/test/fixtures/edges.yml +0 -6
  297. data/test/fixtures/entrants.yml +0 -14
  298. data/test/fixtures/faces.yml +0 -11
  299. data/test/fixtures/fk_test_has_fk.yml +0 -3
  300. data/test/fixtures/fk_test_has_pk.yml +0 -2
  301. data/test/fixtures/funny_jokes.yml +0 -10
  302. data/test/fixtures/interests.yml +0 -33
  303. data/test/fixtures/items.yml +0 -4
  304. data/test/fixtures/jobs.yml +0 -7
  305. data/test/fixtures/legacy_things.yml +0 -3
  306. data/test/fixtures/mateys.yml +0 -4
  307. data/test/fixtures/member_types.yml +0 -6
  308. data/test/fixtures/members.yml +0 -6
  309. data/test/fixtures/memberships.yml +0 -20
  310. data/test/fixtures/men.yml +0 -5
  311. data/test/fixtures/minimalistics.yml +0 -2
  312. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  313. data/test/fixtures/mixins.yml +0 -29
  314. data/test/fixtures/movies.yml +0 -7
  315. data/test/fixtures/naked/csv/accounts.csv +0 -1
  316. data/test/fixtures/naked/yml/accounts.yml +0 -1
  317. data/test/fixtures/naked/yml/companies.yml +0 -1
  318. data/test/fixtures/naked/yml/courses.yml +0 -1
  319. data/test/fixtures/organizations.yml +0 -5
  320. data/test/fixtures/owners.yml +0 -7
  321. data/test/fixtures/parrots.yml +0 -27
  322. data/test/fixtures/parrots_pirates.yml +0 -7
  323. data/test/fixtures/people.yml +0 -15
  324. data/test/fixtures/pets.yml +0 -14
  325. data/test/fixtures/pirates.yml +0 -9
  326. data/test/fixtures/polymorphic_designs.yml +0 -19
  327. data/test/fixtures/polymorphic_prices.yml +0 -19
  328. data/test/fixtures/posts.yml +0 -52
  329. data/test/fixtures/price_estimates.yml +0 -7
  330. data/test/fixtures/projects.yml +0 -7
  331. data/test/fixtures/readers.yml +0 -9
  332. data/test/fixtures/references.yml +0 -17
  333. data/test/fixtures/reserved_words/distinct.yml +0 -5
  334. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  335. data/test/fixtures/reserved_words/group.yml +0 -14
  336. data/test/fixtures/reserved_words/select.yml +0 -8
  337. data/test/fixtures/reserved_words/values.yml +0 -7
  338. data/test/fixtures/ships.yml +0 -5
  339. data/test/fixtures/sponsors.yml +0 -9
  340. data/test/fixtures/subscribers.yml +0 -7
  341. data/test/fixtures/subscriptions.yml +0 -12
  342. data/test/fixtures/taggings.yml +0 -28
  343. data/test/fixtures/tags.yml +0 -7
  344. data/test/fixtures/tasks.yml +0 -7
  345. data/test/fixtures/tees.yml +0 -4
  346. data/test/fixtures/ties.yml +0 -4
  347. data/test/fixtures/topics.yml +0 -42
  348. data/test/fixtures/toys.yml +0 -4
  349. data/test/fixtures/treasures.yml +0 -10
  350. data/test/fixtures/vertices.yml +0 -4
  351. data/test/fixtures/warehouse-things.yml +0 -3
  352. data/test/fixtures/zines.yml +0 -5
  353. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  354. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  355. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  356. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  357. data/test/migrations/duplicate/3_foo.rb +0 -7
  358. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  359. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  360. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  361. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  362. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  363. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  364. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  365. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  366. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  367. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  368. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  369. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  370. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  371. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  372. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  373. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  374. data/test/models/author.rb +0 -151
  375. data/test/models/auto_id.rb +0 -4
  376. data/test/models/binary.rb +0 -2
  377. data/test/models/bird.rb +0 -9
  378. data/test/models/book.rb +0 -4
  379. data/test/models/categorization.rb +0 -5
  380. data/test/models/category.rb +0 -34
  381. data/test/models/citation.rb +0 -6
  382. data/test/models/club.rb +0 -13
  383. data/test/models/column_name.rb +0 -3
  384. data/test/models/comment.rb +0 -29
  385. data/test/models/company.rb +0 -173
  386. data/test/models/company_in_module.rb +0 -78
  387. data/test/models/computer.rb +0 -3
  388. data/test/models/contact.rb +0 -16
  389. data/test/models/contract.rb +0 -5
  390. data/test/models/course.rb +0 -3
  391. data/test/models/customer.rb +0 -73
  392. data/test/models/default.rb +0 -2
  393. data/test/models/developer.rb +0 -101
  394. data/test/models/edge.rb +0 -5
  395. data/test/models/entrant.rb +0 -3
  396. data/test/models/essay.rb +0 -3
  397. data/test/models/event.rb +0 -3
  398. data/test/models/event_author.rb +0 -8
  399. data/test/models/face.rb +0 -7
  400. data/test/models/guid.rb +0 -2
  401. data/test/models/interest.rb +0 -5
  402. data/test/models/invoice.rb +0 -4
  403. data/test/models/item.rb +0 -7
  404. data/test/models/job.rb +0 -5
  405. data/test/models/joke.rb +0 -3
  406. data/test/models/keyboard.rb +0 -3
  407. data/test/models/legacy_thing.rb +0 -3
  408. data/test/models/line_item.rb +0 -3
  409. data/test/models/man.rb +0 -9
  410. data/test/models/matey.rb +0 -4
  411. data/test/models/member.rb +0 -12
  412. data/test/models/member_detail.rb +0 -5
  413. data/test/models/member_type.rb +0 -3
  414. data/test/models/membership.rb +0 -9
  415. data/test/models/minimalistic.rb +0 -2
  416. data/test/models/mixed_case_monkey.rb +0 -3
  417. data/test/models/movie.rb +0 -5
  418. data/test/models/order.rb +0 -4
  419. data/test/models/organization.rb +0 -6
  420. data/test/models/owner.rb +0 -5
  421. data/test/models/parrot.rb +0 -22
  422. data/test/models/person.rb +0 -16
  423. data/test/models/pet.rb +0 -5
  424. data/test/models/pirate.rb +0 -80
  425. data/test/models/polymorphic_design.rb +0 -3
  426. data/test/models/polymorphic_price.rb +0 -3
  427. data/test/models/post.rb +0 -102
  428. data/test/models/price_estimate.rb +0 -3
  429. data/test/models/project.rb +0 -30
  430. data/test/models/reader.rb +0 -4
  431. data/test/models/reference.rb +0 -4
  432. data/test/models/reply.rb +0 -46
  433. data/test/models/ship.rb +0 -19
  434. data/test/models/ship_part.rb +0 -7
  435. data/test/models/sponsor.rb +0 -4
  436. data/test/models/subject.rb +0 -4
  437. data/test/models/subscriber.rb +0 -8
  438. data/test/models/subscription.rb +0 -4
  439. data/test/models/tag.rb +0 -7
  440. data/test/models/tagging.rb +0 -10
  441. data/test/models/task.rb +0 -3
  442. data/test/models/tee.rb +0 -4
  443. data/test/models/tie.rb +0 -4
  444. data/test/models/topic.rb +0 -80
  445. data/test/models/toy.rb +0 -6
  446. data/test/models/treasure.rb +0 -8
  447. data/test/models/vertex.rb +0 -9
  448. data/test/models/warehouse_thing.rb +0 -5
  449. data/test/models/zine.rb +0 -3
  450. data/test/schema/mysql_specific_schema.rb +0 -31
  451. data/test/schema/postgresql_specific_schema.rb +0 -114
  452. data/test/schema/schema.rb +0 -550
  453. data/test/schema/schema2.rb +0 -6
  454. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -1,20 +1,11 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_record/connection_adapters/statement_pool'
4
+ require 'arel/visitors/bind_visitor'
2
5
 
3
- begin
4
- require_library_or_gem 'pg'
5
- rescue LoadError => e
6
- begin
7
- require_library_or_gem 'postgres'
8
- class PGresult
9
- alias_method :nfields, :num_fields unless self.method_defined?(:nfields)
10
- alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples)
11
- alias_method :ftype, :type unless self.method_defined?(:ftype)
12
- alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples)
13
- end
14
- rescue LoadError
15
- raise e
16
- end
17
- end
6
+ # Make sure we're using pg high enough for PGResult#values
7
+ gem 'pg', '~> 0.11'
8
+ require 'pg'
18
9
 
19
10
  module ActiveRecord
20
11
  class Base
@@ -26,7 +17,7 @@ module ActiveRecord
26
17
  username = config[:username].to_s if config[:username]
27
18
  password = config[:password].to_s if config[:password]
28
19
 
29
- if config.has_key?(:database)
20
+ if config.key?(:database)
30
21
  database = config[:database]
31
22
  else
32
23
  raise ArgumentError, "No database specified. Missing argument: database."
@@ -39,12 +30,6 @@ module ActiveRecord
39
30
  end
40
31
 
41
32
  module ConnectionAdapters
42
- class TableDefinition
43
- def xml(*args)
44
- options = args.extract_options!
45
- column(args[0], 'xml', options)
46
- end
47
- end
48
33
  # PostgreSQL-specific extensions to column definitions in a table.
49
34
  class PostgreSQLColumn < Column #:nodoc:
50
35
  # Instantiates a new PostgreSQL column definition in a table.
@@ -52,6 +37,22 @@ module ActiveRecord
52
37
  super(name, self.class.extract_value_from_default(default), sql_type, null)
53
38
  end
54
39
 
40
+ # :stopdoc:
41
+ class << self
42
+ attr_accessor :money_precision
43
+ def string_to_time(string)
44
+ return string unless String === string
45
+
46
+ case string
47
+ when 'infinity' then 1.0 / 0.0
48
+ when '-infinity' then -1.0 / 0.0
49
+ else
50
+ super
51
+ end
52
+ end
53
+ end
54
+ # :startdoc:
55
+
55
56
  private
56
57
  def extract_limit(sql_type)
57
58
  case sql_type
@@ -69,9 +70,11 @@ module ActiveRecord
69
70
 
70
71
  # Extracts the precision from PostgreSQL-specific data types.
71
72
  def extract_precision(sql_type)
72
- # Actual code is defined dynamically in PostgreSQLAdapter.connect
73
- # depending on the server specifics
74
- super
73
+ if sql_type == 'money'
74
+ self.class.money_precision
75
+ else
76
+ super
77
+ end
75
78
  end
76
79
 
77
80
  # Maps PostgreSQL-specific data types to logical Rails types.
@@ -81,18 +84,18 @@ module ActiveRecord
81
84
  when /^(?:real|double precision)$/
82
85
  :float
83
86
  # Monetary types
84
- when /^money$/
87
+ when 'money'
85
88
  :decimal
86
89
  # Character types
87
90
  when /^(?:character varying|bpchar)(?:\(\d+\))?$/
88
91
  :string
89
92
  # Binary data types
90
- when /^bytea$/
93
+ when 'bytea'
91
94
  :binary
92
95
  # Date/time types
93
96
  when /^timestamp with(?:out)? time zone$/
94
97
  :datetime
95
- when /^interval$/
98
+ when 'interval'
96
99
  :string
97
100
  # Geometric types
98
101
  when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
@@ -104,13 +107,22 @@ module ActiveRecord
104
107
  when /^bit(?: varying)?(?:\(\d+\))?$/
105
108
  :string
106
109
  # XML type
107
- when /^xml$/
110
+ when 'xml'
108
111
  :xml
112
+ # tsvector type
113
+ when 'tsvector'
114
+ :tsvector
109
115
  # Arrays
110
116
  when /^\D+\[\]$/
111
117
  :string
112
118
  # Object identifier types
113
- when /^oid$/
119
+ when 'oid'
120
+ :integer
121
+ # UUID type
122
+ when 'uuid'
123
+ :string
124
+ # Small and big integer types
125
+ when /^(?:small|big)int$/
114
126
  :integer
115
127
  # Pass through all types that are not specific to PostgreSQL.
116
128
  else
@@ -121,15 +133,20 @@ module ActiveRecord
121
133
  # Extracts the value from a PostgreSQL column default definition.
122
134
  def self.extract_value_from_default(default)
123
135
  case default
136
+ # This is a performance optimization for Ruby 1.9.2 in development.
137
+ # If the value is nil, we return nil straight away without checking
138
+ # the regular expressions. If we check each regular expression,
139
+ # Regexp#=== will call NilClass#to_str, which will trigger
140
+ # method_missing (defined by whiny nil in ActiveSupport) which
141
+ # makes this method very very slow.
142
+ when NilClass
143
+ nil
124
144
  # Numeric types
125
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
145
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
126
146
  $1
127
147
  # Character types
128
- when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
148
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
129
149
  $1
130
- # Character types (8.1 formatting)
131
- when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
132
- $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
133
150
  # Binary data types
134
151
  when /\A'(.*)'::bytea\z/m
135
152
  $1
@@ -168,9 +185,7 @@ module ActiveRecord
168
185
  end
169
186
  end
170
187
  end
171
- end
172
188
 
173
- module ConnectionAdapters
174
189
  # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
175
190
  # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
176
191
  #
@@ -181,15 +196,29 @@ module ActiveRecord
181
196
  # * <tt>:username</tt> - Defaults to nothing.
182
197
  # * <tt>:password</tt> - Defaults to nothing.
183
198
  # * <tt>:database</tt> - The name of the database. No default, must be provided.
184
- # * <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 <tt>:schema_order</tt> option.
185
- # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO <encoding></tt> call on the connection.
186
- # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
187
- # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
199
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
200
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
201
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
202
+ # <encoding></tt> call on the connection.
203
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
204
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
188
205
  class PostgreSQLAdapter < AbstractAdapter
189
- ADAPTER_NAME = 'PostgreSQL'.freeze
206
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
207
+ def xml(*args)
208
+ options = args.extract_options!
209
+ column(args[0], 'xml', options)
210
+ end
211
+
212
+ def tsvector(*args)
213
+ options = args.extract_options!
214
+ column(args[0], 'tsvector', options)
215
+ end
216
+ end
217
+
218
+ ADAPTER_NAME = 'PostgreSQL'
190
219
 
191
220
  NATIVE_DATABASE_TYPES = {
192
- :primary_key => "serial primary key".freeze,
221
+ :primary_key => "serial primary key",
193
222
  :string => { :name => "character varying", :limit => 255 },
194
223
  :text => { :name => "text" },
195
224
  :integer => { :name => "integer" },
@@ -201,7 +230,8 @@ module ActiveRecord
201
230
  :date => { :name => "date" },
202
231
  :binary => { :name => "bytea" },
203
232
  :boolean => { :name => "boolean" },
204
- :xml => { :name => "xml" }
233
+ :xml => { :name => "xml" },
234
+ :tsvector => { :name => "tsvector" }
205
235
  }
206
236
 
207
237
  # Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -209,41 +239,129 @@ module ActiveRecord
209
239
  ADAPTER_NAME
210
240
  end
211
241
 
242
+ # Returns +true+, since this connection adapter supports prepared statement
243
+ # caching.
244
+ def supports_statement_cache?
245
+ true
246
+ end
247
+
248
+ def supports_index_sort_order?
249
+ true
250
+ end
251
+
252
+ class StatementPool < ConnectionAdapters::StatementPool
253
+ def initialize(connection, max)
254
+ super
255
+ @counter = 0
256
+ @cache = Hash.new { |h,pid| h[pid] = {} }
257
+ end
258
+
259
+ def each(&block); cache.each(&block); end
260
+ def key?(key); cache.key?(key); end
261
+ def [](key); cache[key]; end
262
+ def length; cache.length; end
263
+
264
+ def next_key
265
+ "a#{@counter + 1}"
266
+ end
267
+
268
+ def []=(sql, key)
269
+ while @max <= cache.size
270
+ dealloc(cache.shift.last)
271
+ end
272
+ @counter += 1
273
+ cache[sql] = key
274
+ end
275
+
276
+ def clear
277
+ cache.each_value do |stmt_key|
278
+ dealloc stmt_key
279
+ end
280
+ cache.clear
281
+ end
282
+
283
+ def delete(sql_key)
284
+ dealloc cache[sql_key]
285
+ cache.delete sql_key
286
+ end
287
+
288
+ private
289
+ def cache
290
+ @cache[$$]
291
+ end
292
+
293
+ def dealloc(key)
294
+ @connection.query "DEALLOCATE #{key}" if connection_active?
295
+ end
296
+
297
+ def connection_active?
298
+ @connection.status == PGconn::CONNECTION_OK
299
+ rescue PGError
300
+ false
301
+ end
302
+ end
303
+
304
+ class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
305
+ include Arel::Visitors::BindVisitor
306
+ end
307
+
212
308
  # Initializes and connects a PostgreSQL adapter.
213
309
  def initialize(connection, logger, connection_parameters, config)
214
310
  super(connection, logger)
311
+
312
+ if config.fetch(:prepared_statements) { true }
313
+ @visitor = Arel::Visitors::PostgreSQL.new self
314
+ else
315
+ @visitor = BindSubstitution.new self
316
+ end
317
+
215
318
  @connection_parameters, @config = connection_parameters, config
216
319
 
320
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
321
+ @local_tz = nil
322
+ @table_alias_length = nil
323
+
217
324
  connect
325
+ @statements = StatementPool.new @connection,
326
+ config.fetch(:statement_limit) { 1000 }
327
+
328
+ if postgresql_version < 80200
329
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
330
+ end
331
+
332
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
333
+ end
334
+
335
+ # Clears the prepared statements cache.
336
+ def clear_cache!
337
+ @statements.clear
218
338
  end
219
339
 
220
340
  # Is this connection alive and ready for queries?
221
341
  def active?
222
- if @connection.respond_to?(:status)
223
- @connection.status == PGconn::CONNECTION_OK
224
- else
225
- # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
226
- @connection.query 'SELECT 1'
227
- true
228
- end
229
- # postgres-pr raises a NoMethodError when querying if no connection is available.
230
- rescue PGError, NoMethodError
342
+ @connection.query 'SELECT 1'
343
+ true
344
+ rescue PGError
231
345
  false
232
346
  end
233
347
 
234
348
  # Close then reopen the connection.
235
349
  def reconnect!
236
- if @connection.respond_to?(:reset)
237
- @connection.reset
238
- configure_connection
239
- else
240
- disconnect!
241
- connect
242
- end
350
+ clear_cache!
351
+ @connection.reset
352
+ @open_transactions = 0
353
+ configure_connection
243
354
  end
244
355
 
245
- # Close the connection.
356
+ def reset!
357
+ clear_cache!
358
+ super
359
+ end
360
+
361
+ # Disconnects from the database if already connected. Otherwise, this
362
+ # method does nothing.
246
363
  def disconnect!
364
+ clear_cache!
247
365
  @connection.close rescue nil
248
366
  end
249
367
 
@@ -251,12 +369,12 @@ module ActiveRecord
251
369
  NATIVE_DATABASE_TYPES
252
370
  end
253
371
 
254
- # Does PostgreSQL support migrations?
372
+ # Returns true, since this connection adapter supports migrations.
255
373
  def supports_migrations?
256
374
  true
257
375
  end
258
376
 
259
- # Does PostgreSQL support finding primary key on non-ActiveRecord tables?
377
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
260
378
  def supports_primary_key? #:nodoc:
261
379
  true
262
380
  end
@@ -264,126 +382,92 @@ module ActiveRecord
264
382
  # Enable standard-conforming strings if available.
265
383
  def set_standard_conforming_strings
266
384
  old, self.client_min_messages = client_min_messages, 'panic'
267
- execute('SET standard_conforming_strings = on') rescue nil
385
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
268
386
  ensure
269
387
  self.client_min_messages = old
270
388
  end
271
389
 
272
390
  def supports_insert_with_returning?
273
- postgresql_version >= 80200
391
+ true
274
392
  end
275
393
 
276
394
  def supports_ddl_transactions?
277
395
  true
278
396
  end
279
397
 
398
+ # Returns true, since this connection adapter supports savepoints.
280
399
  def supports_savepoints?
281
400
  true
282
401
  end
283
402
 
284
- # Returns the configured supported identifier length supported by PostgreSQL,
285
- # or report the default of 63 on PostgreSQL 7.x.
403
+ # Returns true.
404
+ def supports_explain?
405
+ true
406
+ end
407
+
408
+ # Returns the configured supported identifier length supported by PostgreSQL
286
409
  def table_alias_length
287
- @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
410
+ @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
288
411
  end
289
412
 
290
413
  # QUOTING ==================================================
291
414
 
292
415
  # Escapes binary strings for bytea input to the database.
293
- def escape_bytea(original_value)
294
- if @connection.respond_to?(:escape_bytea)
295
- self.class.instance_eval do
296
- define_method(:escape_bytea) do |value|
297
- @connection.escape_bytea(value) if value
298
- end
299
- end
300
- elsif PGconn.respond_to?(:escape_bytea)
301
- self.class.instance_eval do
302
- define_method(:escape_bytea) do |value|
303
- PGconn.escape_bytea(value) if value
304
- end
305
- end
306
- else
307
- self.class.instance_eval do
308
- define_method(:escape_bytea) do |value|
309
- if value
310
- result = ''
311
- value.each_byte { |c| result << sprintf('\\\\%03o', c) }
312
- result
313
- end
314
- end
315
- end
316
- end
317
- escape_bytea(original_value)
416
+ def escape_bytea(value)
417
+ @connection.escape_bytea(value) if value
318
418
  end
319
419
 
320
420
  # Unescapes bytea output from a database to the binary string it represents.
321
421
  # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
322
422
  # on escaped binary output from database drive.
323
- def unescape_bytea(original_value)
324
- # In each case, check if the value actually is escaped PostgreSQL bytea output
325
- # or an unescaped Active Record attribute that was just written.
326
- if @connection.respond_to?(:unescape_bytea)
327
- self.class.instance_eval do
328
- define_method(:unescape_bytea) do |value|
329
- @connection.unescape_bytea(value) if value
330
- end
331
- end
332
- elsif PGconn.respond_to?(:unescape_bytea)
333
- self.class.instance_eval do
334
- define_method(:unescape_bytea) do |value|
335
- PGconn.unescape_bytea(value) if value
336
- end
337
- end
338
- else
339
- raise 'Your PostgreSQL connection does not support unescape_bytea. Try upgrading to pg 0.9.0 or later.'
340
- end
341
- unescape_bytea(original_value)
423
+ def unescape_bytea(value)
424
+ @connection.unescape_bytea(value) if value
342
425
  end
343
426
 
344
427
  # Quotes PostgreSQL-specific data types for SQL input.
345
428
  def quote(value, column = nil) #:nodoc:
346
- if value.kind_of?(String) && column && column.type == :binary
347
- "'#{escape_bytea(value)}'"
348
- elsif value.kind_of?(String) && column && column.sql_type == 'xml'
349
- "xml '#{quote_string(value)}'"
350
- elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
429
+ return super unless column
430
+
431
+ case value
432
+ when Float
433
+ return super unless value.infinite? && column.type == :datetime
434
+ "'#{value.to_s.downcase}'"
435
+ when Numeric
436
+ return super unless column.sql_type == 'money'
351
437
  # Not truly string input, so doesn't require (or allow) escape string syntax.
352
- "'#{value.to_s}'"
353
- elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
354
- case value
355
- when /^[01]*$/
356
- "B'#{value}'" # Bit-string notation
357
- when /^[0-9A-F]*$/i
358
- "X'#{value}'" # Hexadecimal notation
438
+ "'#{value}'"
439
+ when String
440
+ case column.sql_type
441
+ when 'bytea' then "'#{escape_bytea(value)}'"
442
+ when 'xml' then "xml '#{quote_string(value)}'"
443
+ when /^bit/
444
+ case value
445
+ when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
446
+ when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
447
+ end
448
+ else
449
+ super
359
450
  end
360
451
  else
361
452
  super
362
453
  end
363
454
  end
364
455
 
365
- # Quotes strings for use in SQL input in the postgres driver for better performance.
366
- def quote_string(original_value) #:nodoc:
367
- if @connection.respond_to?(:escape)
368
- self.class.instance_eval do
369
- define_method(:quote_string) do |s|
370
- @connection.escape(s)
371
- end
372
- end
373
- elsif PGconn.respond_to?(:escape)
374
- self.class.instance_eval do
375
- define_method(:quote_string) do |s|
376
- PGconn.escape(s)
377
- end
378
- end
456
+ def type_cast(value, column)
457
+ return super unless column
458
+
459
+ case value
460
+ when String
461
+ return super unless 'bytea' == column.sql_type
462
+ { :value => value, :format => 1 }
379
463
  else
380
- # There are some incorrectly compiled postgres drivers out there
381
- # that don't define PGconn.escape.
382
- self.class.instance_eval do
383
- remove_method(:quote_string)
384
- end
464
+ super
385
465
  end
386
- quote_string(original_value)
466
+ end
467
+
468
+ # Quotes strings for use in SQL input.
469
+ def quote_string(s) #:nodoc:
470
+ @connection.escape(s)
387
471
  end
388
472
 
389
473
  # Checks the following cases:
@@ -420,28 +504,73 @@ module ActiveRecord
420
504
  end
421
505
  end
422
506
 
507
+ # Set the authorized user for this session
508
+ def session_auth=(user)
509
+ clear_cache!
510
+ exec_query "SET SESSION AUTHORIZATION #{user}"
511
+ end
512
+
423
513
  # REFERENTIAL INTEGRITY ====================================
424
514
 
425
- def supports_disable_referential_integrity?() #:nodoc:
426
- version = query("SHOW server_version")[0][0].split('.')
427
- (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
428
- rescue
429
- return false
515
+ def supports_disable_referential_integrity? #:nodoc:
516
+ true
430
517
  end
431
518
 
432
- def disable_referential_integrity(&block) #:nodoc:
433
- if supports_disable_referential_integrity?() then
519
+ def disable_referential_integrity #:nodoc:
520
+ if supports_disable_referential_integrity? then
434
521
  execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
435
522
  end
436
523
  yield
437
524
  ensure
438
- if supports_disable_referential_integrity?() then
525
+ if supports_disable_referential_integrity? then
439
526
  execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
440
527
  end
441
528
  end
442
529
 
443
530
  # DATABASE STATEMENTS ======================================
444
531
 
532
+ def explain(arel, binds = [])
533
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
534
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
535
+ end
536
+
537
+ class ExplainPrettyPrinter # :nodoc:
538
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
539
+ # PostgreSQL shell:
540
+ #
541
+ # QUERY PLAN
542
+ # ------------------------------------------------------------------------------
543
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
544
+ # Join Filter: (posts.user_id = users.id)
545
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
546
+ # Index Cond: (id = 1)
547
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
548
+ # Filter: (posts.user_id = 1)
549
+ # (6 rows)
550
+ #
551
+ def pp(result)
552
+ header = result.columns.first
553
+ lines = result.rows.map(&:first)
554
+
555
+ # We add 2 because there's one char of padding at both sides, note
556
+ # the extra hyphens in the example above.
557
+ width = [header, *lines].map(&:length).max + 2
558
+
559
+ pp = []
560
+
561
+ pp << header.center(width).rstrip
562
+ pp << '-' * width
563
+
564
+ pp += lines.map {|line| " #{line}"}
565
+
566
+ nrows = result.rows.length
567
+ rows_label = nrows == 1 ? 'row' : 'rows'
568
+ pp << "(#{nrows} #{rows_label})"
569
+
570
+ pp.join("\n") + "\n"
571
+ end
572
+ end
573
+
445
574
  # Executes a SELECT query and returns an array of rows. Each row is an
446
575
  # array of field values.
447
576
  def select_rows(sql, name = nil)
@@ -449,68 +578,68 @@ module ActiveRecord
449
578
  end
450
579
 
451
580
  # Executes an INSERT query and returns the new record's ID
452
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
453
- # Extract the table from the insert sql. Yuck.
454
- table = sql.split(" ", 4)[2].gsub('"', '')
455
-
456
- # Try an insert with 'returning id' if available (PG >= 8.2)
457
- if supports_insert_with_returning?
458
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
459
- if pk
460
- id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
461
- clear_query_cache
462
- return id
463
- end
581
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
582
+ unless pk
583
+ # Extract the table from the insert sql. Yuck.
584
+ table_ref = extract_table_ref_from_insert_sql(sql)
585
+ pk = primary_key(table_ref) if table_ref
464
586
  end
465
587
 
466
- # Otherwise, insert then grab last_insert_id.
467
- if insert_id = super
468
- insert_id
588
+ if pk
589
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
469
590
  else
470
- # If neither pk nor sequence name is given, look them up.
471
- unless pk || sequence_name
472
- pk, sequence_name = *pk_and_sequence_for(table)
473
- end
474
-
475
- # If a pk is given, fallback to default sequence name.
476
- # Don't fetch last insert id for a table without a pk.
477
- if pk && sequence_name ||= default_sequence_name(table, pk)
478
- last_insert_id(table, sequence_name)
479
- end
591
+ super
480
592
  end
481
593
  end
594
+ alias :create :insert
482
595
 
483
596
  # create a 2D array representing the result set
484
597
  def result_as_array(res) #:nodoc:
485
598
  # check if we have any binary column and if they need escaping
486
- unescape_col = []
487
- for j in 0...res.nfields do
488
- # unescape string passed BYTEA field (OID == 17)
489
- unescape_col << ( res.ftype(j)==17 )
599
+ ftypes = Array.new(res.nfields) do |i|
600
+ [i, res.ftype(i)]
490
601
  end
491
602
 
492
- ary = []
493
- for i in 0...res.ntuples do
494
- ary << []
495
- for j in 0...res.nfields do
496
- data = res.getvalue(i,j)
497
- data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
498
- ary[i] << data
603
+ rows = res.values
604
+ return rows unless ftypes.any? { |_, x|
605
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
606
+ }
607
+
608
+ typehash = ftypes.group_by { |_, type| type }
609
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
610
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
611
+
612
+ rows.each do |row|
613
+ # unescape string passed BYTEA field (OID == 17)
614
+ binaries.each do |index, _|
615
+ row[index] = unescape_bytea(row[index])
616
+ end
617
+
618
+ # If this is a money type column and there are any currency symbols,
619
+ # then strip them off. Indeed it would be prettier to do this in
620
+ # PostgreSQLColumn.string_to_decimal but would break form input
621
+ # fields that call value_before_type_cast.
622
+ monies.each do |index, _|
623
+ data = row[index]
624
+ # Because money output is formatted according to the locale, there are two
625
+ # cases to consider (note the decimal separators):
626
+ # (1) $12,345,678.12
627
+ # (2) $12.345.678,12
628
+ case data
629
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
630
+ data.gsub!(/[^-\d.]/, '')
631
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
632
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
633
+ end
499
634
  end
500
635
  end
501
- return ary
502
636
  end
503
637
 
504
638
 
505
639
  # Queries the database and returns the results in an Array-like object
506
640
  def query(sql, name = nil) #:nodoc:
507
641
  log(sql, name) do
508
- if @async
509
- res = @connection.async_exec(sql)
510
- else
511
- res = @connection.exec(sql)
512
- end
513
- return result_as_array(res)
642
+ result_as_array @connection.async_exec(sql)
514
643
  end
515
644
  end
516
645
 
@@ -518,14 +647,48 @@ module ActiveRecord
518
647
  # or raising a PGError exception otherwise.
519
648
  def execute(sql, name = nil)
520
649
  log(sql, name) do
521
- if @async
522
- @connection.async_exec(sql)
523
- else
524
- @connection.exec(sql)
525
- end
650
+ @connection.async_exec(sql)
526
651
  end
527
652
  end
528
653
 
654
+ def substitute_at(column, index)
655
+ Arel::Nodes::BindParam.new "$#{index + 1}"
656
+ end
657
+
658
+ def exec_query(sql, name = 'SQL', binds = [])
659
+ log(sql, name, binds) do
660
+ result = binds.empty? ? exec_no_cache(sql, binds) :
661
+ exec_cache(sql, binds)
662
+
663
+ ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
664
+ result.clear
665
+ return ret
666
+ end
667
+ end
668
+
669
+ def exec_delete(sql, name = 'SQL', binds = [])
670
+ log(sql, name, binds) do
671
+ result = binds.empty? ? exec_no_cache(sql, binds) :
672
+ exec_cache(sql, binds)
673
+ affected = result.cmd_tuples
674
+ result.clear
675
+ affected
676
+ end
677
+ end
678
+ alias :exec_update :exec_delete
679
+
680
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
681
+ unless pk
682
+ # Extract the table from the insert sql. Yuck.
683
+ table_ref = extract_table_ref_from_insert_sql(sql)
684
+ pk = primary_key(table_ref) if table_ref
685
+ end
686
+
687
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
688
+
689
+ [sql, binds]
690
+ end
691
+
529
692
  # Executes an UPDATE query and returns the number of affected tuples.
530
693
  def update_sql(sql, name = nil)
531
694
  super.cmd_tuples
@@ -546,12 +709,8 @@ module ActiveRecord
546
709
  execute "ROLLBACK"
547
710
  end
548
711
 
549
- if defined?(PGconn::PQTRANS_IDLE)
550
- # The ruby-pg driver supports inspecting the transaction status,
551
- # while the ruby-postgres driver does not.
552
- def outside_transaction?
553
- @connection.transaction_status == PGconn::PQTRANS_IDLE
554
- end
712
+ def outside_transaction?
713
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
555
714
  end
556
715
 
557
716
  def create_savepoint
@@ -568,12 +727,14 @@ module ActiveRecord
568
727
 
569
728
  # SCHEMA STATEMENTS ========================================
570
729
 
571
- def recreate_database(name) #:nodoc:
730
+ # Drops the database specified on the +name+ attribute
731
+ # and creates it again using the provided +options+.
732
+ def recreate_database(name, options = {}) #:nodoc:
572
733
  drop_database(name)
573
- create_database(name)
734
+ create_database(name, options)
574
735
  end
575
736
 
576
- # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
737
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
577
738
  # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
578
739
  # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
579
740
  #
@@ -603,88 +764,112 @@ module ActiveRecord
603
764
  execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
604
765
  end
605
766
 
606
- # Drops a PostgreSQL database
767
+ # Drops a PostgreSQL database.
607
768
  #
608
769
  # Example:
609
770
  # drop_database 'matt_development'
610
771
  def drop_database(name) #:nodoc:
611
- if postgresql_version >= 80200
612
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
613
- else
614
- begin
615
- execute "DROP DATABASE #{quote_table_name(name)}"
616
- rescue ActiveRecord::StatementInvalid
617
- @logger.warn "#{name} database doesn't exist." if @logger
618
- end
619
- end
772
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
620
773
  end
621
774
 
622
-
623
775
  # Returns the list of all tables in the schema search path or a specified schema.
624
776
  def tables(name = nil)
625
- schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
626
- query(<<-SQL, name).map { |row| row[0] }
777
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
627
778
  SELECT tablename
628
- FROM pg_tables
629
- WHERE schemaname IN (#{schemas})
779
+ FROM pg_tables
780
+ WHERE schemaname = ANY (current_schemas(false))
630
781
  SQL
631
782
  end
632
783
 
633
- # Returns the list of all indexes for a table.
784
+ # Returns true if table exists.
785
+ # If the schema is not specified as part of +name+ then it will only find tables within
786
+ # the current schema search path (regardless of permissions to access tables in other schemas)
787
+ def table_exists?(name)
788
+ schema, table = Utils.extract_schema_and_table(name.to_s)
789
+ return false unless table
790
+
791
+ binds = [[nil, table]]
792
+ binds << [nil, schema] if schema
793
+
794
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
795
+ SELECT COUNT(*)
796
+ FROM pg_class c
797
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
798
+ WHERE c.relkind in ('v','r')
799
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
800
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
801
+ SQL
802
+ end
803
+
804
+ # Returns true if schema exists.
805
+ def schema_exists?(name)
806
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
807
+ SELECT COUNT(*)
808
+ FROM pg_namespace
809
+ WHERE nspname = '#{name}'
810
+ SQL
811
+ end
812
+
813
+ # Returns an array of indexes for the given table.
634
814
  def indexes(table_name, name = nil)
635
- schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
636
- result = query(<<-SQL, name)
637
- SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
638
- FROM pg_class t, pg_class i, pg_index d
815
+ result = query(<<-SQL, 'SCHEMA')
816
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
817
+ FROM pg_class t
818
+ INNER JOIN pg_index d ON t.oid = d.indrelid
819
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
639
820
  WHERE i.relkind = 'i'
640
- AND d.indexrelid = i.oid
641
821
  AND d.indisprimary = 'f'
642
- AND t.oid = d.indrelid
643
822
  AND t.relname = '#{table_name}'
644
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
823
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
645
824
  ORDER BY i.relname
646
825
  SQL
647
826
 
648
827
 
649
- indexes = []
650
-
651
- indexes = result.map do |row|
828
+ result.map do |row|
652
829
  index_name = row[0]
653
830
  unique = row[1] == 't'
654
831
  indkey = row[2].split(" ")
655
- oid = row[3]
832
+ inddef = row[3]
833
+ oid = row[4]
656
834
 
657
- columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
658
- SELECT a.attname, a.attnum
835
+ columns = Hash[query(<<-SQL, "SCHEMA")]
836
+ SELECT a.attnum, a.attname
659
837
  FROM pg_attribute a
660
838
  WHERE a.attrelid = #{oid}
661
839
  AND a.attnum IN (#{indkey.join(",")})
662
840
  SQL
663
841
 
664
- column_names = indkey.map {|attnum| columns[attnum] }
665
- IndexDefinition.new(table_name, index_name, unique, column_names)
842
+ column_names = columns.values_at(*indkey).compact
666
843
 
667
- end
844
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
845
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
846
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
668
847
 
669
- indexes
848
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
849
+ end.compact
670
850
  end
671
851
 
672
852
  # Returns the list of all column definitions for a table.
673
853
  def columns(table_name, name = nil)
674
854
  # Limit, precision, and scale are all handled by the superclass.
675
- column_definitions(table_name).collect do |name, type, default, notnull|
676
- PostgreSQLColumn.new(name, default, type, notnull == 'f')
855
+ column_definitions(table_name).collect do |column_name, type, default, notnull|
856
+ PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
677
857
  end
678
858
  end
679
859
 
680
860
  # Returns the current database name.
681
861
  def current_database
682
- query('select current_database()')[0][0]
862
+ query('select current_database()', 'SCHEMA')[0][0]
863
+ end
864
+
865
+ # Returns the current schema name.
866
+ def current_schema
867
+ query('SELECT current_schema', 'SCHEMA')[0][0]
683
868
  end
684
869
 
685
870
  # Returns the current database encoding format.
686
871
  def encoding
687
- query(<<-end_sql)[0][0]
872
+ query(<<-end_sql, 'SCHEMA')[0][0]
688
873
  SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
689
874
  WHERE pg_database.datname LIKE '#{current_database}'
690
875
  end_sql
@@ -697,49 +882,59 @@ module ActiveRecord
697
882
  # This should be not be called manually but set in database.yml.
698
883
  def schema_search_path=(schema_csv)
699
884
  if schema_csv
700
- execute "SET search_path TO #{schema_csv}"
885
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
701
886
  @schema_search_path = schema_csv
702
887
  end
703
888
  end
704
889
 
705
890
  # Returns the active schema search path.
706
891
  def schema_search_path
707
- @schema_search_path ||= query('SHOW search_path')[0][0]
892
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
708
893
  end
709
894
 
710
895
  # Returns the current client message level.
711
896
  def client_min_messages
712
- query('SHOW client_min_messages')[0][0]
897
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
713
898
  end
714
899
 
715
900
  # Set the client message level.
716
901
  def client_min_messages=(level)
717
- execute("SET client_min_messages TO '#{level}'")
902
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
718
903
  end
719
904
 
720
905
  # Returns the sequence name for a table's primary key or some other specified key.
721
906
  def default_sequence_name(table_name, pk = nil) #:nodoc:
722
- default_pk, default_seq = pk_and_sequence_for(table_name)
723
- default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
907
+ serial_sequence(table_name, pk || 'id').split('.').last
908
+ rescue ActiveRecord::StatementInvalid
909
+ "#{table_name}_#{pk || 'id'}_seq"
910
+ end
911
+
912
+ def serial_sequence(table, column)
913
+ result = exec_query(<<-eosql, 'SCHEMA')
914
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
915
+ eosql
916
+ result.rows.first.first
724
917
  end
725
918
 
726
919
  # Resets the sequence of a table's primary key to the maximum value.
727
920
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
728
921
  unless pk and sequence
729
922
  default_pk, default_sequence = pk_and_sequence_for(table)
923
+
730
924
  pk ||= default_pk
731
925
  sequence ||= default_sequence
732
926
  end
733
- if pk
734
- if sequence
735
- quoted_sequence = quote_column_name(sequence)
736
927
 
737
- select_value <<-end_sql, 'Reset sequence'
738
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
739
- end_sql
740
- else
741
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
742
- end
928
+ if @logger && pk && !sequence
929
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
930
+ end
931
+
932
+ if pk && sequence
933
+ quoted_sequence = quote_table_name(sequence)
934
+
935
+ select_value <<-end_sql, 'SCHEMA'
936
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
937
+ end_sql
743
938
  end
744
939
  end
745
940
 
@@ -747,7 +942,7 @@ module ActiveRecord
747
942
  def pk_and_sequence_for(table) #:nodoc:
748
943
  # First try looking for a sequence with a dependency on the
749
944
  # given table's primary key.
750
- result = query(<<-end_sql, 'PK and serial sequence')[0]
945
+ result = query(<<-end_sql, 'SCHEMA')[0]
751
946
  SELECT attr.attname, seq.relname
752
947
  FROM pg_class seq,
753
948
  pg_attribute attr,
@@ -765,16 +960,13 @@ module ActiveRecord
765
960
  end_sql
766
961
 
767
962
  if result.nil? or result.empty?
768
- # If that fails, try parsing the primary key's default value.
769
- # Support the 7.x and 8.0 nextval('foo'::text) as well as
770
- # the 8.1+ nextval('foo'::regclass).
771
- result = query(<<-end_sql, 'PK and custom sequence')[0]
963
+ result = query(<<-end_sql, 'SCHEMA')[0]
772
964
  SELECT attr.attname,
773
965
  CASE
774
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
775
- substr(split_part(def.adsrc, '''', 2),
776
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
777
- ELSE split_part(def.adsrc, '''', 2)
966
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
967
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
968
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
969
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
778
970
  END
779
971
  FROM pg_class t
780
972
  JOIN pg_attribute attr ON (t.oid = attrelid)
@@ -782,11 +974,10 @@ module ActiveRecord
782
974
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
783
975
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
784
976
  AND cons.contype = 'p'
785
- AND def.adsrc ~* 'nextval'
977
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
786
978
  end_sql
787
979
  end
788
980
 
789
- # [primary_key, sequence]
790
981
  [result.first, result.last]
791
982
  rescue
792
983
  nil
@@ -794,49 +985,49 @@ module ActiveRecord
794
985
 
795
986
  # Returns just a table's primary key
796
987
  def primary_key(table)
797
- pk_and_sequence = pk_and_sequence_for(table)
798
- pk_and_sequence && pk_and_sequence.first
988
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
989
+ SELECT attr.attname
990
+ FROM pg_attribute attr
991
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
992
+ WHERE cons.contype = 'p'
993
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
994
+ end_sql
995
+
996
+ row && row.first
799
997
  end
800
998
 
801
999
  # Renames a table.
1000
+ # Also renames a table's primary key sequence if the sequence name matches the
1001
+ # Active Record default.
1002
+ #
1003
+ # Example:
1004
+ # rename_table('octopuses', 'octopi')
802
1005
  def rename_table(name, new_name)
1006
+ clear_cache!
803
1007
  execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
1008
+ pk, seq = pk_and_sequence_for(new_name)
1009
+ if seq == "#{name}_#{pk}_seq"
1010
+ new_seq = "#{new_name}_#{pk}_seq"
1011
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
1012
+ end
804
1013
  end
805
1014
 
806
1015
  # Adds a new column to the named table.
807
1016
  # See TableDefinition#column for details of the options you can use.
808
1017
  def add_column(table_name, column_name, type, options = {})
809
- default = options[:default]
810
- notnull = options[:null] == false
811
-
812
- # Add the column.
813
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
1018
+ clear_cache!
1019
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1020
+ add_column_options!(add_column_sql, options)
814
1021
 
815
- change_column_default(table_name, column_name, default) if options_include_default?(options)
816
- change_column_null(table_name, column_name, false, default) if notnull
1022
+ execute add_column_sql
817
1023
  end
818
1024
 
819
1025
  # Changes the column of a table.
820
1026
  def change_column(table_name, column_name, type, options = {})
1027
+ clear_cache!
821
1028
  quoted_table_name = quote_table_name(table_name)
822
1029
 
823
- begin
824
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
825
- rescue ActiveRecord::StatementInvalid => e
826
- raise e if postgresql_version > 80000
827
- # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
828
- begin
829
- begin_db_transaction
830
- tmp_column_name = "#{column_name}_ar_tmp"
831
- add_column(table_name, tmp_column_name, type, options)
832
- execute "UPDATE #{quoted_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])})"
833
- remove_column(table_name, column_name)
834
- rename_column(table_name, tmp_column_name, column_name)
835
- commit_db_transaction
836
- rescue
837
- rollback_db_transaction
838
- end
839
- end
1030
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
840
1031
 
841
1032
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
842
1033
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -844,10 +1035,12 @@ module ActiveRecord
844
1035
 
845
1036
  # Changes the default value of a table column.
846
1037
  def change_column_default(table_name, column_name, default)
1038
+ clear_cache!
847
1039
  execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
848
1040
  end
849
1041
 
850
1042
  def change_column_null(table_name, column_name, null, default = nil)
1043
+ clear_cache!
851
1044
  unless null || default.nil?
852
1045
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
853
1046
  end
@@ -856,6 +1049,7 @@ module ActiveRecord
856
1049
 
857
1050
  # Renames a column in a table.
858
1051
  def rename_column(table_name, column_name, new_column_name)
1052
+ clear_cache!
859
1053
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
860
1054
  end
861
1055
 
@@ -863,19 +1057,42 @@ module ActiveRecord
863
1057
  execute "DROP INDEX #{quote_table_name(index_name)}"
864
1058
  end
865
1059
 
1060
+ def rename_index(table_name, old_name, new_name)
1061
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
1062
+ end
1063
+
866
1064
  def index_name_length
867
1065
  63
868
1066
  end
869
1067
 
870
1068
  # Maps logical Rails types to PostgreSQL-specific data types.
871
1069
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
872
- return super unless type.to_s == 'integer'
873
-
874
- case limit
875
- when 1..2; 'smallint'
876
- when 3..4, nil; 'integer'
877
- when 5..8; 'bigint'
878
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1070
+ case type.to_s
1071
+ when 'binary'
1072
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
1073
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
1074
+ case limit
1075
+ when nil, 0..0x3fffffff; super(type)
1076
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
1077
+ end
1078
+ when 'text'
1079
+ # PostgreSQL doesn't support limits on text columns.
1080
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
1081
+ case limit
1082
+ when nil, 0..0x3fffffff; super(type)
1083
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
1084
+ end
1085
+ when 'integer'
1086
+ return 'integer' unless limit
1087
+
1088
+ case limit
1089
+ when 1, 2; 'smallint'
1090
+ when 3, 4; 'integer'
1091
+ when 5..8; 'bigint'
1092
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1093
+ end
1094
+ else
1095
+ super
879
1096
  end
880
1097
  end
881
1098
 
@@ -885,153 +1102,172 @@ module ActiveRecord
885
1102
  # requires that the ORDER BY include the distinct column.
886
1103
  #
887
1104
  # distinct("posts.id", "posts.created_at desc")
888
- def distinct(columns, order_by) #:nodoc:
889
- return "DISTINCT #{columns}" if order_by.blank?
1105
+ def distinct(columns, orders) #:nodoc:
1106
+ return "DISTINCT #{columns}" if orders.empty?
890
1107
 
891
1108
  # Construct a clean list of column names from the ORDER BY clause, removing
892
1109
  # any ASC/DESC modifiers
893
- order_columns = order_by.split(',').collect { |s| s.split.first }
894
- order_columns.delete_if &:blank?
1110
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
1111
+ order_columns.delete_if { |c| c.blank? }
895
1112
  order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
896
1113
 
897
- # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
898
- # all the required columns for the ORDER BY to work properly.
899
- sql = "DISTINCT ON (#{columns}) #{columns}, "
900
- sql << order_columns * ', '
1114
+ "DISTINCT #{columns}, #{order_columns * ', '}"
901
1115
  end
902
1116
 
903
- # Returns an ORDER BY clause for the passed order option.
904
- #
905
- # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
906
- # by wrapping the +sql+ string as a sub-select and ordering in that query.
907
- def add_order_by_for_association_limiting!(sql, options) #:nodoc:
908
- return sql if options[:order].blank?
909
-
910
- order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
911
- order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
912
- order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
1117
+ module Utils
1118
+ extend self
913
1119
 
914
- sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
1120
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1121
+ # +schema_name+ is nil if not specified in +name+.
1122
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1123
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
1124
+ #
1125
+ # * <tt>table_name</tt>
1126
+ # * <tt>"table.name"</tt>
1127
+ # * <tt>schema_name.table_name</tt>
1128
+ # * <tt>schema_name."table.name"</tt>
1129
+ # * <tt>"schema.name"."table name"</tt>
1130
+ def extract_schema_and_table(name)
1131
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1132
+ [schema, table]
1133
+ end
915
1134
  end
916
1135
 
917
1136
  protected
918
- # Returns the version of the connected PostgreSQL version.
1137
+ # Returns the version of the connected PostgreSQL server.
919
1138
  def postgresql_version
920
- @postgresql_version ||=
921
- if @connection.respond_to?(:server_version)
922
- @connection.server_version
1139
+ @connection.server_version
1140
+ end
1141
+
1142
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
1143
+ FOREIGN_KEY_VIOLATION = "23503"
1144
+ UNIQUE_VIOLATION = "23505"
1145
+
1146
+ def translate_exception(exception, message)
1147
+ return exception unless exception.respond_to?(:result)
1148
+
1149
+ case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
1150
+ when UNIQUE_VIOLATION
1151
+ RecordNotUnique.new(message, exception)
1152
+ when FOREIGN_KEY_VIOLATION
1153
+ InvalidForeignKey.new(message, exception)
1154
+ else
1155
+ super
1156
+ end
1157
+ end
1158
+
1159
+ private
1160
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1161
+
1162
+ def exec_no_cache(sql, binds)
1163
+ @connection.async_exec(sql, [])
1164
+ end
1165
+
1166
+ def exec_cache(sql, binds)
1167
+ begin
1168
+ stmt_key = prepare_statement sql
1169
+
1170
+ # Clear the queue
1171
+ @connection.get_last_result
1172
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
1173
+ type_cast(val, col)
1174
+ })
1175
+ @connection.block
1176
+ @connection.get_last_result
1177
+ rescue PGError => e
1178
+ # Get the PG code for the failure. Annoyingly, the code for
1179
+ # prepared statements whose return value may have changed is
1180
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
1181
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1182
+ begin
1183
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
1184
+ rescue
1185
+ raise e
1186
+ end
1187
+ if FEATURE_NOT_SUPPORTED == code
1188
+ @statements.delete sql_key(sql)
1189
+ retry
923
1190
  else
924
- # Mimic PGconn.server_version behavior
925
- begin
926
- query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
927
- ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
928
- rescue
929
- 0
930
- end
1191
+ raise e
931
1192
  end
1193
+ end
1194
+ end
1195
+
1196
+ # Returns the statement identifier for the client side cache
1197
+ # of statements
1198
+ def sql_key(sql)
1199
+ "#{schema_search_path}-#{sql}"
1200
+ end
1201
+
1202
+ # Prepare the statement if it hasn't been prepared, return
1203
+ # the statement key.
1204
+ def prepare_statement(sql)
1205
+ sql_key = sql_key(sql)
1206
+ unless @statements.key? sql_key
1207
+ nextkey = @statements.next_key
1208
+ @connection.prepare nextkey, sql
1209
+ @statements[sql_key] = nextkey
1210
+ end
1211
+ @statements[sql_key]
932
1212
  end
933
1213
 
934
- private
935
1214
  # The internal PostgreSQL identifier of the money data type.
936
1215
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
1216
+ # The internal PostgreSQL identifier of the BYTEA data type.
1217
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
937
1218
 
938
1219
  # Connects to a PostgreSQL server and sets up the adapter depending on the
939
1220
  # connected server's characteristics.
940
1221
  def connect
941
1222
  @connection = PGconn.connect(*@connection_parameters)
942
- PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
943
-
944
- # Ignore async_exec and async_query when using postgres-pr.
945
- @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
946
1223
 
947
1224
  # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
948
1225
  # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
949
1226
  # should know about this but can't detect it there, so deal with it here.
950
- money_precision = (postgresql_version >= 80300) ? 19 : 10
951
- PostgreSQLColumn.module_eval(<<-end_eval)
952
- def extract_precision(sql_type) # def extract_precision(sql_type)
953
- if sql_type =~ /^money$/ # if sql_type =~ /^money$/
954
- #{money_precision} # 19
955
- else # else
956
- super # super
957
- end # end
958
- end # end
959
- end_eval
1227
+ PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
960
1228
 
961
1229
  configure_connection
962
1230
  end
963
1231
 
964
- # Configures the encoding, verbosity, and schema search path of the connection.
1232
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
965
1233
  # This is called by #connect and should not be called manually.
966
1234
  def configure_connection
967
1235
  if @config[:encoding]
968
- if @connection.respond_to?(:set_client_encoding)
969
- @connection.set_client_encoding(@config[:encoding])
970
- else
971
- execute("SET client_encoding TO '#{@config[:encoding]}'")
972
- end
1236
+ @connection.set_client_encoding(@config[:encoding])
973
1237
  end
974
1238
  self.client_min_messages = @config[:min_messages] if @config[:min_messages]
975
1239
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
976
1240
 
977
1241
  # Use standard-conforming strings if available so we don't have to do the E'...' dance.
978
1242
  set_standard_conforming_strings
1243
+
1244
+ # If using Active Record's time zone support configure the connection to return
1245
+ # TIMESTAMP WITH ZONE types in UTC.
1246
+ if ActiveRecord::Base.default_timezone == :utc
1247
+ execute("SET time zone 'UTC'", 'SCHEMA')
1248
+ elsif @local_tz
1249
+ execute("SET time zone '#{@local_tz}'", 'SCHEMA')
1250
+ end
979
1251
  end
980
1252
 
981
1253
  # Returns the current ID of a table's sequence.
982
- def last_insert_id(table, sequence_name) #:nodoc:
983
- Integer(select_value("SELECT currval('#{sequence_name}')"))
1254
+ def last_insert_id(sequence_name) #:nodoc:
1255
+ r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1256
+ Integer(r.rows.first.first)
984
1257
  end
985
1258
 
986
1259
  # Executes a SELECT query and returns the results, performing any data type
987
1260
  # conversions that are required to be performed here instead of in PostgreSQLColumn.
988
- def select(sql, name = nil)
989
- fields, rows = select_raw(sql, name)
990
- result = []
991
- for row in rows
992
- row_hash = {}
993
- fields.each_with_index do |f, i|
994
- row_hash[f] = row[i]
995
- end
996
- result << row_hash
997
- end
998
- result
1261
+ def select(sql, name = nil, binds = [])
1262
+ exec_query(sql, name, binds).to_a
999
1263
  end
1000
1264
 
1001
1265
  def select_raw(sql, name = nil)
1002
1266
  res = execute(sql, name)
1003
1267
  results = result_as_array(res)
1004
- fields = []
1005
- rows = []
1006
- if res.ntuples > 0
1007
- fields = res.fields
1008
- results.each do |row|
1009
- hashed_row = {}
1010
- row.each_index do |cell_index|
1011
- # If this is a money type column and there are any currency symbols,
1012
- # then strip them off. Indeed it would be prettier to do this in
1013
- # PostgreSQLColumn.string_to_decimal but would break form input
1014
- # fields that call value_before_type_cast.
1015
- if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
1016
- # Because money output is formatted according to the locale, there are two
1017
- # cases to consider (note the decimal separators):
1018
- # (1) $12,345,678.12
1019
- # (2) $12.345.678,12
1020
- case column = row[cell_index]
1021
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
1022
- row[cell_index] = column.gsub(/[^-\d\.]/, '')
1023
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
1024
- row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
1025
- end
1026
- end
1027
-
1028
- hashed_row[fields[cell_index]] = column
1029
- end
1030
- rows << row
1031
- end
1032
- end
1268
+ fields = res.fields
1033
1269
  res.clear
1034
- return fields, rows
1270
+ return fields, results
1035
1271
  end
1036
1272
 
1037
1273
  # Returns the list of a table's column names, data types, and default values.
@@ -1053,8 +1289,9 @@ module ActiveRecord
1053
1289
  # - format_type includes the column size constraint, e.g. varchar(50)
1054
1290
  # - ::regclass is a function that gives the id for a table name
1055
1291
  def column_definitions(table_name) #:nodoc:
1056
- query <<-end_sql
1057
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1292
+ exec_query(<<-end_sql, 'SCHEMA').rows
1293
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1294
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
1058
1295
  FROM pg_attribute a LEFT JOIN pg_attrdef d
1059
1296
  ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1060
1297
  WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -1064,15 +1301,23 @@ module ActiveRecord
1064
1301
  end
1065
1302
 
1066
1303
  def extract_pg_identifier_from_name(name)
1067
- match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1304
+ match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1068
1305
 
1069
1306
  if match_data
1070
- rest = name[match_data[0].length..-1]
1071
- rest = rest[1..-1] if rest[0,1] == "."
1307
+ rest = name[match_data[0].length, name.length]
1308
+ rest = rest[1, rest.length] if rest.start_with? "."
1072
1309
  [match_data[1], (rest.length > 0 ? rest : nil)]
1073
1310
  end
1074
1311
  end
1312
+
1313
+ def extract_table_ref_from_insert_sql(sql)
1314
+ sql[/into\s+([^\(]*).*values\s*\(/i]
1315
+ $1.strip if $1
1316
+ end
1317
+
1318
+ def table_definition
1319
+ TableDefinition.new(self)
1320
+ end
1075
1321
  end
1076
1322
  end
1077
1323
  end
1078
-