2007-01-22 14:43:50 +01:00
require 'active_record/connection_adapters/abstract_adapter'
module ActiveRecord
class Base
# Establishes a connection to the database that's used by all Active Record objects
def self . postgresql_connection ( config ) # :nodoc:
require_library_or_gem 'postgres' unless self . class . const_defined? ( :PGconn )
config = config . symbolize_keys
host = config [ :host ]
2007-02-09 09:04:31 +01:00
port = config [ :port ] || 5432
2007-01-22 14:43:50 +01:00
username = config [ :username ] . to_s
password = config [ :password ] . to_s
if config . has_key? ( :database )
database = config [ :database ]
else
raise ArgumentError , " No database specified. Missing argument: database. "
end
2007-12-21 08:48:59 +01:00
# The postgres drivers don't allow the creation of an unconnected PGconn object,
# so just pass a nil connection object for the time being.
ConnectionAdapters :: PostgreSQLAdapter . new ( nil , logger , [ host , port , nil , nil , database , username , password ] , config )
end
end
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
def initialize ( name , default , sql_type = nil , null = true )
super ( name , self . class . extract_value_from_default ( default ) , sql_type , null )
end
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
private
# Extracts the scale from PostgreSQL-specific data types.
def extract_scale ( sql_type )
# Money type has a fixed scale of 2.
sql_type =~ / ^money / ? 2 : super
end
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
# Extracts the precision from PostgreSQL-specific data types.
def extract_precision ( sql_type )
# Actual code is defined dynamically in PostgreSQLAdapter.connect
# depending on the server specifics
super
end
# Escapes binary strings for bytea input to the database.
def self . string_to_binary ( value )
if PGconn . respond_to? ( :escape_bytea )
self . class . module_eval do
define_method ( :string_to_binary ) do | value |
PGconn . escape_bytea ( value ) if value
end
end
else
self . class . module_eval do
define_method ( :string_to_binary ) do | value |
if value
result = ''
value . each_byte { | c | result << sprintf ( '\\\\%03o' , c ) }
result
end
end
end
end
self . class . string_to_binary ( value )
end
# Unescapes bytea output from a database to the binary string it represents.
def self . binary_to_string ( value )
# In each case, check if the value actually is escaped PostgreSQL bytea output
# or an unescaped Active Record attribute that was just written.
if PGconn . respond_to? ( :unescape_bytea )
self . class . module_eval do
define_method ( :binary_to_string ) do | value |
if value =~ / \\ \ d{3} /
PGconn . unescape_bytea ( value )
else
value
end
end
end
else
self . class . module_eval do
define_method ( :binary_to_string ) do | value |
if value =~ / \\ \ d{3} /
result = ''
i , max = 0 , value . size
while i < max
char = value [ i ]
if char == ?\\
if value [ i + 1 ] == ?\\
char = ?\\
i += 1
else
char = value [ i + 1 .. i + 3 ] . oct
i += 3
end
end
result << char
i += 1
end
result
else
value
end
end
end
end
self . class . binary_to_string ( value )
end
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type ( field_type )
case field_type
# Numeric and monetary types
when / ^(?:real|double precision)$ /
:float
# Monetary types
when / ^money$ /
:decimal
# Character types
when / ^(?:character varying|bpchar)(?: \ ( \ d+ \ ))?$ /
:string
# Binary data types
when / ^bytea$ /
:binary
# Date/time types
when / ^timestamp with(?:out)? time zone$ /
:datetime
when / ^interval$ /
:string
# Geometric types
when / ^(?:point|line|lseg|box|"?path"?|polygon|circle)$ /
:string
# Network address types
when / ^(?:cidr|inet|macaddr)$ /
:string
# Bit strings
when / ^bit(?: varying)?(?: \ ( \ d+ \ ))?$ /
:string
# XML type
when / ^xml$ /
:string
# Arrays
when / ^ \ D+ \ [ \ ]$ /
:string
# Object identifier types
when / ^oid$ /
:integer
# Pass through all types that are not specific to PostgreSQL.
else
super
end
end
# Extracts the value from a PostgreSQL column default definition.
def self . extract_value_from_default ( default )
case default
# Numeric types
when / \ A-? \ d+( \ . \ d*)? \ z /
default
# Character types
when / \ A'(.*)'::(?:character varying|bpchar|text) \ z /m
$1
# Character types (8.1 formatting)
when / \ AE'(.*)'::(?:character varying|bpchar|text) \ z /m
$1 . gsub ( / \\ ( \ d \ d \ d) / ) { $1 . oct . chr }
# Binary data types
when / \ A'(.*)'::bytea \ z /m
$1
# Date/time types
when / \ A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date) \ z /
$1
when / \ A'(.*)'::interval \ z /
$1
# Boolean type
when 'true'
true
when 'false'
false
# Geometric types
when / \ A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle) \ z /
$1
# Network address types
when / \ A'(.*)'::(?:cidr|inet|macaddr) \ z /
$1
# Bit string types
when / \ AB'(.*)'::"?bit(?: varying)?"? \ z /
$1
# XML type
when / \ A'(.*)'::xml \ z /m
$1
# Arrays
when / \ A'(.*)'::"? \ D+"? \ [ \ ] \ z /
$1
# Object identifier types
when / \ A-? \ d+ \ z /
$1
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
end
end
2007-01-22 14:43:50 +01:00
end
end
module ConnectionAdapters
2007-12-21 08:48:59 +01:00
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
2007-01-22 14:43:50 +01:00
#
# Options:
#
# * <tt>:host</tt> -- Defaults to localhost
# * <tt>:port</tt> -- Defaults to 5432
# * <tt>:username</tt> -- Defaults to nothing
# * <tt>:password</tt> -- Defaults to nothing
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
# * <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.
2007-12-21 08:48:59 +01:00
# * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
# * <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.
2007-02-09 09:04:31 +01:00
# * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
2007-01-22 14:43:50 +01:00
class PostgreSQLAdapter < AbstractAdapter
2007-12-21 08:48:59 +01:00
# Returns 'PostgreSQL' as adapter name for identification purposes.
2007-01-22 14:43:50 +01:00
def adapter_name
'PostgreSQL'
end
2007-12-21 08:48:59 +01:00
# Initializes and connects a PostgreSQL adapter.
def initialize ( connection , logger , connection_parameters , config )
2007-01-22 14:43:50 +01:00
super ( connection , logger )
2007-12-21 08:48:59 +01:00
@connection_parameters , @config = connection_parameters , config
connect
2007-01-22 14:43:50 +01:00
end
# Is this connection alive and ready for queries?
def active?
if @connection . respond_to? ( :status )
@connection . status == PGconn :: CONNECTION_OK
else
2007-12-21 08:48:59 +01:00
# We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
2007-01-22 14:43:50 +01:00
@connection . query 'SELECT 1'
true
end
2007-12-21 08:48:59 +01:00
# postgres-pr raises a NoMethodError when querying if no connection is available.
2007-01-22 14:43:50 +01:00
rescue PGError , NoMethodError
2007-02-09 09:04:31 +01:00
false
2007-01-22 14:43:50 +01:00
end
# Close then reopen the connection.
def reconnect!
if @connection . respond_to? ( :reset )
@connection . reset
configure_connection
2007-12-21 08:48:59 +01:00
else
disconnect!
connect
2007-01-22 14:43:50 +01:00
end
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
# Close the connection.
2007-01-22 14:43:50 +01:00
def disconnect!
@connection . close rescue nil
end
2007-12-21 08:48:59 +01:00
def native_database_types #:nodoc:
2007-01-22 14:43:50 +01:00
{
:primary_key = > " serial primary key " ,
:string = > { :name = > " character varying " , :limit = > 255 } ,
:text = > { :name = > " text " } ,
:integer = > { :name = > " integer " } ,
:float = > { :name = > " float " } ,
2007-02-09 09:04:31 +01:00
:decimal = > { :name = > " decimal " } ,
2007-01-22 14:43:50 +01:00
:datetime = > { :name = > " timestamp " } ,
:timestamp = > { :name = > " timestamp " } ,
:time = > { :name = > " time " } ,
:date = > { :name = > " date " } ,
:binary = > { :name = > " bytea " } ,
:boolean = > { :name = > " boolean " }
}
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
# Does PostgreSQL support migrations?
2007-01-22 14:43:50 +01:00
def supports_migrations?
true
2007-02-09 09:04:31 +01:00
end
2007-12-21 08:48:59 +01:00
# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
# error messages in the logs when working on a PostgreSQL database server that
# does not support standard conforming strings.
client_min_messages_old = client_min_messages
self . client_min_messages = 'panic'
# postgres-pr does not raise an exception when client_min_messages is set higher
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
# PGresult instead.
has_support = execute ( 'SHOW standard_conforming_strings' ) [ 0 ] [ 0 ] rescue false
self . client_min_messages = client_min_messages_old
has_support
end
# Returns the configured supported identifier length supported by PostgreSQL,
# or report the default of 63 on PostgreSQL 7.x.
2007-01-22 14:43:50 +01:00
def table_alias_length
2007-12-21 08:48:59 +01:00
@table_alias_length || = ( postgresql_version > = 80000 ? query ( 'SHOW max_identifier_length' ) [ 0 ] [ 0 ] . to_i : 63 )
2007-01-22 14:43:50 +01:00
end
# QUOTING ==================================================
2007-12-21 08:48:59 +01:00
# Quotes PostgreSQL-specific data types for SQL input.
def quote ( value , column = nil ) #:nodoc:
2007-01-22 14:43:50 +01:00
if value . kind_of? ( String ) && column && column . type == :binary
2007-12-21 08:48:59 +01:00
" #{ quoted_string_prefix } ' #{ column . class . string_to_binary ( value ) } ' "
elsif value . kind_of? ( String ) && column && column . sql_type =~ / ^xml$ /
" xml ' #{ quote_string ( value ) } ' "
elsif value . kind_of? ( Numeric ) && column && column . sql_type =~ / ^money$ /
# Not truly string input, so doesn't require (or allow) escape string syntax.
" ' #{ value . to_s } ' "
elsif value . kind_of? ( String ) && column && column . sql_type =~ / ^bit /
case value
when / ^[01]*$ /
" B' #{ value } ' " # Bit-string notation
when / ^[0-9A-F]*$ /i
" X' #{ value } ' " # Hexadecimal notation
end
2007-01-22 14:43:50 +01:00
else
super
end
end
2007-12-21 08:48:59 +01:00
# Quotes strings for use in SQL input in the postgres driver for better performance.
def quote_string ( s ) #:nodoc:
if PGconn . respond_to? ( :escape )
self . class . instance_eval do
define_method ( :quote_string ) do | s |
PGconn . escape ( s )
end
end
else
# There are some incorrectly compiled postgres drivers out there
# that don't define PGconn.escape.
self . class . instance_eval do
undef_method ( :quote_string )
end
end
quote_string ( s )
end
# Quotes column names for use in SQL queries.
def quote_column_name ( name ) #:nodoc:
2007-01-22 14:43:50 +01:00
%( " #{ name } " )
end
2007-12-21 08:48:59 +01:00
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date ( value ) #:nodoc:
if value . acts_like? ( :time ) && value . respond_to? ( :usec )
" #{ super } . #{ sprintf ( " %06d " , value . usec ) } "
else
super
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity ( & block ) #:nodoc:
execute ( tables . collect { | name | " ALTER TABLE #{ quote_table_name ( name ) } DISABLE TRIGGER ALL " } . join ( " ; " ) )
yield
ensure
execute ( tables . collect { | name | " ALTER TABLE #{ quote_table_name ( name ) } ENABLE TRIGGER ALL " } . join ( " ; " ) )
end
2007-02-09 09:04:31 +01:00
# DATABASE STATEMENTS ======================================
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
def select_rows ( sql , name = nil )
select_raw ( sql , name ) . last
end
# Executes an INSERT query and returns the new record's ID
def insert ( sql , name = nil , pk = nil , id_value = nil , sequence_name = nil )
2007-01-22 14:43:50 +01:00
table = sql . split ( " " , 4 ) [ 2 ]
2007-12-21 08:48:59 +01:00
super || last_insert_id ( table , sequence_name || default_sequence_name ( table , pk ) )
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Queries the database and returns the results in an Array or nil otherwise.
2007-01-22 14:43:50 +01:00
def query ( sql , name = nil ) #:nodoc:
2007-02-09 09:04:31 +01:00
log ( sql , name ) do
if @async
@connection . async_query ( sql )
else
@connection . query ( sql )
end
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Executes an SQL statement, returning a PGresult object on success
# or raising a PGError exception otherwise.
def execute ( sql , name = nil )
2007-02-09 09:04:31 +01:00
log ( sql , name ) do
if @async
@connection . async_exec ( sql )
else
@connection . exec ( sql )
end
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Executes an UPDATE query and returns the number of affected tuples.
def update_sql ( sql , name = nil )
super . cmdtuples
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Begins a transaction.
def begin_db_transaction
2007-01-22 14:43:50 +01:00
execute " BEGIN "
end
2007-12-21 08:48:59 +01:00
# Commits a transaction.
def commit_db_transaction
2007-01-22 14:43:50 +01:00
execute " COMMIT "
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
# Aborts a transaction.
def rollback_db_transaction
2007-01-22 14:43:50 +01:00
execute " ROLLBACK "
end
# SCHEMA STATEMENTS ========================================
2007-12-21 08:48:59 +01:00
# Returns the list of all tables in the schema search path or a specified schema.
def tables ( name = nil )
2007-01-22 14:43:50 +01:00
schemas = schema_search_path . split ( / , / ) . map { | p | quote ( p ) } . join ( ',' )
query ( <<-SQL, name).map { |row| row[0] }
SELECT tablename
FROM pg_tables
WHERE schemaname IN ( #{schemas})
SQL
end
2007-12-21 08:48:59 +01:00
# Returns the list of all indexes for a table.
def indexes ( table_name , name = nil )
2007-01-22 14:43:50 +01:00
result = query ( <<-SQL, name)
SELECT i . relname , d . indisunique , a . attname
FROM pg_class t , pg_class i , pg_index d , pg_attribute a
WHERE i . relkind = 'i'
AND d . indexrelid = i . oid
AND d . indisprimary = 'f'
AND t . oid = d . indrelid
AND t . relname = '#{table_name}'
AND a . attrelid = t . oid
AND ( d . indkey [ 0 ] = a . attnum OR d . indkey [ 1 ] = a . attnum
OR d . indkey [ 2 ] = a . attnum OR d . indkey [ 3 ] = a . attnum
OR d . indkey [ 4 ] = a . attnum OR d . indkey [ 5 ] = a . attnum
OR d . indkey [ 6 ] = a . attnum OR d . indkey [ 7 ] = a . attnum
OR d . indkey [ 8 ] = a . attnum OR d . indkey [ 9 ] = a . attnum )
ORDER BY i . relname
SQL
current_index = nil
indexes = [ ]
result . each do | row |
if current_index != row [ 0 ]
indexes << IndexDefinition . new ( table_name , row [ 0 ] , row [ 1 ] == " t " , [ ] )
current_index = row [ 0 ]
end
indexes . last . columns << row [ 2 ]
end
indexes
end
2007-12-21 08:48:59 +01:00
# Returns the list of all column definitions for a table.
def columns ( table_name , name = nil )
# Limit, precision, and scale are all handled by the superclass.
column_definitions ( table_name ) . collect do | name , type , default , notnull |
PostgreSQLColumn . new ( name , default , type , notnull == 'f' )
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
#
# This should be not be called manually but set in database.yml.
def schema_search_path = ( schema_csv )
2007-01-22 14:43:50 +01:00
if schema_csv
execute " SET search_path TO #{ schema_csv } "
2007-12-21 08:48:59 +01:00
@schema_search_path = schema_csv
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
# Returns the active schema search path.
def schema_search_path
2007-01-22 14:43:50 +01:00
@schema_search_path || = query ( 'SHOW search_path' ) [ 0 ] [ 0 ]
end
2007-12-21 08:48:59 +01:00
# Returns the current client message level.
def client_min_messages
query ( 'SHOW client_min_messages' ) [ 0 ] [ 0 ]
end
# Set the client message level.
def client_min_messages = ( level )
execute ( " SET client_min_messages TO ' #{ level } ' " )
end
# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name ( table_name , pk = nil ) #:nodoc:
2007-01-22 14:43:50 +01:00
default_pk , default_seq = pk_and_sequence_for ( table_name )
default_seq || " #{ table_name } _ #{ pk || default_pk || 'id' } _seq "
end
2007-12-21 08:48:59 +01:00
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence! ( table , pk = nil , sequence = nil ) #:nodoc:
2007-01-22 14:43:50 +01:00
unless pk and sequence
default_pk , default_sequence = pk_and_sequence_for ( table )
pk || = default_pk
sequence || = default_sequence
end
if pk
if sequence
select_value <<-end_sql, 'Reset sequence'
SELECT setval ( '#{sequence}' , ( SELECT COALESCE ( MAX ( #{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
end_sql
else
@logger . warn " #{ table } has primary key #{ pk } with no default sequence " if @logger
end
end
end
2007-12-21 08:48:59 +01:00
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for ( table ) #:nodoc:
2007-01-22 14:43:50 +01:00
# First try looking for a sequence with a dependency on the
# given table's primary key.
2007-02-09 09:04:31 +01:00
result = query ( <<-end_sql, 'PK and serial sequence')[0]
2007-12-21 08:48:59 +01:00
SELECT attr . attname , seq . relname
2007-01-22 14:43:50 +01:00
FROM pg_class seq ,
pg_attribute attr ,
pg_depend dep ,
pg_namespace name ,
pg_constraint cons
WHERE seq . oid = dep . objid
AND seq . relkind = 'S'
AND attr . attrelid = dep . refobjid
AND attr . attnum = dep . refobjsubid
AND attr . attrelid = cons . conrelid
AND attr . attnum = cons . conkey [ 1 ]
AND cons . contype = 'p'
AND dep . refobjid = '#{table}' :: regclass
end_sql
if result . nil? or result . empty?
# If that fails, try parsing the primary key's default value.
# Support the 7.x and 8.0 nextval('foo'::text) as well as
# the 8.1+ nextval('foo'::regclass).
2007-02-09 09:04:31 +01:00
result = query ( <<-end_sql, 'PK and custom sequence')[0]
2007-12-21 08:48:59 +01:00
SELECT attr . attname , split_part ( def . adsrc , '' '' , 2 )
2007-01-22 14:43:50 +01:00
FROM pg_class t
JOIN pg_attribute attr ON ( t . oid = attrelid )
JOIN pg_attrdef def ON ( adrelid = attrelid AND adnum = attnum )
JOIN pg_constraint cons ON ( conrelid = adrelid AND adnum = conkey [ 1 ] )
WHERE t . oid = '#{table}' :: regclass
AND cons . contype = 'p'
AND def . adsrc ~ * 'nextval'
end_sql
end
2007-12-21 08:48:59 +01:00
# [primary_key, sequence]
2007-02-09 09:04:31 +01:00
[ result . first , result . last ]
2007-01-22 14:43:50 +01:00
rescue
nil
end
2007-12-21 08:48:59 +01:00
# Renames a table.
2007-01-22 14:43:50 +01:00
def rename_table ( name , new_name )
execute " ALTER TABLE #{ name } RENAME TO #{ new_name } "
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
# Adds a column to a table.
2007-01-22 14:43:50 +01:00
def add_column ( table_name , column_name , type , options = { } )
2007-02-09 09:04:31 +01:00
default = options [ :default ]
notnull = options [ :null ] == false
# Add the column.
2007-12-21 08:48:59 +01:00
execute ( " ALTER TABLE #{ table_name } ADD COLUMN #{ quote_column_name ( column_name ) } #{ type_to_sql ( type , options [ :limit ] ) } " )
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
change_column_default ( table_name , column_name , default ) if options_include_default? ( options )
change_column_null ( table_name , column_name , false , default ) if notnull
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Changes the column of a table.
def change_column ( table_name , column_name , type , options = { } )
2007-01-22 14:43:50 +01:00
begin
2007-12-21 08:48:59 +01:00
execute " ALTER TABLE #{ table_name } ALTER COLUMN #{ quote_column_name ( column_name ) } TYPE #{ type_to_sql ( type , options [ :limit ] , options [ :precision ] , options [ :scale ] ) } "
2007-01-22 14:43:50 +01:00
rescue ActiveRecord :: StatementInvalid
2007-12-21 08:48:59 +01:00
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
2007-01-22 14:43:50 +01:00
begin_db_transaction
2007-12-21 08:48:59 +01:00
tmp_column_name = " #{ column_name } _ar_tmp "
add_column ( table_name , tmp_column_name , type , options )
execute " UPDATE #{ table_name } SET #{ quote_column_name ( tmp_column_name ) } = CAST( #{ quote_column_name ( column_name ) } AS #{ type_to_sql ( type , options [ :limit ] , options [ :precision ] , options [ :scale ] ) } ) "
2007-01-22 14:43:50 +01:00
remove_column ( table_name , column_name )
2007-12-21 08:48:59 +01:00
rename_column ( table_name , tmp_column_name , column_name )
2007-01-22 14:43:50 +01:00
commit_db_transaction
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
change_column_default ( table_name , column_name , options [ :default ] ) if options_include_default? ( options )
change_column_null ( table_name , column_name , options [ :null ] , options [ :default ] ) if options . key? ( :null )
2007-02-09 09:04:31 +01:00
end
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
# Changes the default value of a table column.
def change_column_default ( table_name , column_name , default )
2007-02-09 09:04:31 +01:00
execute " ALTER TABLE #{ table_name } ALTER COLUMN #{ quote_column_name ( column_name ) } SET DEFAULT #{ quote ( default ) } "
2007-01-22 14:43:50 +01:00
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
def change_column_null ( table_name , column_name , null , default = nil )
unless null || default . nil?
execute ( " UPDATE #{ table_name } SET #{ quote_column_name ( column_name ) } = #{ quote ( default ) } WHERE #{ quote_column_name ( column_name ) } IS NULL " )
end
execute ( " ALTER TABLE #{ table_name } ALTER #{ quote_column_name ( column_name ) } #{ null ? 'DROP' : 'SET' } NOT NULL " )
end
# Renames a column in a table.
def rename_column ( table_name , column_name , new_column_name )
2007-02-09 09:04:31 +01:00
execute " ALTER TABLE #{ table_name } RENAME COLUMN #{ quote_column_name ( column_name ) } TO #{ quote_column_name ( new_column_name ) } "
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Drops an index from a table.
def remove_index ( table_name , options = { } )
2007-01-22 14:43:50 +01:00
execute " DROP INDEX #{ index_name ( table_name , options ) } "
end
2007-12-21 08:48:59 +01:00
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql ( type , limit = nil , precision = nil , scale = nil )
2007-02-09 09:04:31 +01:00
return super unless type . to_s == 'integer'
if limit . nil? || limit == 4
'integer'
elsif limit < 4
'smallint'
else
'bigint'
end
end
2007-12-21 08:48:59 +01:00
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
2007-02-09 09:04:31 +01:00
#
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
#
# distinct("posts.id", "posts.created_at desc")
2007-12-21 08:48:59 +01:00
def distinct ( columns , order_by ) #:nodoc:
2007-02-09 09:04:31 +01:00
return " DISTINCT #{ columns } " if order_by . blank?
2007-12-21 08:48:59 +01:00
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
2007-02-09 09:04:31 +01:00
order_columns = order_by . split ( ',' ) . collect { | s | s . split . first }
order_columns . delete_if & :blank?
order_columns = order_columns . zip ( ( 0 ... order_columns . size ) . to_a ) . map { | s , i | " #{ s } AS alias_ #{ i } " }
2007-12-21 08:48:59 +01:00
# Return a DISTINCT ON() clause that's distinct on the columns we want but includes
# all the required columns for the ORDER BY to work properly.
2007-02-09 09:04:31 +01:00
sql = " DISTINCT ON ( #{ columns } ) #{ columns } , "
sql << order_columns * ', '
end
2007-12-21 08:48:59 +01:00
# Returns an ORDER BY clause for the passed order option.
2007-02-09 09:04:31 +01:00
#
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the sql as a sub-select and ordering in that query.
2007-12-21 08:48:59 +01:00
def add_order_by_for_association_limiting! ( sql , options ) #:nodoc:
2007-02-09 09:04:31 +01:00
return sql if options [ :order ] . blank?
order = options [ :order ] . split ( ',' ) . collect { | s | s . strip } . reject ( & :blank? )
order . map! { | s | 'DESC' if s =~ / \ bdesc$ /i }
order = order . zip ( ( 0 ... order . size ) . to_a ) . map { | s , i | " id_list.alias_ #{ i } #{ s } " } . join ( ', ' )
sql . replace " SELECT * FROM ( #{ sql } ) AS id_list ORDER BY #{ order } "
end
2007-12-21 08:48:59 +01:00
protected
# Returns the version of the connected PostgreSQL version.
def postgresql_version
@postgresql_version || =
if @connection . respond_to? ( :server_version )
@connection . server_version
else
# Mimic PGconn.server_version behavior
begin
query ( 'SELECT version()' ) [ 0 ] [ 0 ] =~ / PostgreSQL ( \ d+) \ .( \ d+) \ .( \ d+) /
( $1 . to_i * 10000 ) + ( $2 . to_i * 100 ) + $3 . to_i
rescue
0
end
end
end
2007-01-22 14:43:50 +01:00
private
2007-12-21 08:48:59 +01:00
# The internal PostgreSQL identifer of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
def connect
@connection = PGconn . connect ( * @connection_parameters )
PGconn . translate_results = false if PGconn . respond_to? ( :translate_results = )
# Ignore async_exec and async_query when using postgres-pr.
@async = @config [ :allow_concurrency ] && @connection . respond_to? ( :async_exec )
# Use escape string syntax if available. We cannot do this lazily when encountering
# the first string, because that could then break any transactions in progress.
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
# support escape string syntax. Don't override the inherited quoted_string_prefix.
if supports_standard_conforming_strings?
self . class . instance_eval do
define_method ( :quoted_string_prefix ) { 'E' }
end
end
2007-01-22 14:43:50 +01:00
2007-12-21 08:48:59 +01:00
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
# should know about this but can't detect it there, so deal with it here.
money_precision = ( postgresql_version > = 80300 ) ? 19 : 10
PostgreSQLColumn . module_eval ( <<-end_eval)
def extract_precision ( sql_type )
if sql_type =~ / ^money$ /
#{money_precision}
else
super
end
end
end_eval
configure_connection
end
# Configures the encoding, verbosity, and schema search path of the connection.
# This is called by #connect and should not be called manually.
2007-01-22 14:43:50 +01:00
def configure_connection
if @config [ :encoding ]
2007-12-21 08:48:59 +01:00
if @connection . respond_to? ( :set_client_encoding )
@connection . set_client_encoding ( @config [ :encoding ] )
else
execute ( " SET client_encoding TO ' #{ @config [ :encoding ] } ' " )
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
self . client_min_messages = @config [ :min_messages ] if @config [ :min_messages ]
self . schema_search_path = @config [ :schema_search_path ] || @config [ :schema_order ]
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
# Returns the current ID of a table's sequence.
def last_insert_id ( table , sequence_name ) #:nodoc:
2007-01-22 14:43:50 +01:00
Integer ( select_value ( " SELECT currval(' #{ sequence_name } ') " ) )
end
2007-12-21 08:48:59 +01:00
# Executes a SELECT query and returns the results, performing any data type
# conversions that are required to be performed here instead of in PostgreSQLColumn.
2007-01-22 14:43:50 +01:00
def select ( sql , name = nil )
2007-12-21 08:48:59 +01:00
fields , rows = select_raw ( sql , name )
result = [ ]
for row in rows
row_hash = { }
fields . each_with_index do | f , i |
row_hash [ f ] = row [ i ]
end
result << row_hash
end
result
end
def select_raw ( sql , name = nil )
2007-01-22 14:43:50 +01:00
res = execute ( sql , name )
results = res . result
2007-12-21 08:48:59 +01:00
fields = [ ]
2007-01-22 14:43:50 +01:00
rows = [ ]
if results . length > 0
fields = res . fields
results . each do | row |
hashed_row = { }
2007-12-21 08:48:59 +01:00
row . each_index do | cell_index |
# If this is a money type column and there are any currency symbols,
# then strip them off. Indeed it would be prettier to do this in
# PostgreSQLColumn.string_to_decimal but would break form input
# fields that call value_before_type_cast.
if res . type ( cell_index ) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
# (1) $12,345,678.12
# (2) $12.345.678,12
case column = row [ cell_index ]
when / ^-? \ D+[ \ d,]+ \ . \ d{2}$ / # (1)
row [ cell_index ] = column . gsub ( / [^- \ d \ .] / , '' )
when / ^-? \ D+[ \ d \ .]+, \ d{2}$ / # (2)
row [ cell_index ] = column . gsub ( / [^- \ d,] / , '' ) . sub ( / , / , '.' )
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
hashed_row [ fields [ cell_index ] ] = column
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
rows << row
2007-01-22 14:43:50 +01:00
end
end
2007-02-09 09:04:31 +01:00
res . clear
2007-12-21 08:48:59 +01:00
return fields , rows
2007-01-22 14:43:50 +01:00
end
2007-02-09 09:04:31 +01:00
2007-12-21 08:48:59 +01:00
# Returns the list of a table's column names, data types, and default values.
2007-01-22 14:43:50 +01:00
#
# The underlying query is roughly:
# SELECT column.name, column.type, default.value
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
# WHERE column.table_id = get_table_id('table_name')
# AND column.num > 0
# AND NOT column.is_dropped
# ORDER BY column.num
#
# If the table name is not prefixed with a schema, the database will
# take the first match from the schema search path.
#
# Query implementation notes:
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
2007-12-21 08:48:59 +01:00
def column_definitions ( table_name ) #:nodoc:
2007-01-22 14:43:50 +01:00
query <<-end_sql
SELECT a . attname , format_type ( a . atttypid , a . atttypmod ) , d . adsrc , a . attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a . attrelid = d . adrelid AND a . attnum = d . adnum
WHERE a . attrelid = '#{table_name}' :: regclass
AND a . attnum > 0 AND NOT a . attisdropped
ORDER BY a . attnum
end_sql
end
end
end
end