sskirby-activerecord 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,426 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ gem 'mysql', '~> 2.8.1'
6
+ require 'mysql'
7
+
8
+ class Mysql
9
+ class Time
10
+ ###
11
+ # This monkey patch is for test_additional_columns_from_join_table
12
+ def to_date
13
+ Date.new(year, month, day)
14
+ end
15
+ end
16
+ class Stmt; include Enumerable end
17
+ class Result; include Enumerable end
18
+ end
19
+
20
+ module ActiveRecord
21
+ class Base
22
+ # Establishes a connection to the database that's used by all Active Record objects.
23
+ def self.mysql_connection(config) # :nodoc:
24
+ config = config.symbolize_keys
25
+ host = config[:host]
26
+ port = config[:port]
27
+ socket = config[:socket]
28
+ username = config[:username] ? config[:username].to_s : 'root'
29
+ password = config[:password].to_s
30
+ database = config[:database]
31
+
32
+ mysql = Mysql.init
33
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
34
+
35
+ default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
36
+ default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
37
+ options = [host, username, password, database, port, socket, default_flags]
38
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
39
+ end
40
+ end
41
+
42
+ module ConnectionAdapters
43
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
44
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
45
+ #
46
+ # Options:
47
+ #
48
+ # * <tt>:host</tt> - Defaults to "localhost".
49
+ # * <tt>:port</tt> - Defaults to 3306.
50
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
51
+ # * <tt>:username</tt> - Defaults to "root"
52
+ # * <tt>:password</tt> - Defaults to nothing.
53
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
54
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
55
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
56
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
57
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
58
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
59
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
60
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
61
+ #
62
+ class MysqlAdapter < AbstractMysqlAdapter
63
+
64
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
65
+ def self.string_to_time(value)
66
+ return super unless Mysql::Time === value
67
+ new_time(
68
+ value.year,
69
+ value.month,
70
+ value.day,
71
+ value.hour,
72
+ value.minute,
73
+ value.second,
74
+ value.second_part)
75
+ end
76
+
77
+ def self.string_to_dummy_time(v)
78
+ return super unless Mysql::Time === v
79
+ new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
80
+ end
81
+
82
+ def self.string_to_date(v)
83
+ return super unless Mysql::Time === v
84
+ new_date(v.year, v.month, v.day)
85
+ end
86
+
87
+ def adapter
88
+ MysqlAdapter
89
+ end
90
+ end
91
+
92
+ ADAPTER_NAME = 'MySQL'
93
+
94
+ class StatementPool < ConnectionAdapters::StatementPool
95
+ def initialize(connection, max = 1000)
96
+ super
97
+ @cache = Hash.new { |h,pid| h[pid] = {} }
98
+ end
99
+
100
+ def each(&block); cache.each(&block); end
101
+ def key?(key); cache.key?(key); end
102
+ def [](key); cache[key]; end
103
+ def length; cache.length; end
104
+ def delete(key); cache.delete(key); end
105
+
106
+ def []=(sql, key)
107
+ while @max <= cache.size
108
+ cache.shift.last[:stmt].close
109
+ end
110
+ cache[sql] = key
111
+ end
112
+
113
+ def clear
114
+ cache.values.each do |hash|
115
+ hash[:stmt].close
116
+ end
117
+ cache.clear
118
+ end
119
+
120
+ private
121
+ def cache
122
+ @cache[$$]
123
+ end
124
+ end
125
+
126
+ def initialize(connection, logger, connection_options, config)
127
+ super
128
+ @statements = StatementPool.new(@connection,
129
+ config.fetch(:statement_limit) { 1000 })
130
+ @client_encoding = nil
131
+ connect
132
+ end
133
+
134
+ # Returns true, since this connection adapter supports prepared statement
135
+ # caching.
136
+ def supports_statement_cache?
137
+ true
138
+ end
139
+
140
+ # HELPER METHODS ===========================================
141
+
142
+ def each_hash(result) # :nodoc:
143
+ if block_given?
144
+ result.each_hash do |row|
145
+ row.symbolize_keys!
146
+ yield row
147
+ end
148
+ else
149
+ to_enum(:each_hash, result)
150
+ end
151
+ end
152
+
153
+ def new_column(field, default, type, null, collation) # :nodoc:
154
+ Column.new(field, default, type, null, collation)
155
+ end
156
+
157
+ def error_number(exception) # :nodoc:
158
+ exception.errno if exception.respond_to?(:errno)
159
+ end
160
+
161
+ # QUOTING ==================================================
162
+
163
+ def type_cast(value, column)
164
+ return super unless value == true || value == false
165
+
166
+ value ? 1 : 0
167
+ end
168
+
169
+ def quote_string(string) #:nodoc:
170
+ @connection.quote(string)
171
+ end
172
+
173
+ # CONNECTION MANAGEMENT ====================================
174
+
175
+ def active?
176
+ if @connection.respond_to?(:stat)
177
+ @connection.stat
178
+ else
179
+ @connection.query 'select 1'
180
+ end
181
+
182
+ # mysql-ruby doesn't raise an exception when stat fails.
183
+ if @connection.respond_to?(:errno)
184
+ @connection.errno.zero?
185
+ else
186
+ true
187
+ end
188
+ rescue Mysql::Error
189
+ false
190
+ end
191
+
192
+ def reconnect!
193
+ disconnect!
194
+ clear_cache!
195
+ connect
196
+ end
197
+
198
+ # Disconnects from the database if already connected. Otherwise, this
199
+ # method does nothing.
200
+ def disconnect!
201
+ @connection.close rescue nil
202
+ end
203
+
204
+ def reset!
205
+ if @connection.respond_to?(:change_user)
206
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
207
+ # reset the connection is to change the user to the same user.
208
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
209
+ configure_connection
210
+ end
211
+ end
212
+
213
+ # DATABASE STATEMENTS ======================================
214
+
215
+ def select_rows(sql, name = nil)
216
+ @connection.query_with_result = true
217
+ rows = exec_without_stmt(sql, name).rows
218
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
219
+ rows
220
+ end
221
+
222
+ # Clears the prepared statements cache.
223
+ def clear_cache!
224
+ @statements.clear
225
+ end
226
+
227
+ if "<3".respond_to?(:encode)
228
+ # Taken from here:
229
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
230
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
231
+ ENCODINGS = {
232
+ "armscii8" => nil,
233
+ "ascii" => Encoding::US_ASCII,
234
+ "big5" => Encoding::Big5,
235
+ "binary" => Encoding::ASCII_8BIT,
236
+ "cp1250" => Encoding::Windows_1250,
237
+ "cp1251" => Encoding::Windows_1251,
238
+ "cp1256" => Encoding::Windows_1256,
239
+ "cp1257" => Encoding::Windows_1257,
240
+ "cp850" => Encoding::CP850,
241
+ "cp852" => Encoding::CP852,
242
+ "cp866" => Encoding::IBM866,
243
+ "cp932" => Encoding::Windows_31J,
244
+ "dec8" => nil,
245
+ "eucjpms" => Encoding::EucJP_ms,
246
+ "euckr" => Encoding::EUC_KR,
247
+ "gb2312" => Encoding::EUC_CN,
248
+ "gbk" => Encoding::GBK,
249
+ "geostd8" => nil,
250
+ "greek" => Encoding::ISO_8859_7,
251
+ "hebrew" => Encoding::ISO_8859_8,
252
+ "hp8" => nil,
253
+ "keybcs2" => nil,
254
+ "koi8r" => Encoding::KOI8_R,
255
+ "koi8u" => Encoding::KOI8_U,
256
+ "latin1" => Encoding::ISO_8859_1,
257
+ "latin2" => Encoding::ISO_8859_2,
258
+ "latin5" => Encoding::ISO_8859_9,
259
+ "latin7" => Encoding::ISO_8859_13,
260
+ "macce" => Encoding::MacCentEuro,
261
+ "macroman" => Encoding::MacRoman,
262
+ "sjis" => Encoding::SHIFT_JIS,
263
+ "swe7" => nil,
264
+ "tis620" => Encoding::TIS_620,
265
+ "ucs2" => Encoding::UTF_16BE,
266
+ "ujis" => Encoding::EucJP_ms,
267
+ "utf8" => Encoding::UTF_8,
268
+ "utf8mb4" => Encoding::UTF_8,
269
+ }
270
+ else
271
+ ENCODINGS = Hash.new { |h,k| h[k] = k }
272
+ end
273
+
274
+ # Get the client encoding for this database
275
+ def client_encoding
276
+ return @client_encoding if @client_encoding
277
+
278
+ result = exec_query(
279
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
280
+ 'SCHEMA')
281
+ @client_encoding = ENCODINGS[result.rows.last.last]
282
+ end
283
+
284
+ def exec_query(sql, name = 'SQL', binds = [])
285
+ log(sql, name, binds) do
286
+ exec_stmt(sql, name, binds) do |cols, stmt|
287
+ ActiveRecord::Result.new(cols, stmt.to_a) if cols
288
+ end
289
+ end
290
+ end
291
+
292
+ def last_inserted_id(result)
293
+ @connection.insert_id
294
+ end
295
+
296
+ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
297
+ # Some queries, like SHOW CREATE TABLE don't work through the prepared
298
+ # statement API. For those queries, we need to use this method. :'(
299
+ log(sql, name) do
300
+ result = @connection.query(sql)
301
+ cols = []
302
+ rows = []
303
+
304
+ if result
305
+ cols = result.fetch_fields.map { |field| field.name }
306
+ rows = result.to_a
307
+ result.free
308
+ end
309
+ ActiveRecord::Result.new(cols, rows)
310
+ end
311
+ end
312
+
313
+ def execute_and_free(sql, name = nil)
314
+ result = execute(sql, name)
315
+ ret = yield result
316
+ result.free
317
+ ret
318
+ end
319
+
320
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
321
+ super sql, name
322
+ id_value || @connection.insert_id
323
+ end
324
+ alias :create :insert_sql
325
+
326
+ def exec_delete(sql, name, binds)
327
+ log(sql, name, binds) do
328
+ exec_stmt(sql, name, binds) do |cols, stmt|
329
+ stmt.affected_rows
330
+ end
331
+ end
332
+ end
333
+ alias :exec_update :exec_delete
334
+
335
+ def begin_db_transaction #:nodoc:
336
+ exec_without_stmt "BEGIN"
337
+ rescue Mysql::Error
338
+ # Transactions aren't supported
339
+ end
340
+
341
+ private
342
+
343
+ def exec_stmt(sql, name, binds)
344
+ cache = {}
345
+ if binds.empty?
346
+ stmt = @connection.prepare(sql)
347
+ else
348
+ cache = @statements[sql] ||= {
349
+ :stmt => @connection.prepare(sql)
350
+ }
351
+ stmt = cache[:stmt]
352
+ end
353
+
354
+ begin
355
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
356
+ rescue Mysql::Error => e
357
+ # Older versions of MySQL leave the prepared statement in a bad
358
+ # place when an error occurs. To support older mysql versions, we
359
+ # need to close the statement and delete the statement from the
360
+ # cache.
361
+ stmt.close
362
+ @statements.delete sql
363
+ raise e
364
+ end
365
+
366
+ cols = nil
367
+ if metadata = stmt.result_metadata
368
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
369
+ field.name
370
+ }
371
+ end
372
+
373
+ result = yield [cols, stmt]
374
+
375
+ stmt.result_metadata.free if cols
376
+ stmt.free_result
377
+ stmt.close if binds.empty?
378
+
379
+ result
380
+ end
381
+
382
+ def connect
383
+ encoding = @config[:encoding]
384
+ if encoding
385
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
386
+ end
387
+
388
+ if @config[:sslca] || @config[:sslkey]
389
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
390
+ end
391
+
392
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
393
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
394
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
395
+
396
+ @connection.real_connect(*@connection_options)
397
+
398
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
399
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
400
+
401
+ configure_connection
402
+ end
403
+
404
+ def configure_connection
405
+ encoding = @config[:encoding]
406
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
407
+
408
+ # By default, MySQL 'where id is null' selects the last inserted id.
409
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
410
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
411
+ end
412
+
413
+ def select(sql, name = nil, binds = [])
414
+ @connection.query_with_result = true
415
+ rows = exec_query(sql, name, binds).to_a
416
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
417
+ rows
418
+ end
419
+
420
+ # Returns the version of the connected MySQL server.
421
+ def version
422
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
423
+ end
424
+ end
425
+ end
426
+ end
@@ -0,0 +1,1261 @@
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
+
5
+ # Make sure we're using pg high enough for PGResult#values
6
+ gem 'pg', '~> 0.11'
7
+ require 'pg'
8
+
9
+ module ActiveRecord
10
+ class Base
11
+ # Establishes a connection to the database that's used by all Active Record objects
12
+ def self.postgresql_connection(config) # :nodoc:
13
+ config = config.symbolize_keys
14
+ host = config[:host]
15
+ port = config[:port] || 5432
16
+ username = config[:username].to_s if config[:username]
17
+ password = config[:password].to_s if config[:password]
18
+
19
+ if config.key?(:database)
20
+ database = config[:database]
21
+ else
22
+ raise ArgumentError, "No database specified. Missing argument: database."
23
+ end
24
+
25
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
26
+ # so just pass a nil connection object for the time being.
27
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
28
+ end
29
+ end
30
+
31
+ module ConnectionAdapters
32
+ # PostgreSQL-specific extensions to column definitions in a table.
33
+ class PostgreSQLColumn < Column #:nodoc:
34
+ # Instantiates a new PostgreSQL column definition in a table.
35
+ def initialize(name, default, sql_type = nil, null = true)
36
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
37
+ end
38
+
39
+ # :stopdoc:
40
+ class << self
41
+ attr_accessor :money_precision
42
+ def string_to_time(string)
43
+ return string unless String === string
44
+
45
+ case string
46
+ when 'infinity' then 1.0 / 0.0
47
+ when '-infinity' then -1.0 / 0.0
48
+ else
49
+ super
50
+ end
51
+ end
52
+ end
53
+ # :startdoc:
54
+
55
+ private
56
+ def extract_limit(sql_type)
57
+ case sql_type
58
+ when /^bigint/i; 8
59
+ when /^smallint/i; 2
60
+ else super
61
+ end
62
+ end
63
+
64
+ # Extracts the scale from PostgreSQL-specific data types.
65
+ def extract_scale(sql_type)
66
+ # Money type has a fixed scale of 2.
67
+ sql_type =~ /^money/ ? 2 : super
68
+ end
69
+
70
+ # Extracts the precision from PostgreSQL-specific data types.
71
+ def extract_precision(sql_type)
72
+ if sql_type == 'money'
73
+ self.class.money_precision
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # Maps PostgreSQL-specific data types to logical Rails types.
80
+ def simplified_type(field_type)
81
+ case field_type
82
+ # Numeric and monetary types
83
+ when /^(?:real|double precision)$/
84
+ :float
85
+ # Monetary types
86
+ when 'money'
87
+ :decimal
88
+ # Character types
89
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
90
+ :string
91
+ # Binary data types
92
+ when 'bytea'
93
+ :binary
94
+ # Date/time types
95
+ when /^timestamp with(?:out)? time zone$/
96
+ :datetime
97
+ when 'interval'
98
+ :string
99
+ # Geometric types
100
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
101
+ :string
102
+ # Network address types
103
+ when /^(?:cidr|inet|macaddr)$/
104
+ :string
105
+ # Bit strings
106
+ when /^bit(?: varying)?(?:\(\d+\))?$/
107
+ :string
108
+ # XML type
109
+ when 'xml'
110
+ :xml
111
+ # tsvector type
112
+ when 'tsvector'
113
+ :tsvector
114
+ # Arrays
115
+ when /^\D+\[\]$/
116
+ :string
117
+ # Object identifier types
118
+ when 'oid'
119
+ :integer
120
+ # UUID type
121
+ when 'uuid'
122
+ :string
123
+ # Small and big integer types
124
+ when /^(?:small|big)int$/
125
+ :integer
126
+ # Pass through all types that are not specific to PostgreSQL.
127
+ else
128
+ super
129
+ end
130
+ end
131
+
132
+ # Extracts the value from a PostgreSQL column default definition.
133
+ def self.extract_value_from_default(default)
134
+ case default
135
+ # This is a performance optimization for Ruby 1.9.2 in development.
136
+ # If the value is nil, we return nil straight away without checking
137
+ # the regular expressions. If we check each regular expression,
138
+ # Regexp#=== will call NilClass#to_str, which will trigger
139
+ # method_missing (defined by whiny nil in ActiveSupport) which
140
+ # makes this method very very slow.
141
+ when NilClass
142
+ nil
143
+ # Numeric types
144
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
145
+ $1
146
+ # Character types
147
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
148
+ $1
149
+ # Character types (8.1 formatting)
150
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
151
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
152
+ # Binary data types
153
+ when /\A'(.*)'::bytea\z/m
154
+ $1
155
+ # Date/time types
156
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
157
+ $1
158
+ when /\A'(.*)'::interval\z/
159
+ $1
160
+ # Boolean type
161
+ when 'true'
162
+ true
163
+ when 'false'
164
+ false
165
+ # Geometric types
166
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
167
+ $1
168
+ # Network address types
169
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
170
+ $1
171
+ # Bit string types
172
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
173
+ $1
174
+ # XML type
175
+ when /\A'(.*)'::xml\z/m
176
+ $1
177
+ # Arrays
178
+ when /\A'(.*)'::"?\D+"?\[\]\z/
179
+ $1
180
+ # Object identifier types
181
+ when /\A-?\d+\z/
182
+ $1
183
+ else
184
+ # Anything else is blank, some user type, or some function
185
+ # and we can't know the value of that, so return nil.
186
+ nil
187
+ end
188
+ end
189
+ end
190
+
191
+ # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
192
+ # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
193
+ #
194
+ # Options:
195
+ #
196
+ # * <tt>:host</tt> - Defaults to "localhost".
197
+ # * <tt>:port</tt> - Defaults to 5432.
198
+ # * <tt>:username</tt> - Defaults to nothing.
199
+ # * <tt>:password</tt> - Defaults to nothing.
200
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
201
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
202
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
203
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
204
+ # <encoding></tt> call on the connection.
205
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
206
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
207
+ class PostgreSQLAdapter < AbstractAdapter
208
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
209
+ def xml(*args)
210
+ options = args.extract_options!
211
+ column(args[0], 'xml', options)
212
+ end
213
+
214
+ def tsvector(*args)
215
+ options = args.extract_options!
216
+ column(args[0], 'tsvector', options)
217
+ end
218
+ end
219
+
220
+ ADAPTER_NAME = 'PostgreSQL'
221
+
222
+ NATIVE_DATABASE_TYPES = {
223
+ :primary_key => "serial primary key",
224
+ :string => { :name => "character varying", :limit => 255 },
225
+ :text => { :name => "text" },
226
+ :integer => { :name => "integer" },
227
+ :float => { :name => "float" },
228
+ :decimal => { :name => "decimal" },
229
+ :datetime => { :name => "timestamp" },
230
+ :timestamp => { :name => "timestamp" },
231
+ :time => { :name => "time" },
232
+ :date => { :name => "date" },
233
+ :binary => { :name => "bytea" },
234
+ :boolean => { :name => "boolean" },
235
+ :xml => { :name => "xml" },
236
+ :tsvector => { :name => "tsvector" }
237
+ }
238
+
239
+ # Returns 'PostgreSQL' as adapter name for identification purposes.
240
+ def adapter_name
241
+ ADAPTER_NAME
242
+ end
243
+
244
+ # Returns +true+, since this connection adapter supports prepared statement
245
+ # caching.
246
+ def supports_statement_cache?
247
+ true
248
+ end
249
+
250
+ def supports_index_sort_order?
251
+ true
252
+ end
253
+
254
+ class StatementPool < ConnectionAdapters::StatementPool
255
+ def initialize(connection, max)
256
+ super
257
+ @counter = 0
258
+ @cache = Hash.new { |h,pid| h[pid] = {} }
259
+ end
260
+
261
+ def each(&block); cache.each(&block); end
262
+ def key?(key); cache.key?(key); end
263
+ def [](key); cache[key]; end
264
+ def length; cache.length; end
265
+
266
+ def next_key
267
+ "a#{@counter + 1}"
268
+ end
269
+
270
+ def []=(sql, key)
271
+ while @max <= cache.size
272
+ dealloc(cache.shift.last)
273
+ end
274
+ @counter += 1
275
+ cache[sql] = key
276
+ end
277
+
278
+ def clear
279
+ cache.each_value do |stmt_key|
280
+ dealloc stmt_key
281
+ end
282
+ cache.clear
283
+ end
284
+
285
+ def delete(sql_key)
286
+ dealloc cache[sql_key]
287
+ cache.delete sql_key
288
+ end
289
+
290
+ private
291
+ def cache
292
+ @cache[$$]
293
+ end
294
+
295
+ def dealloc(key)
296
+ @connection.query "DEALLOCATE #{key}" if connection_active?
297
+ end
298
+
299
+ def connection_active?
300
+ @connection.status == PGconn::CONNECTION_OK
301
+ rescue PGError
302
+ false
303
+ end
304
+ end
305
+
306
+ # Initializes and connects a PostgreSQL adapter.
307
+ def initialize(connection, logger, connection_parameters, config)
308
+ super(connection, logger)
309
+ @connection_parameters, @config = connection_parameters, config
310
+ @visitor = Arel::Visitors::PostgreSQL.new self
311
+
312
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
313
+ @local_tz = nil
314
+ @table_alias_length = nil
315
+
316
+ connect
317
+ @statements = StatementPool.new @connection,
318
+ config.fetch(:statement_limit) { 1000 }
319
+
320
+ if postgresql_version < 80200
321
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
322
+ end
323
+
324
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
325
+ end
326
+
327
+ # Clears the prepared statements cache.
328
+ def clear_cache!
329
+ @statements.clear
330
+ end
331
+
332
+ # Is this connection alive and ready for queries?
333
+ def active?
334
+ @connection.status == PGconn::CONNECTION_OK
335
+ rescue PGError
336
+ false
337
+ end
338
+
339
+ # Close then reopen the connection.
340
+ def reconnect!
341
+ clear_cache!
342
+ @connection.reset
343
+ configure_connection
344
+ end
345
+
346
+ def reset!
347
+ clear_cache!
348
+ super
349
+ end
350
+
351
+ # Disconnects from the database if already connected. Otherwise, this
352
+ # method does nothing.
353
+ def disconnect!
354
+ clear_cache!
355
+ @connection.close rescue nil
356
+ end
357
+
358
+ def native_database_types #:nodoc:
359
+ NATIVE_DATABASE_TYPES
360
+ end
361
+
362
+ # Returns true, since this connection adapter supports migrations.
363
+ def supports_migrations?
364
+ true
365
+ end
366
+
367
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
368
+ def supports_primary_key? #:nodoc:
369
+ true
370
+ end
371
+
372
+ # Enable standard-conforming strings if available.
373
+ def set_standard_conforming_strings
374
+ old, self.client_min_messages = client_min_messages, 'panic'
375
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
376
+ ensure
377
+ self.client_min_messages = old
378
+ end
379
+
380
+ def supports_insert_with_returning?
381
+ true
382
+ end
383
+
384
+ def supports_ddl_transactions?
385
+ true
386
+ end
387
+
388
+ # Returns true, since this connection adapter supports savepoints.
389
+ def supports_savepoints?
390
+ true
391
+ end
392
+
393
+ # Returns true.
394
+ def supports_explain?
395
+ true
396
+ end
397
+
398
+ # Returns the configured supported identifier length supported by PostgreSQL
399
+ def table_alias_length
400
+ @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
401
+ end
402
+
403
+ # QUOTING ==================================================
404
+
405
+ # Escapes binary strings for bytea input to the database.
406
+ def escape_bytea(value)
407
+ @connection.escape_bytea(value) if value
408
+ end
409
+
410
+ # Unescapes bytea output from a database to the binary string it represents.
411
+ # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
412
+ # on escaped binary output from database drive.
413
+ def unescape_bytea(value)
414
+ @connection.unescape_bytea(value) if value
415
+ end
416
+
417
+ # Quotes PostgreSQL-specific data types for SQL input.
418
+ def quote(value, column = nil) #:nodoc:
419
+ return super unless column
420
+
421
+ case value
422
+ when Float
423
+ return super unless value.infinite? && column.type == :datetime
424
+ "'#{value.to_s.downcase}'"
425
+ when Numeric
426
+ return super unless column.sql_type == 'money'
427
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
428
+ "'#{value}'"
429
+ when String
430
+ case column.sql_type
431
+ when 'bytea' then "'#{escape_bytea(value)}'"
432
+ when 'xml' then "xml '#{quote_string(value)}'"
433
+ when /^bit/
434
+ case value
435
+ when /^[01]*$/ then "B'#{value}'" # Bit-string notation
436
+ when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
437
+ end
438
+ else
439
+ super
440
+ end
441
+ else
442
+ super
443
+ end
444
+ end
445
+
446
+ def type_cast(value, column)
447
+ return super unless column
448
+
449
+ case value
450
+ when String
451
+ return super unless 'bytea' == column.sql_type
452
+ { :value => value, :format => 1 }
453
+ else
454
+ super
455
+ end
456
+ end
457
+
458
+ # Quotes strings for use in SQL input.
459
+ def quote_string(s) #:nodoc:
460
+ @connection.escape(s)
461
+ end
462
+
463
+ # Checks the following cases:
464
+ #
465
+ # - table_name
466
+ # - "table.name"
467
+ # - schema_name.table_name
468
+ # - schema_name."table.name"
469
+ # - "schema.name".table_name
470
+ # - "schema.name"."table.name"
471
+ def quote_table_name(name)
472
+ schema, name_part = extract_pg_identifier_from_name(name.to_s)
473
+
474
+ unless name_part
475
+ quote_column_name(schema)
476
+ else
477
+ table_name, name_part = extract_pg_identifier_from_name(name_part)
478
+ "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
479
+ end
480
+ end
481
+
482
+ # Quotes column names for use in SQL queries.
483
+ def quote_column_name(name) #:nodoc:
484
+ PGconn.quote_ident(name.to_s)
485
+ end
486
+
487
+ # Quote date/time values for use in SQL input. Includes microseconds
488
+ # if the value is a Time responding to usec.
489
+ def quoted_date(value) #:nodoc:
490
+ if value.acts_like?(:time) && value.respond_to?(:usec)
491
+ "#{super}.#{sprintf("%06d", value.usec)}"
492
+ else
493
+ super
494
+ end
495
+ end
496
+
497
+ # Set the authorized user for this session
498
+ def session_auth=(user)
499
+ clear_cache!
500
+ exec_query "SET SESSION AUTHORIZATION #{user}"
501
+ end
502
+
503
+ # REFERENTIAL INTEGRITY ====================================
504
+
505
+ def supports_disable_referential_integrity? #:nodoc:
506
+ true
507
+ end
508
+
509
+ def disable_referential_integrity #:nodoc:
510
+ if supports_disable_referential_integrity? then
511
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
512
+ end
513
+ yield
514
+ ensure
515
+ if supports_disable_referential_integrity? then
516
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
517
+ end
518
+ end
519
+
520
+ # DATABASE STATEMENTS ======================================
521
+
522
+ def explain(arel, binds = [])
523
+ sql = "EXPLAIN #{to_sql(arel)}"
524
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
525
+ end
526
+
527
+ class ExplainPrettyPrinter # :nodoc:
528
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
529
+ # PostgreSQL shell:
530
+ #
531
+ # QUERY PLAN
532
+ # ------------------------------------------------------------------------------
533
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
534
+ # Join Filter: (posts.user_id = users.id)
535
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
536
+ # Index Cond: (id = 1)
537
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
538
+ # Filter: (posts.user_id = 1)
539
+ # (6 rows)
540
+ #
541
+ def pp(result)
542
+ header = result.columns.first
543
+ lines = result.rows.map(&:first)
544
+
545
+ # We add 2 because there's one char of padding at both sides, note
546
+ # the extra hyphens in the example above.
547
+ width = [header, *lines].map(&:length).max + 2
548
+
549
+ pp = []
550
+
551
+ pp << header.center(width).rstrip
552
+ pp << '-' * width
553
+
554
+ pp += lines.map {|line| " #{line}"}
555
+
556
+ nrows = result.rows.length
557
+ rows_label = nrows == 1 ? 'row' : 'rows'
558
+ pp << "(#{nrows} #{rows_label})"
559
+
560
+ pp.join("\n") + "\n"
561
+ end
562
+ end
563
+
564
+ # Executes a SELECT query and returns an array of rows. Each row is an
565
+ # array of field values.
566
+ def select_rows(sql, name = nil)
567
+ select_raw(sql, name).last
568
+ end
569
+
570
+ # Executes an INSERT query and returns the new record's ID
571
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
572
+ unless pk
573
+ # Extract the table from the insert sql. Yuck.
574
+ table_ref = extract_table_ref_from_insert_sql(sql)
575
+ pk = primary_key(table_ref) if table_ref
576
+ end
577
+
578
+ if pk
579
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
580
+ else
581
+ super
582
+ end
583
+ end
584
+ alias :create :insert
585
+
586
+ # create a 2D array representing the result set
587
+ def result_as_array(res) #:nodoc:
588
+ # check if we have any binary column and if they need escaping
589
+ ftypes = Array.new(res.nfields) do |i|
590
+ [i, res.ftype(i)]
591
+ end
592
+
593
+ rows = res.values
594
+ return rows unless ftypes.any? { |_, x|
595
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
596
+ }
597
+
598
+ typehash = ftypes.group_by { |_, type| type }
599
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
600
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
601
+
602
+ rows.each do |row|
603
+ # unescape string passed BYTEA field (OID == 17)
604
+ binaries.each do |index, _|
605
+ row[index] = unescape_bytea(row[index])
606
+ end
607
+
608
+ # If this is a money type column and there are any currency symbols,
609
+ # then strip them off. Indeed it would be prettier to do this in
610
+ # PostgreSQLColumn.string_to_decimal but would break form input
611
+ # fields that call value_before_type_cast.
612
+ monies.each do |index, _|
613
+ data = row[index]
614
+ # Because money output is formatted according to the locale, there are two
615
+ # cases to consider (note the decimal separators):
616
+ # (1) $12,345,678.12
617
+ # (2) $12.345.678,12
618
+ case data
619
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
620
+ data.gsub!(/[^-\d.]/, '')
621
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
622
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
623
+ end
624
+ end
625
+ end
626
+ end
627
+
628
+
629
+ # Queries the database and returns the results in an Array-like object
630
+ def query(sql, name = nil) #:nodoc:
631
+ log(sql, name) do
632
+ result_as_array @connection.async_exec(sql)
633
+ end
634
+ end
635
+
636
+ # Executes an SQL statement, returning a PGresult object on success
637
+ # or raising a PGError exception otherwise.
638
+ def execute(sql, name = nil)
639
+ log(sql, name) do
640
+ @connection.async_exec(sql)
641
+ end
642
+ end
643
+
644
+ def substitute_at(column, index)
645
+ Arel.sql("$#{index + 1}")
646
+ end
647
+
648
+ def exec_query(sql, name = 'SQL', binds = [])
649
+ log(sql, name, binds) do
650
+ result = binds.empty? ? exec_no_cache(sql, binds) :
651
+ exec_cache(sql, binds)
652
+
653
+ ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
654
+ result.clear
655
+ return ret
656
+ end
657
+ end
658
+
659
+ def exec_delete(sql, name = 'SQL', binds = [])
660
+ log(sql, name, binds) do
661
+ result = binds.empty? ? exec_no_cache(sql, binds) :
662
+ exec_cache(sql, binds)
663
+ affected = result.cmd_tuples
664
+ result.clear
665
+ affected
666
+ end
667
+ end
668
+ alias :exec_update :exec_delete
669
+
670
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
671
+ unless pk
672
+ # Extract the table from the insert sql. Yuck.
673
+ table_ref = extract_table_ref_from_insert_sql(sql)
674
+ pk = primary_key(table_ref) if table_ref
675
+ end
676
+
677
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
678
+
679
+ [sql, binds]
680
+ end
681
+
682
+ # Executes an UPDATE query and returns the number of affected tuples.
683
+ def update_sql(sql, name = nil)
684
+ super.cmd_tuples
685
+ end
686
+
687
+ # Begins a transaction.
688
+ def begin_db_transaction
689
+ execute "BEGIN"
690
+ end
691
+
692
+ # Commits a transaction.
693
+ def commit_db_transaction
694
+ execute "COMMIT"
695
+ end
696
+
697
+ # Aborts a transaction.
698
+ def rollback_db_transaction
699
+ execute "ROLLBACK"
700
+ end
701
+
702
+ def outside_transaction?
703
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
704
+ end
705
+
706
+ def create_savepoint
707
+ execute("SAVEPOINT #{current_savepoint_name}")
708
+ end
709
+
710
+ def rollback_to_savepoint
711
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
712
+ end
713
+
714
+ def release_savepoint
715
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
716
+ end
717
+
718
+ # SCHEMA STATEMENTS ========================================
719
+
720
+ # Drops the database specified on the +name+ attribute
721
+ # and creates it again using the provided +options+.
722
+ def recreate_database(name, options = {}) #:nodoc:
723
+ drop_database(name)
724
+ create_database(name, options)
725
+ end
726
+
727
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
728
+ # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
729
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
730
+ #
731
+ # Example:
732
+ # create_database config[:database], config
733
+ # create_database 'foo_development', :encoding => 'unicode'
734
+ def create_database(name, options = {})
735
+ options = options.reverse_merge(:encoding => "utf8")
736
+
737
+ option_string = options.symbolize_keys.sum do |key, value|
738
+ case key
739
+ when :owner
740
+ " OWNER = \"#{value}\""
741
+ when :template
742
+ " TEMPLATE = \"#{value}\""
743
+ when :encoding
744
+ " ENCODING = '#{value}'"
745
+ when :tablespace
746
+ " TABLESPACE = \"#{value}\""
747
+ when :connection_limit
748
+ " CONNECTION LIMIT = #{value}"
749
+ else
750
+ ""
751
+ end
752
+ end
753
+
754
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
755
+ end
756
+
757
+ # Drops a PostgreSQL database.
758
+ #
759
+ # Example:
760
+ # drop_database 'matt_development'
761
+ def drop_database(name) #:nodoc:
762
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
763
+ end
764
+
765
+ # Returns the list of all tables in the schema search path or a specified schema.
766
+ def tables(name = nil)
767
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
768
+ SELECT tablename
769
+ FROM pg_tables
770
+ WHERE schemaname = ANY (current_schemas(false))
771
+ SQL
772
+ end
773
+
774
+ # Returns true if table exists.
775
+ # If the schema is not specified as part of +name+ then it will only find tables within
776
+ # the current schema search path (regardless of permissions to access tables in other schemas)
777
+ def table_exists?(name)
778
+ schema, table = Utils.extract_schema_and_table(name.to_s)
779
+ return false unless table
780
+
781
+ binds = [[nil, table]]
782
+ binds << [nil, schema] if schema
783
+
784
+ exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
785
+ SELECT COUNT(*)
786
+ FROM pg_class c
787
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
788
+ WHERE c.relkind in ('v','r')
789
+ AND c.relname = $1
790
+ AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
791
+ SQL
792
+ end
793
+
794
+ # Returns true if schema exists.
795
+ def schema_exists?(name)
796
+ exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0
797
+ SELECT COUNT(*)
798
+ FROM pg_namespace
799
+ WHERE nspname = $1
800
+ SQL
801
+ end
802
+
803
+ # Returns an array of indexes for the given table.
804
+ def indexes(table_name, name = nil)
805
+ result = query(<<-SQL, name)
806
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
807
+ FROM pg_class t
808
+ INNER JOIN pg_index d ON t.oid = d.indrelid
809
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
810
+ WHERE i.relkind = 'i'
811
+ AND d.indisprimary = 'f'
812
+ AND t.relname = '#{table_name}'
813
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
814
+ ORDER BY i.relname
815
+ SQL
816
+
817
+
818
+ result.map do |row|
819
+ index_name = row[0]
820
+ unique = row[1] == 't'
821
+ indkey = row[2].split(" ")
822
+ inddef = row[3]
823
+ oid = row[4]
824
+
825
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
826
+ SELECT a.attnum, a.attname
827
+ FROM pg_attribute a
828
+ WHERE a.attrelid = #{oid}
829
+ AND a.attnum IN (#{indkey.join(",")})
830
+ SQL
831
+
832
+ column_names = columns.values_at(*indkey).compact
833
+
834
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
835
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
836
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
837
+
838
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
839
+ end.compact
840
+ end
841
+
842
+ # Returns the list of all column definitions for a table.
843
+ def columns(table_name, name = nil)
844
+ # Limit, precision, and scale are all handled by the superclass.
845
+ column_definitions(table_name).collect do |column_name, type, default, notnull|
846
+ PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
847
+ end
848
+ end
849
+
850
+ # Returns the current database name.
851
+ def current_database
852
+ query('select current_database()')[0][0]
853
+ end
854
+
855
+ # Returns the current schema name.
856
+ def current_schema
857
+ query('SELECT current_schema', 'SCHEMA')[0][0]
858
+ end
859
+
860
+ # Returns the current database encoding format.
861
+ def encoding
862
+ query(<<-end_sql)[0][0]
863
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
864
+ WHERE pg_database.datname LIKE '#{current_database}'
865
+ end_sql
866
+ end
867
+
868
+ # Sets the schema search path to a string of comma-separated schema names.
869
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
870
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
871
+ #
872
+ # This should be not be called manually but set in database.yml.
873
+ def schema_search_path=(schema_csv)
874
+ if schema_csv
875
+ execute "SET search_path TO #{schema_csv}"
876
+ @schema_search_path = schema_csv
877
+ end
878
+ end
879
+
880
+ # Returns the active schema search path.
881
+ def schema_search_path
882
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
883
+ end
884
+
885
+ # Returns the current client message level.
886
+ def client_min_messages
887
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
888
+ end
889
+
890
+ # Set the client message level.
891
+ def client_min_messages=(level)
892
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
893
+ end
894
+
895
+ # Returns the sequence name for a table's primary key or some other specified key.
896
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
897
+ serial_sequence(table_name, pk || 'id').split('.').last
898
+ rescue ActiveRecord::StatementInvalid
899
+ "#{table_name}_#{pk || 'id'}_seq"
900
+ end
901
+
902
+ def serial_sequence(table, column)
903
+ result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
904
+ SELECT pg_get_serial_sequence($1, $2)
905
+ eosql
906
+ result.rows.first.first
907
+ end
908
+
909
+ # Resets the sequence of a table's primary key to the maximum value.
910
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
911
+ unless pk and sequence
912
+ default_pk, default_sequence = pk_and_sequence_for(table)
913
+
914
+ pk ||= default_pk
915
+ sequence ||= default_sequence
916
+ end
917
+
918
+ if @logger && pk && !sequence
919
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
920
+ end
921
+
922
+ if pk && sequence
923
+ quoted_sequence = quote_table_name(sequence)
924
+
925
+ select_value <<-end_sql, 'Reset sequence'
926
+ 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)
927
+ end_sql
928
+ end
929
+ end
930
+
931
+ # Returns a table's primary key and belonging sequence.
932
+ def pk_and_sequence_for(table) #:nodoc:
933
+ # First try looking for a sequence with a dependency on the
934
+ # given table's primary key.
935
+ result = exec_query(<<-end_sql, 'SCHEMA').rows.first
936
+ SELECT attr.attname, ns.nspname, seq.relname
937
+ FROM pg_class seq
938
+ INNER JOIN pg_depend dep ON seq.oid = dep.objid
939
+ INNER JOIN pg_attribute attr ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
940
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
941
+ INNER JOIN pg_namespace ns ON seq.relnamespace = ns.oid
942
+ WHERE seq.relkind = 'S'
943
+ AND cons.contype = 'p'
944
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
945
+ end_sql
946
+
947
+ # [primary_key, sequence]
948
+ if result.second == 'public' then
949
+ sequence = result.last
950
+ else
951
+ sequence = result.second+'.'+result.last
952
+ end
953
+
954
+ [result.first, sequence]
955
+ rescue
956
+ nil
957
+ end
958
+
959
+ # Returns just a table's primary key
960
+ def primary_key(table)
961
+ row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
962
+ SELECT DISTINCT(attr.attname)
963
+ FROM pg_attribute attr
964
+ INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
965
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
966
+ WHERE cons.contype = 'p'
967
+ AND dep.refobjid = $1::regclass
968
+ end_sql
969
+
970
+ row && row.first
971
+ end
972
+
973
+ # Renames a table.
974
+ #
975
+ # Example:
976
+ # rename_table('octopuses', 'octopi')
977
+ def rename_table(name, new_name)
978
+ clear_cache!
979
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
980
+ end
981
+
982
+ # Adds a new column to the named table.
983
+ # See TableDefinition#column for details of the options you can use.
984
+ def add_column(table_name, column_name, type, options = {})
985
+ clear_cache!
986
+ 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])}"
987
+ add_column_options!(add_column_sql, options)
988
+
989
+ execute add_column_sql
990
+ end
991
+
992
+ # Changes the column of a table.
993
+ def change_column(table_name, column_name, type, options = {})
994
+ clear_cache!
995
+ quoted_table_name = quote_table_name(table_name)
996
+
997
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
998
+
999
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
1000
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
1001
+ end
1002
+
1003
+ # Changes the default value of a table column.
1004
+ def change_column_default(table_name, column_name, default)
1005
+ clear_cache!
1006
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
1007
+ end
1008
+
1009
+ def change_column_null(table_name, column_name, null, default = nil)
1010
+ clear_cache!
1011
+ unless null || default.nil?
1012
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
1013
+ end
1014
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
1015
+ end
1016
+
1017
+ # Renames a column in a table.
1018
+ def rename_column(table_name, column_name, new_column_name)
1019
+ clear_cache!
1020
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1021
+ end
1022
+
1023
+ def remove_index!(table_name, index_name) #:nodoc:
1024
+ execute "DROP INDEX #{quote_table_name(index_name)}"
1025
+ end
1026
+
1027
+ def rename_index(table_name, old_name, new_name)
1028
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
1029
+ end
1030
+
1031
+ def index_name_length
1032
+ 63
1033
+ end
1034
+
1035
+ # Maps logical Rails types to PostgreSQL-specific data types.
1036
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1037
+ return super unless type.to_s == 'integer'
1038
+ return 'integer' unless limit
1039
+
1040
+ case limit
1041
+ when 1, 2; 'smallint'
1042
+ when 3, 4; 'integer'
1043
+ when 5..8; 'bigint'
1044
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1045
+ end
1046
+ end
1047
+
1048
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1049
+ #
1050
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
1051
+ # requires that the ORDER BY include the distinct column.
1052
+ #
1053
+ # distinct("posts.id", "posts.created_at desc")
1054
+ def distinct(columns, orders) #:nodoc:
1055
+ return "DISTINCT #{columns}" if orders.empty?
1056
+
1057
+ # Construct a clean list of column names from the ORDER BY clause, removing
1058
+ # any ASC/DESC modifiers
1059
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
1060
+ order_columns.delete_if { |c| c.blank? }
1061
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
1062
+
1063
+ "DISTINCT #{columns}, #{order_columns * ', '}"
1064
+ end
1065
+
1066
+ module Utils
1067
+ extend self
1068
+
1069
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1070
+ # +schema_name+ is nil if not specified in +name+.
1071
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1072
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
1073
+ #
1074
+ # * <tt>table_name</tt>
1075
+ # * <tt>"table.name"</tt>
1076
+ # * <tt>schema_name.table_name</tt>
1077
+ # * <tt>schema_name."table.name"</tt>
1078
+ # * <tt>"schema.name"."table name"</tt>
1079
+ def extract_schema_and_table(name)
1080
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1081
+ [schema, table]
1082
+ end
1083
+ end
1084
+
1085
+ protected
1086
+ # Returns the version of the connected PostgreSQL server.
1087
+ def postgresql_version
1088
+ @connection.server_version
1089
+ end
1090
+
1091
+ def translate_exception(exception, message)
1092
+ case exception.message
1093
+ when /duplicate key value violates unique constraint/
1094
+ RecordNotUnique.new(message, exception)
1095
+ when /violates foreign key constraint/
1096
+ InvalidForeignKey.new(message, exception)
1097
+ else
1098
+ super
1099
+ end
1100
+ end
1101
+
1102
+ private
1103
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1104
+
1105
+ def exec_no_cache(sql, binds)
1106
+ @connection.async_exec(sql)
1107
+ end
1108
+
1109
+ def exec_cache(sql, binds)
1110
+ begin
1111
+ stmt_key = prepare_statement sql
1112
+
1113
+ # Clear the queue
1114
+ @connection.get_last_result
1115
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
1116
+ type_cast(val, col)
1117
+ })
1118
+ @connection.block
1119
+ @connection.get_last_result
1120
+ rescue PGError => e
1121
+ # Get the PG code for the failure. Annoyingly, the code for
1122
+ # prepared statements whose return value may have changed is
1123
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
1124
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1125
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
1126
+ if FEATURE_NOT_SUPPORTED == code
1127
+ @statements.delete sql_key(sql)
1128
+ retry
1129
+ else
1130
+ raise e
1131
+ end
1132
+ end
1133
+ end
1134
+
1135
+ # Returns the statement identifier for the client side cache
1136
+ # of statements
1137
+ def sql_key(sql)
1138
+ "#{schema_search_path}-#{sql}"
1139
+ end
1140
+
1141
+ # Prepare the statement if it hasn't been prepared, return
1142
+ # the statement key.
1143
+ def prepare_statement(sql)
1144
+ sql_key = sql_key(sql)
1145
+ unless @statements.key? sql_key
1146
+ nextkey = @statements.next_key
1147
+ @connection.prepare nextkey, sql
1148
+ @statements[sql_key] = nextkey
1149
+ end
1150
+ @statements[sql_key]
1151
+ end
1152
+
1153
+ # The internal PostgreSQL identifier of the money data type.
1154
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
1155
+ # The internal PostgreSQL identifier of the BYTEA data type.
1156
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
1157
+
1158
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
1159
+ # connected server's characteristics.
1160
+ def connect
1161
+ @connection = PGconn.connect(*@connection_parameters)
1162
+
1163
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
1164
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
1165
+ # should know about this but can't detect it there, so deal with it here.
1166
+ PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
1167
+
1168
+ configure_connection
1169
+ end
1170
+
1171
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
1172
+ # This is called by #connect and should not be called manually.
1173
+ def configure_connection
1174
+ if @config[:encoding]
1175
+ @connection.set_client_encoding(@config[:encoding])
1176
+ end
1177
+ self.client_min_messages = @config[:min_messages] if @config[:min_messages]
1178
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
1179
+
1180
+ # Use standard-conforming strings if available so we don't have to do the E'...' dance.
1181
+ set_standard_conforming_strings
1182
+
1183
+ # If using Active Record's time zone support configure the connection to return
1184
+ # TIMESTAMP WITH ZONE types in UTC.
1185
+ if ActiveRecord::Base.default_timezone == :utc
1186
+ execute("SET time zone 'UTC'", 'SCHEMA')
1187
+ elsif @local_tz
1188
+ execute("SET time zone '#{@local_tz}'", 'SCHEMA')
1189
+ end
1190
+ end
1191
+
1192
+ # Returns the current ID of a table's sequence.
1193
+ def last_insert_id(sequence_name) #:nodoc:
1194
+ r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
1195
+ Integer(r.rows.first.first)
1196
+ end
1197
+
1198
+ # Executes a SELECT query and returns the results, performing any data type
1199
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
1200
+ def select(sql, name = nil, binds = [])
1201
+ exec_query(sql, name, binds).to_a
1202
+ end
1203
+
1204
+ def select_raw(sql, name = nil)
1205
+ res = execute(sql, name)
1206
+ results = result_as_array(res)
1207
+ fields = res.fields
1208
+ res.clear
1209
+ return fields, results
1210
+ end
1211
+
1212
+ # Returns the list of a table's column names, data types, and default values.
1213
+ #
1214
+ # The underlying query is roughly:
1215
+ # SELECT column.name, column.type, default.value
1216
+ # FROM column LEFT JOIN default
1217
+ # ON column.table_id = default.table_id
1218
+ # AND column.num = default.column_num
1219
+ # WHERE column.table_id = get_table_id('table_name')
1220
+ # AND column.num > 0
1221
+ # AND NOT column.is_dropped
1222
+ # ORDER BY column.num
1223
+ #
1224
+ # If the table name is not prefixed with a schema, the database will
1225
+ # take the first match from the schema search path.
1226
+ #
1227
+ # Query implementation notes:
1228
+ # - format_type includes the column size constraint, e.g. varchar(50)
1229
+ # - ::regclass is a function that gives the id for a table name
1230
+ def column_definitions(table_name) #:nodoc:
1231
+ exec_query(<<-end_sql, 'SCHEMA').rows
1232
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1233
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
1234
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1235
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1236
+ AND a.attnum > 0 AND NOT a.attisdropped
1237
+ ORDER BY a.attnum
1238
+ end_sql
1239
+ end
1240
+
1241
+ def extract_pg_identifier_from_name(name)
1242
+ match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1243
+
1244
+ if match_data
1245
+ rest = name[match_data[0].length, name.length]
1246
+ rest = rest[1, rest.length] if rest.start_with? "."
1247
+ [match_data[1], (rest.length > 0 ? rest : nil)]
1248
+ end
1249
+ end
1250
+
1251
+ def extract_table_ref_from_insert_sql(sql)
1252
+ sql[/into\s+([^\(]*).*values\s*\(/i]
1253
+ $1.strip if $1
1254
+ end
1255
+
1256
+ def table_definition
1257
+ TableDefinition.new(self)
1258
+ end
1259
+ end
1260
+ end
1261
+ end