sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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