2016-03-03 07:09:28 -05:00
# rubocop:disable all
2016-03-03 06:35:06 -05:00
# These changes add support for PostgreSQL operator classes when creating
# indexes and dumping/loading schemas. Taken from Rails pull request
# https://github.com/rails/rails/pull/19090.
#
# License:
#
# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'date'
require 'set'
require 'bigdecimal'
require 'bigdecimal/util'
# As the Struct definition is changed in this PR/patch we have to first remove
# the existing one.
ActiveRecord :: ConnectionAdapters . send ( :remove_const , :IndexDefinition )
module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
2018-12-07 13:15:06 -05:00
attrs = [ :table , :name , :unique , :columns , :lengths , :orders , :where , :type , :using , :comment , :opclasses ]
2018-06-09 13:58:58 -04:00
class IndexDefinition < Struct . new ( * attrs ) #:nodoc:
2016-03-03 06:35:06 -05:00
end
end
end
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
def add_index_options ( table_name , column_name , options = { } ) #:nodoc:
column_names = Array ( column_name )
index_name = index_name ( table_name , column : column_names )
options . assert_valid_keys ( :unique , :order , :name , :where , :length , :internal , :using , :algorithm , :type , :opclasses )
index_type = options [ :unique ] ? " UNIQUE " : " "
index_type = options [ :type ] . to_s if options . key? ( :type )
index_name = options [ :name ] . to_s if options . key? ( :name )
max_index_length = options . fetch ( :internal , false ) ? index_name_length : allowed_index_name_length
if options . key? ( :algorithm )
algorithm = index_algorithms . fetch ( options [ :algorithm ] ) {
raise ArgumentError . new ( " Algorithm must be one of the following: #{ index_algorithms . keys . map ( & :inspect ) . join ( ', ' ) } " )
}
end
using = " USING #{ options [ :using ] } " if options [ :using ] . present?
if supports_partial_index?
index_options = options [ :where ] ? " WHERE #{ options [ :where ] } " : " "
end
if index_name . length > max_index_length
raise ArgumentError , " Index name ' #{ index_name } ' on table ' #{ table_name } ' is too long; the limit is #{ max_index_length } characters "
end
2019-01-15 16:05:36 -05:00
if data_source_exists? ( table_name ) && index_name_exists? ( table_name , index_name )
2016-03-03 06:35:06 -05:00
raise ArgumentError , " Index name ' #{ index_name } ' on table ' #{ table_name } ' already exists "
end
index_columns = quoted_columns_for_index ( column_names , options ) . join ( " , " )
[ index_name , index_type , index_columns , index_options , algorithm , using ]
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module SchemaStatements
# Returns an array of indexes for the given table.
def indexes ( table_name , name = nil )
result = query ( <<-SQL, 'SCHEMA')
SELECT distinct i . relname , d . indisunique , d . indkey , pg_get_indexdef ( d . indexrelid ) , t . oid
FROM pg_class t
INNER JOIN pg_index d ON t . oid = d . indrelid
INNER JOIN pg_class i ON d . indexrelid = i . oid
WHERE i . relkind = 'i'
AND d . indisprimary = 'f'
AND t . relname = '#{table_name}'
AND i . relnamespace IN ( SELECT oid FROM pg_namespace WHERE nspname = ANY ( current_schemas ( false ) ) )
ORDER BY i . relname
SQL
result . map do | row |
index_name = row [ 0 ]
2018-12-07 13:15:06 -05:00
unique = row [ 1 ]
indkey = row [ 2 ] . split ( " " ) . map ( & :to_i )
2016-03-03 06:35:06 -05:00
inddef = row [ 3 ]
oid = row [ 4 ]
columns = Hash [ query ( <<-SQL, "SCHEMA")]
SELECT a . attnum , a . attname
FROM pg_attribute a
WHERE a . attrelid = #{oid}
AND a . attnum IN ( #{indkey.join(",")})
SQL
column_names = columns . values_at ( * indkey ) . compact
unless column_names . empty?
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
desc_order_columns = inddef . scan ( / ( \ w+) DESC / ) . flatten
orders = desc_order_columns . any? ? Hash [ desc_order_columns . map { | order_column | [ order_column , :desc ] } ] : { }
where = inddef . scan ( / WHERE (.+)$ / ) . flatten [ 0 ]
using = inddef . scan ( / USING (.+?) / ) . flatten [ 0 ] . to_sym
2017-09-18 14:37:57 -04:00
opclasses = Hash [ inddef . scan ( / \ ((.+?) \ )(?:$| WHERE ) / ) . flatten [ 0 ] . split ( ',' ) . map do | column_and_opclass |
2018-11-19 09:33:20 -05:00
column , opclass = column_and_opclass . split ( ' ' ) . map ( & :strip )
end . reject do | column , opclass |
[ 'desc' , 'asc' ] . include? ( opclass & . downcase )
end . map do | column , opclass |
[ column , opclass ] if opclass
end . compact ]
2016-03-03 06:35:06 -05:00
2018-12-07 13:15:06 -05:00
index_attrs = [ table_name , index_name , unique , column_names , [ ] , orders , where , nil , using , nil , opclasses ]
2018-08-29 15:29:18 -04:00
IndexDefinition . new ( * index_attrs )
2016-03-03 06:35:06 -05:00
end
end . compact
end
def add_index ( table_name , column_name , options = { } ) #:nodoc:
index_name , index_type , index_columns_and_opclasses , index_options , index_algorithm , index_using = add_index_options ( table_name , column_name , options )
execute " CREATE #{ index_type } INDEX #{ index_algorithm } #{ quote_column_name ( index_name ) } ON #{ quote_table_name ( table_name ) } #{ index_using } ( #{ index_columns_and_opclasses } ) #{ index_options } "
end
protected
def quoted_columns_for_index ( column_names , options = { } )
column_opclasses = options [ :opclasses ] || { }
column_names . map { | name | " #{ quote_column_name ( name ) } #{ column_opclasses [ name ] } " }
2018-11-19 09:33:20 -05:00
quoted_columns = Hash [ column_names . map { | name | [ name . to_sym , " #{ quote_column_name ( name ) } #{ column_opclasses [ name ] } " ] } ]
add_options_for_index_columns ( quoted_columns , options ) . values
2016-03-03 06:35:06 -05:00
end
end
end
end
end
module ActiveRecord
class SchemaDumper
private
def indexes ( table , stream )
if ( indexes = @connection . indexes ( table ) ) . any?
add_index_statements = indexes . map do | index |
2018-08-29 15:29:18 -04:00
table_name = remove_prefix_and_suffix ( index . table ) . inspect
" add_index #{ ( [ table_name ] + index_parts ( index ) ) . join ( ', ' ) } "
2016-03-03 06:35:06 -05:00
end
stream . puts add_index_statements . sort . join ( " \n " )
stream . puts
end
end
2018-08-29 15:29:18 -04:00
def indexes_in_create ( table , stream )
if ( indexes = @connection . indexes ( table ) ) . any?
index_statements = indexes . map do | index |
" t.index #{ index_parts ( index ) . join ( ', ' ) } "
end
stream . puts index_statements . sort . join ( " \n " )
end
end
def index_parts ( index )
index_parts = [
index . columns . inspect ,
" name: #{ index . name . inspect } " ,
]
index_parts << " unique: true " if index . unique
index_parts << " length: { #{ format_options ( index . lengths ) } } " if index . lengths . present?
index_parts << " order: { #{ format_options ( index . orders ) } } " if index . orders . present?
index_parts << " where: #{ index . where . inspect } " if index . where
index_parts << " using: #{ index . using . inspect } " if index . using
index_parts << " type: #{ index . type . inspect } " if index . type
index_parts << " opclasses: #{ index . opclasses . inspect } " if index . opclasses . present?
2018-12-07 13:15:06 -05:00
index_parts << " comment: #{ index . comment . inspect } " if index . comment
2018-08-29 15:29:18 -04:00
index_parts
end
2018-09-25 10:26:01 -04:00
def format_options ( options )
options . map { | key , value | " #{ key } : #{ value . inspect } " } . join ( " , " )
end
2016-03-03 06:35:06 -05:00
end
end