activerecord 1.0.0 → 4.0.0

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

Potentially problematic release.


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

Files changed (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,97 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLColumn < Column
4
+ module ArrayParser
5
+ private
6
+ # Loads pg_array_parser if available. String parsing can be
7
+ # performed quicker by a native extension, which will not create
8
+ # a large amount of Ruby objects that will need to be garbage
9
+ # collected. pg_array_parser has a C and Java extension
10
+ begin
11
+ require 'pg_array_parser'
12
+ include PgArrayParser
13
+ rescue LoadError
14
+ def parse_pg_array(string)
15
+ parse_data(string, 0)
16
+ end
17
+ end
18
+
19
+ def parse_data(string, index)
20
+ local_index = index
21
+ array = []
22
+ while(local_index < string.length)
23
+ case string[local_index]
24
+ when '{'
25
+ local_index,array = parse_array_contents(array, string, local_index + 1)
26
+ when '}'
27
+ return array
28
+ end
29
+ local_index += 1
30
+ end
31
+
32
+ array
33
+ end
34
+
35
+ def parse_array_contents(array, string, index)
36
+ is_escaping = false
37
+ is_quoted = false
38
+ was_quoted = false
39
+ current_item = ''
40
+
41
+ local_index = index
42
+ while local_index
43
+ token = string[local_index]
44
+ if is_escaping
45
+ current_item << token
46
+ is_escaping = false
47
+ else
48
+ if is_quoted
49
+ case token
50
+ when '"'
51
+ is_quoted = false
52
+ was_quoted = true
53
+ when "\\"
54
+ is_escaping = true
55
+ else
56
+ current_item << token
57
+ end
58
+ else
59
+ case token
60
+ when "\\"
61
+ is_escaping = true
62
+ when ','
63
+ add_item_to_array(array, current_item, was_quoted)
64
+ current_item = ''
65
+ was_quoted = false
66
+ when '"'
67
+ is_quoted = true
68
+ when '{'
69
+ internal_items = []
70
+ local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
71
+ array.push(internal_items)
72
+ when '}'
73
+ add_item_to_array(array, current_item, was_quoted)
74
+ return local_index,array
75
+ else
76
+ current_item << token
77
+ end
78
+ end
79
+ end
80
+
81
+ local_index += 1
82
+ end
83
+ return local_index,array
84
+ end
85
+
86
+ def add_item_to_array(array, current_item, quoted)
87
+ if current_item.length == 0
88
+ elsif !quoted && current_item == 'NULL'
89
+ array.push nil
90
+ else
91
+ array.push current_item
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,152 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLColumn < Column
4
+ module Cast
5
+ def point_to_string(point)
6
+ "(#{point[0]},#{point[1]})"
7
+ end
8
+
9
+ def string_to_point(string)
10
+ if string[0] == '(' && string[-1] == ')'
11
+ string = string[1...-1]
12
+ end
13
+ string.split(',').map{ |v| Float(v) }
14
+ end
15
+
16
+ def string_to_time(string)
17
+ return string unless String === string
18
+
19
+ case string
20
+ when 'infinity'; 1.0 / 0.0
21
+ when '-infinity'; -1.0 / 0.0
22
+ when / BC$/
23
+ super("-" + string.sub(/ BC$/, ""))
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def string_to_bit(value)
30
+ case value
31
+ when /^0x/i
32
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
33
+ else
34
+ value # Bit-string notation
35
+ end
36
+ end
37
+
38
+ def hstore_to_string(object)
39
+ if Hash === object
40
+ object.map { |k,v|
41
+ "#{escape_hstore(k)}=>#{escape_hstore(v)}"
42
+ }.join ','
43
+ else
44
+ object
45
+ end
46
+ end
47
+
48
+ def string_to_hstore(string)
49
+ if string.nil?
50
+ nil
51
+ elsif String === string
52
+ Hash[string.scan(HstorePair).map { |k,v|
53
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
54
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
55
+ [k,v]
56
+ }]
57
+ else
58
+ string
59
+ end
60
+ end
61
+
62
+ def json_to_string(object)
63
+ if Hash === object
64
+ ActiveSupport::JSON.encode(object)
65
+ else
66
+ object
67
+ end
68
+ end
69
+
70
+ def array_to_string(value, column, adapter, should_be_quoted = false)
71
+ casted_values = value.map do |val|
72
+ if String === val
73
+ if val == "NULL"
74
+ "\"#{val}\""
75
+ else
76
+ quote_and_escape(adapter.type_cast(val, column, true))
77
+ end
78
+ else
79
+ adapter.type_cast(val, column, true)
80
+ end
81
+ end
82
+ "{#{casted_values.join(',')}}"
83
+ end
84
+
85
+ def range_to_string(object)
86
+ from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
87
+ to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
88
+ "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
89
+ end
90
+
91
+ def string_to_json(string)
92
+ if String === string
93
+ ActiveSupport::JSON.decode(string)
94
+ else
95
+ string
96
+ end
97
+ end
98
+
99
+ def string_to_cidr(string)
100
+ if string.nil?
101
+ nil
102
+ elsif String === string
103
+ IPAddr.new(string)
104
+ else
105
+ string
106
+ end
107
+ end
108
+
109
+ def cidr_to_string(object)
110
+ if IPAddr === object
111
+ "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
112
+ else
113
+ object
114
+ end
115
+ end
116
+
117
+ def string_to_array(string, oid)
118
+ parse_pg_array(string).map{|val| oid.type_cast val}
119
+ end
120
+
121
+ private
122
+
123
+ HstorePair = begin
124
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
125
+ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
126
+ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
127
+ end
128
+
129
+ def escape_hstore(value)
130
+ if value.nil?
131
+ 'NULL'
132
+ else
133
+ if value == ""
134
+ '""'
135
+ else
136
+ '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
137
+ end
138
+ end
139
+ end
140
+
141
+ def quote_and_escape(value)
142
+ case value
143
+ when "NULL"
144
+ value
145
+ else
146
+ "\"#{value.gsub(/"/,"\\\"")}\""
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,242 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ module DatabaseStatements
5
+ def explain(arel, binds = [])
6
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
7
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
8
+ end
9
+
10
+ class ExplainPrettyPrinter # :nodoc:
11
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
12
+ # PostgreSQL shell:
13
+ #
14
+ # QUERY PLAN
15
+ # ------------------------------------------------------------------------------
16
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
17
+ # Join Filter: (posts.user_id = users.id)
18
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
19
+ # Index Cond: (id = 1)
20
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
21
+ # Filter: (posts.user_id = 1)
22
+ # (6 rows)
23
+ #
24
+ def pp(result)
25
+ header = result.columns.first
26
+ lines = result.rows.map(&:first)
27
+
28
+ # We add 2 because there's one char of padding at both sides, note
29
+ # the extra hyphens in the example above.
30
+ width = [header, *lines].map(&:length).max + 2
31
+
32
+ pp = []
33
+
34
+ pp << header.center(width).rstrip
35
+ pp << '-' * width
36
+
37
+ pp += lines.map {|line| " #{line}"}
38
+
39
+ nrows = result.rows.length
40
+ rows_label = nrows == 1 ? 'row' : 'rows'
41
+ pp << "(#{nrows} #{rows_label})"
42
+
43
+ pp.join("\n") + "\n"
44
+ end
45
+ end
46
+
47
+ # Executes a SELECT query and returns an array of rows. Each row is an
48
+ # array of field values.
49
+ def select_rows(sql, name = nil)
50
+ select_raw(sql, name).last
51
+ end
52
+
53
+ # Executes an INSERT query and returns the new record's ID
54
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
55
+ unless pk
56
+ # Extract the table from the insert sql. Yuck.
57
+ table_ref = extract_table_ref_from_insert_sql(sql)
58
+ pk = primary_key(table_ref) if table_ref
59
+ end
60
+
61
+ if pk && use_insert_returning?
62
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
63
+ elsif pk
64
+ super
65
+ last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
66
+ else
67
+ super
68
+ end
69
+ end
70
+
71
+ def create
72
+ super.insert
73
+ end
74
+
75
+ # create a 2D array representing the result set
76
+ def result_as_array(res) #:nodoc:
77
+ # check if we have any binary column and if they need escaping
78
+ ftypes = Array.new(res.nfields) do |i|
79
+ [i, res.ftype(i)]
80
+ end
81
+
82
+ rows = res.values
83
+ return rows unless ftypes.any? { |_, x|
84
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
85
+ }
86
+
87
+ typehash = ftypes.group_by { |_, type| type }
88
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
89
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
90
+
91
+ rows.each do |row|
92
+ # unescape string passed BYTEA field (OID == 17)
93
+ binaries.each do |index, _|
94
+ row[index] = unescape_bytea(row[index])
95
+ end
96
+
97
+ # If this is a money type column and there are any currency symbols,
98
+ # then strip them off. Indeed it would be prettier to do this in
99
+ # PostgreSQLColumn.string_to_decimal but would break form input
100
+ # fields that call value_before_type_cast.
101
+ monies.each do |index, _|
102
+ data = row[index]
103
+ # Because money output is formatted according to the locale, there are two
104
+ # cases to consider (note the decimal separators):
105
+ # (1) $12,345,678.12
106
+ # (2) $12.345.678,12
107
+ case data
108
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
109
+ data.gsub!(/[^-\d.]/, '')
110
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
111
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ # Queries the database and returns the results in an Array-like object
118
+ def query(sql, name = nil) #:nodoc:
119
+ log(sql, name) do
120
+ result_as_array @connection.async_exec(sql)
121
+ end
122
+ end
123
+
124
+ # Executes an SQL statement, returning a PGresult object on success
125
+ # or raising a PGError exception otherwise.
126
+ def execute(sql, name = nil)
127
+ log(sql, name) do
128
+ @connection.async_exec(sql)
129
+ end
130
+ end
131
+
132
+ def substitute_at(column, index)
133
+ Arel::Nodes::BindParam.new "$#{index + 1}"
134
+ end
135
+
136
+ def exec_query(sql, name = 'SQL', binds = [])
137
+ log(sql, name, binds) do
138
+ result = binds.empty? ? exec_no_cache(sql, binds) :
139
+ exec_cache(sql, binds)
140
+
141
+ types = {}
142
+ result.fields.each_with_index do |fname, i|
143
+ ftype = result.ftype i
144
+ fmod = result.fmod i
145
+ types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
146
+ warn "unknown OID: #{fname}(#{oid}) (#{sql})"
147
+ OID::Identity.new
148
+ }
149
+ end
150
+
151
+ ret = ActiveRecord::Result.new(result.fields, result.values, types)
152
+ result.clear
153
+ return ret
154
+ end
155
+ end
156
+
157
+ def exec_delete(sql, name = 'SQL', binds = [])
158
+ log(sql, name, binds) do
159
+ result = binds.empty? ? exec_no_cache(sql, binds) :
160
+ exec_cache(sql, binds)
161
+ affected = result.cmd_tuples
162
+ result.clear
163
+ affected
164
+ end
165
+ end
166
+ alias :exec_update :exec_delete
167
+
168
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
169
+ unless pk
170
+ # Extract the table from the insert sql. Yuck.
171
+ table_ref = extract_table_ref_from_insert_sql(sql)
172
+ pk = primary_key(table_ref) if table_ref
173
+ end
174
+
175
+ if pk && use_insert_returning?
176
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}"
177
+ end
178
+
179
+ [sql, binds]
180
+ end
181
+
182
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
183
+ val = exec_query(sql, name, binds)
184
+ if !use_insert_returning? && pk
185
+ unless sequence_name
186
+ table_ref = extract_table_ref_from_insert_sql(sql)
187
+ sequence_name = default_sequence_name(table_ref, pk)
188
+ return val unless sequence_name
189
+ end
190
+ last_insert_id_result(sequence_name)
191
+ else
192
+ val
193
+ end
194
+ end
195
+
196
+ # Executes an UPDATE query and returns the number of affected tuples.
197
+ def update_sql(sql, name = nil)
198
+ super.cmd_tuples
199
+ end
200
+
201
+ # Begins a transaction.
202
+ def begin_db_transaction
203
+ execute "BEGIN"
204
+ end
205
+
206
+ def begin_isolated_db_transaction(isolation)
207
+ begin_db_transaction
208
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
209
+ end
210
+
211
+ # Commits a transaction.
212
+ def commit_db_transaction
213
+ execute "COMMIT"
214
+ end
215
+
216
+ # Aborts a transaction.
217
+ def rollback_db_transaction
218
+ execute "ROLLBACK"
219
+ end
220
+
221
+ def outside_transaction?
222
+ message = "#outside_transaction? is deprecated. This method was only really used " \
223
+ "internally, but you can use #transaction_open? instead."
224
+ ActiveSupport::Deprecation.warn message
225
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
226
+ end
227
+
228
+ def create_savepoint
229
+ execute("SAVEPOINT #{current_savepoint_name}")
230
+ end
231
+
232
+ def rollback_to_savepoint
233
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
234
+ end
235
+
236
+ def release_savepoint
237
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end