Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Revision 9182, 35.3 kB (checked in by bitsweat, 5 months ago)

PostgreSQL: create_ and drop_database support. Closes #9042.

Line 
1 require 'active_record/connection_adapters/abstract_adapter'
2
3 begin
4   require_library_or_gem 'pg'
5 rescue LoadError => e
6   begin
7     require_library_or_gem 'postgres'
8     class PGresult
9       alias_method :nfields, :num_fields unless self.method_defined?(:nfields)
10       alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples)
11       alias_method :ftype, :type unless self.method_defined?(:ftype)
12       alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples)
13     end
14   rescue LoadError
15     raise e
16   end
17 end
18
19 module ActiveRecord
20   class Base
21     # Establishes a connection to the database that's used by all Active Record objects
22     def self.postgresql_connection(config) # :nodoc:
23       config = config.symbolize_keys
24       host     = config[:host]
25       port     = config[:port] || 5432
26       username = config[:username].to_s
27       password = config[:password].to_s
28
29       if config.has_key?(:database)
30         database = config[:database]
31       else
32         raise ArgumentError, "No database specified. Missing argument: database."
33       end
34
35       # The postgres drivers don't allow the creation of an unconnected PGconn object,
36       # so just pass a nil connection object for the time being.
37       ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
38     end
39   end
40
41   module ConnectionAdapters
42     # PostgreSQL-specific extensions to column definitions in a table.
43     class PostgreSQLColumn < Column #:nodoc:
44       # Instantiates a new PostgreSQL column definition in a table.
45       def initialize(name, default, sql_type = nil, null = true)
46         super(name, self.class.extract_value_from_default(default), sql_type, null)
47       end
48
49       private
50         # Extracts the scale from PostgreSQL-specific data types.
51         def extract_scale(sql_type)
52           # Money type has a fixed scale of 2.
53           sql_type =~ /^money/ ? 2 : super
54         end
55
56         # Extracts the precision from PostgreSQL-specific data types.
57         def extract_precision(sql_type)
58           # Actual code is defined dynamically in PostgreSQLAdapter.connect
59           # depending on the server specifics
60           super
61         end
62  
63         # Escapes binary strings for bytea input to the database.
64         def self.string_to_binary(value)
65           if PGconn.respond_to?(:escape_bytea)
66             self.class.module_eval do
67               define_method(:string_to_binary) do |value|
68                 PGconn.escape_bytea(value) if value
69               end
70             end
71           else
72             self.class.module_eval do
73               define_method(:string_to_binary) do |value|
74                 if value
75                   result = ''
76                   value.each_byte { |c| result << sprintf('\\\\%03o', c) }
77                   result
78                 end
79               end
80             end
81           end
82           self.class.string_to_binary(value)
83         end
84  
85         # Unescapes bytea output from a database to the binary string it represents.
86         def self.binary_to_string(value)
87           # In each case, check if the value actually is escaped PostgreSQL bytea output
88           # or an unescaped Active Record attribute that was just written.
89           if PGconn.respond_to?(:unescape_bytea)
90             self.class.module_eval do
91               define_method(:binary_to_string) do |value|
92                 if value =~ /\\\d{3}/
93                   PGconn.unescape_bytea(value)
94                 else
95                   value
96                 end
97               end
98             end
99           else
100             self.class.module_eval do
101               define_method(:binary_to_string) do |value|
102                 if value =~ /\\\d{3}/
103                   result = ''
104                   i, max = 0, value.size
105                   while i < max
106                     char = value[i]
107                     if char == ?\\
108                       if value[i+1] == ?\\
109                         char = ?\\
110                         i += 1
111                       else
112                         char = value[i+1..i+3].oct
113                         i += 3
114                       end
115                     end
116                     result << char
117                     i += 1
118                   end
119                   result
120                 else
121                   value
122                 end
123               end
124             end
125           end
126           self.class.binary_to_string(value)
127         end 
128  
129         # Maps PostgreSQL-specific data types to logical Rails types.
130         def simplified_type(field_type)
131           case field_type
132             # Numeric and monetary types
133             when /^(?:real|double precision)$/
134               :float
135             # Monetary types
136             when /^money$/
137               :decimal
138             # Character types
139             when /^(?:character varying|bpchar)(?:\(\d+\))?$/
140               :string
141             # Binary data types
142             when /^bytea$/
143               :binary
144             # Date/time types
145             when /^timestamp with(?:out)? time zone$/
146               :datetime
147             when /^interval$/
148               :string
149             # Geometric types
150             when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
151               :string
152             # Network address types
153             when /^(?:cidr|inet|macaddr)$/
154               :string
155             # Bit strings
156             when /^bit(?: varying)?(?:\(\d+\))?$/
157               :string
158             # XML type
159             when /^xml$/
160               :string
161             # Arrays
162             when /^\D+\[\]$/
163               :string             
164             # Object identifier types
165             when /^oid$/
166               :integer
167             # Pass through all types that are not specific to PostgreSQL.
168             else
169               super
170           end
171         end
172  
173         # Extracts the value from a PostgreSQL column default definition.
174         def self.extract_value_from_default(default)
175           case default
176             # Numeric types
177             when /\A-?\d+(\.\d*)?\z/
178               default
179             # Character types
180             when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
181               $1
182             # Character types (8.1 formatting)
183             when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
184               $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
185             # Binary data types
186             when /\A'(.*)'::bytea\z/m
187               $1
188             # Date/time types
189             when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
190               $1
191             when /\A'(.*)'::interval\z/
192               $1
193             # Boolean type
194             when 'true'
195               true
196             when 'false'
197               false
198             # Geometric types
199             when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
200               $1
201             # Network address types
202             when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
203               $1
204             # Bit string types
205             when /\AB'(.*)'::"?bit(?: varying)?"?\z/
206               $1
207             # XML type
208             when /\A'(.*)'::xml\z/m
209               $1
210             # Arrays
211             when /\A'(.*)'::"?\D+"?\[\]\z/
212               $1
213             # Object identifier types
214             when /\A-?\d+\z/
215               $1
216             else
217               # Anything else is blank, some user type, or some function
218               # and we can't know the value of that, so return nil.
219               nil
220           end
221         end
222     end
223   end
224
225   module ConnectionAdapters
226     # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
227     # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
228     #
229     # Options:
230     #
231     # * <tt>:host</tt> -- Defaults to localhost
232     # * <tt>:port</tt> -- Defaults to 5432
233     # * <tt>:username</tt> -- Defaults to nothing
234     # * <tt>:password</tt> -- Defaults to nothing
235     # * <tt>:database</tt> -- The name of the database. No default, must be provided.
236     # * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names.  This is backward-compatible with the :schema_order option.
237     # * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
238     # * <tt>:min_messages</tt> -- An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.
239     # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
240     class PostgreSQLAdapter < AbstractAdapter
241       # Returns 'PostgreSQL' as adapter name for identification purposes.
242       def adapter_name
243         'PostgreSQL'
244       end
245
246       # Initializes and connects a PostgreSQL adapter.
247       def initialize(connection, logger, connection_parameters, config)
248         super(connection, logger)
249         @connection_parameters, @config = connection_parameters, config
250
251         connect
252       end
253
254       # Is this connection alive and ready for queries?
255       def active?
256         if @connection.respond_to?(:status)
257           @connection.status == PGconn::CONNECTION_OK
258         else
259           # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
260           @connection.query 'SELECT 1'
261           true
262         end
263       # postgres-pr raises a NoMethodError when querying if no connection is available.
264       rescue PGError, NoMethodError
265         false
266       end
267
268       # Close then reopen the connection.
269       def reconnect!
270         if @connection.respond_to?(:reset)
271           @connection.reset
272           configure_connection
273         else
274           disconnect!
275           connect
276         end
277       end
278
279       # Close the connection.
280       def disconnect!
281         @connection.close rescue nil
282       end
283
284       def native_database_types #:nodoc:
285         {
286           :primary_key => "serial primary key",
287           :string      => { :name => "character varying", :limit => 255 },
288           :text        => { :name => "text" },
289           :integer     => { :name => "integer" },
290           :float       => { :name => "float" },
291           :decimal     => { :name => "decimal" },
292           :datetime    => { :name => "timestamp" },
293           :timestamp   => { :name => "timestamp" },
294           :time        => { :name => "time" },
295           :date        => { :name => "date" },
296           :binary      => { :name => "bytea" },
297           :boolean     => { :name => "boolean" }
298         }
299       end
300
301       # Does PostgreSQL support migrations?
302       def supports_migrations?
303         true
304       end
305
306       # Does PostgreSQL support standard conforming strings?
307       def supports_standard_conforming_strings?
308         # Temporarily set the client message level above error to prevent unintentional
309         # error messages in the logs when working on a PostgreSQL database server that
310         # does not support standard conforming strings.
311         client_min_messages_old = client_min_messages
312         self.client_min_messages = 'panic'
313
314         # postgres-pr does not raise an exception when client_min_messages is set higher
315         # than error and "SHOW standard_conforming_strings" fails, but returns an empty
316         # PGresult instead.
317         has_support = query('SHOW standard_conforming_strings')[0][0] rescue false
318         self.client_min_messages = client_min_messages_old
319         has_support
320       end
321
322       # Returns the configured supported identifier length supported by PostgreSQL,
323       # or report the default of 63 on PostgreSQL 7.x.
324       def table_alias_length
325         @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
326       end
327
328       # QUOTING ==================================================
329
330       # Quotes PostgreSQL-specific data types for SQL input.
331       def quote(value, column = nil) #:nodoc:
332         if value.kind_of?(String) && column && column.type == :binary
333           "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
334         elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
335           "xml '#{quote_string(value)}'"
336         elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
337           # Not truly string input, so doesn't require (or allow) escape string syntax.
338           "'#{value.to_s}'"
339         elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
340           case value
341             when /^[01]*$/
342               "B'#{value}'" # Bit-string notation
343             when /^[0-9A-F]*$/i
344               "X'#{value}'" # Hexadecimal notation
345           end
346         else
347           super
348         end
349       end
350
351       # Quotes strings for use in SQL input in the postgres driver for better performance.
352       def quote_string(s) #:nodoc:
353         if PGconn.respond_to?(:escape)
354           self.class.instance_eval do
355             define_method(:quote_string) do |s|
356               PGconn.escape(s)
357             end
358           end
359         else
360           # There are some incorrectly compiled postgres drivers out there
361           # that don't define PGconn.escape.
362           self.class.instance_eval do
363             undef_method(:quote_string)
364           end
365         end
366         quote_string(s)
367       end
368
369       # Quotes column names for use in SQL queries.
370       def quote_column_name(name) #:nodoc:
371         %("#{name}")
372       end
373
374       # Quote date/time values for use in SQL input. Includes microseconds
375       # if the value is a Time responding to usec.
376       def quoted_date(value) #:nodoc:
377         if value.acts_like?(:time) && value.respond_to?(:usec)
378           "#{super}.#{sprintf("%06d", value.usec)}"
379         else
380           super
381         end
382       end
383
384       # REFERENTIAL INTEGRITY ====================================
385
386       def supports_disable_referential_integrity?() #:nodoc:
387         version = query("SHOW server_version")[0][0].split('.')
388         (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
389       rescue
390         return false
391       end
392
393       def disable_referential_integrity(&block) #:nodoc:
394         if supports_disable_referential_integrity?() then
395           execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
396         end
397         yield
398       ensure
399         if supports_disable_referential_integrity?() then
400           execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
401         end
402       end
403
404       # DATABASE STATEMENTS ======================================
405
406       # Executes a SELECT query and returns an array of rows. Each row is an
407       # array of field values.
408       def select_rows(sql, name = nil)
409         select_raw(sql, name).last
410       end
411
412       # Executes an INSERT query and returns the new record's ID
413       def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
414         table = sql.split(" ", 4)[2].gsub('"', '')
415         super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
416       end
417
418       # create a 2D array representing the result set
419       def result_as_array(res) #:nodoc:
420         ary = []
421         for i in 0...res.ntuples do
422           ary << []
423           for j in 0...res.nfields do
424             ary[i] << res.getvalue(i,j)
425           end
426         end
427         return ary
428       end
429
430
431       # Queries the database and returns the results in an Array-like object
432       def query(sql, name = nil) #:nodoc:
433         log(sql, name) do
434           if @async
435             res = @connection.async_exec(sql)
436           else
437             res = @connection.exec(sql)
438           end
439           return result_as_array(res)
440         end
441       end
442
443       # Executes an SQL statement, returning a PGresult object on success
444       # or raising a PGError exception otherwise.
445       def execute(sql, name = nil)
446         log(sql, name) do
447           if @async
448             @connection.async_exec(sql)
449           else
450             @connection.exec(sql)
451           end
452         end
453       end
454
455       # Executes an UPDATE query and returns the number of affected tuples.
456       def update_sql(sql, name = nil)
457         super.cmd_tuples
458       end
459
460       # Begins a transaction.
461       def begin_db_transaction
462         execute "BEGIN"
463       end
464
465       # Commits a transaction.
466       def commit_db_transaction
467         execute "COMMIT"
468       end
469
470       # Aborts a transaction.
471       def rollback_db_transaction
472         execute "ROLLBACK"
473       end
474
475       # SCHEMA STATEMENTS ========================================
476
477       def recreate_database(name) #:nodoc:
478         drop_database(name)
479         create_database(name)
480       end
481
482       # Create a new PostgreSQL database.  Options include :owner, :template,
483       # :encoding, :tablespace, and :connection_limit (note that MySQL uses
484       # :charset while PostgreSQL uses :encoding).
485       #
486       # Example:
487       #   create_database config[:database], config
488       #   create_database 'foo_development', :encoding => 'unicode'
489       def create_database(name, options = {})
490         options = options.reverse_merge(:encoding => "utf8")
491
492         option_string = options.symbolize_keys.sum do |key, value|
493           case key
494           when :owner
495             " OWNER = '#{value}'"
496           when :template
497             " TEMPLATE = #{value}"
498           when :encoding
499             " ENCODING = '#{value}'"
500           when :tablespace
501             " TABLESPACE = #{value}"
502           when :connection_limit
503             " CONNECTION LIMIT = #{value}"
504           else
505             ""
506           end
507         end
508
509         execute "CREATE DATABASE #{name}#{option_string}"
510       end
511
512       # Drops a PostgreSQL database
513       #
514       # Example:
515       #   drop_database 'matt_development'
516       def drop_database(name) #:nodoc:
517         execute "DROP DATABASE IF EXISTS #{name}"
518       end
519
520
521       # Returns the list of all tables in the schema search path or a specified schema.
522       def tables(name = nil)
523         schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
524         query(<<-SQL, name).map { |row| row[0] }
525           SELECT tablename
526             FROM pg_tables
527            WHERE schemaname IN (#{schemas})
528         SQL
529       end
530
531       # Returns the list of all indexes for a table.
532       def indexes(table_name, name = nil)
533          schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
534          result = query(<<-SQL, name)
535            SELECT distinct i.relname, d.indisunique, a.attname
536              FROM pg_class t, pg_class i, pg_index d, pg_attribute a
537            WHERE i.relkind = 'i'
538              AND d.indexrelid = i.oid
539              AND d.indisprimary = 'f'
540              AND t.oid = d.indrelid
541              AND t.relname = '#{table_name}'
542              AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
543              AND a.attrelid = t.