module ActiverecordCoveringIndex::PostgreSQLAdapter

Public Instance Methods

add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) click to toggle source
Calls superclass method
# File lib/activerecord-covering-index/postgresql_adapter.rb, line 13
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options)
  options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include)

  options[:name] = name if name
  options[:internal] = internal
  non_key_columns = options.delete(:include)

  original_index_options = super(table_name, column_name, **options)

  if original_index_options.first.is_a?(String)
    index_name, index_type, index_columns, index_options, algorithm, using, comment = original_index_options

    if non_key_columns && supports_covering_index?
      non_key_columns = [non_key_columns] if non_key_columns.is_a?(Symbol)
      non_key_columns = quoted_columns_for_index(non_key_columns, {}).join(", ")

      index_options = " INCLUDE (#{non_key_columns})" + index_options
    end

    [index_name, index_type, index_columns, index_options, algorithm, using, comment]
  else
    index, algorithm, if_not_exists = original_index_options

    index_with_include = ActiveRecord::ConnectionAdapters::IndexDefinition.new(
      index.table,
      index.name,
      index.unique,
      index.columns,
      lengths: index.lengths,
      orders: index.orders,
      opclasses: index.opclasses,
      where: index.where,
      type: index.type,
      using: index.using,
      comment: index.comment,
      include: non_key_columns
    )

    [index_with_include, algorithm, if_not_exists]
  end
end
indexes(table_name) click to toggle source
# File lib/activerecord-covering-index/postgresql_adapter.rb, line 55
    def indexes(table_name)
      scope = quoted_scope(table_name)

      result = query(<<~SQL, "SCHEMA")
        SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
                        pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indnkeyatts
        FROM pg_class t
        INNER JOIN pg_index d ON t.oid = d.indrelid
        INNER JOIN pg_class i ON d.indexrelid = i.oid
        LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
        WHERE i.relkind IN ('i', 'I')
          AND d.indisprimary = 'f'
          AND t.relname = #{scope[:name]}
          AND n.nspname = #{scope[:schema]}
        ORDER BY i.relname
      SQL

      result.map do |row|
        index_name = row[0]
        unique = row[1]
        indkey = row[2].split(" ").map(&:to_i)
        inddef = row[3]
        oid = row[4]
        comment = row[5]
        indnkeyatts = row[6]

        using, expressions, _, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?(?: WHERE (.+))?\z/m).flatten

        orders = {}
        opclasses = {}

        columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey)
          SELECT a.attnum, a.attname
          FROM pg_attribute a
          WHERE a.attrelid = #{oid}
          AND a.attnum IN (#{indkey.join(",")})
        SQL

        non_key_columns = columns.pop(columns.count - indnkeyatts)

        if indkey.include?(0)
          columns = expressions
        else
          # add info on sort order (only desc order is explicitly specified, asc is the default)
          # and non-default opclasses
          expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
            opclasses[column] = opclass.to_sym if opclass
            if nulls
              orders[column] = [desc, nulls].compact.join(" ")
            else
              orders[column] = :desc if desc
            end
          end
        end

        ActiveRecord::ConnectionAdapters::IndexDefinition.new(
          table_name,
          index_name,
          unique,
          columns,
          orders: orders,
          opclasses: opclasses,
          where: where,
          using: using.to_sym,
          comment: comment.presence,
          include: non_key_columns
        )
      end
    end
supports_covering_index?() click to toggle source
# File lib/activerecord-covering-index/postgresql_adapter.rb, line 5
def supports_covering_index?
  if respond_to?(:database_version) # ActiveRecord 6+
    database_version >= 110_000
  else
    postgresql_version >= 110_000
  end
end