class DbSchema::Reader::Postgres

Constants

COLUMNS_QUERY
CONSTRAINTS_QUERY
ENUMS_QUERY
EXPRESSION_INDEXES_QUERY
EXTENSIONS_QUERY
INDEXES_QUERY
TABLES_QUERY
VERSION

Attributes

connection[R]

Public Class Methods

new(connection) click to toggle source
# File lib/db_schema/reader/postgres.rb, line 110
def initialize(connection)
  @connection = connection
end

Public Instance Methods

read_enums() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 136
def read_enums
  connection[ENUMS_QUERY].map do |enum_data|
    Definitions::Enum.new(enum_data[:name].to_sym, enum_data[:values].map(&:to_sym))
  end
end
read_extensions() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 142
def read_extensions
  connection[EXTENSIONS_QUERY].map do |extension_data|
    Definitions::Extension.new(extension_data[:extname].to_sym)
  end
end
read_schema() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 114
def read_schema
  Definitions::Schema.new(
    tables:     read_tables,
    enums:      read_enums,
    extensions: read_extensions
  )
end
read_tables() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 122
def read_tables
  checks_data, foreign_keys_data = constraints_data

  table_names.map do |table_name|
    Table.new(
      table_name.to_sym,
      columns_data[table_name],
      indexes_data[table_name],
      checks_data[table_name],
      foreign_keys_data[table_name]
    ).definition
  end
end

Private Instance Methods

columns_data() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 153
def columns_data
  @columns_data ||= connection[COLUMNS_QUERY].to_a.group_by do |column|
    column[:table_name]
  end
end
constraints_data() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 217
def constraints_data
  checks       = Hash.new { |h, k| h[k] = [] }
  foreign_keys = Hash.new { |h, k| h[k] = [] }

  connection[CONSTRAINTS_QUERY].each do |constraint|
    case constraint[:type]
    when 'c'
      checks[constraint[:table_name]] << {
        name:      constraint[:name],
        condition: constraint[:condition]
      }
    when 'f'
      fields = constraint[:conkey].map do |position|
        get_field_name(constraint[:table_name], position)
      end

      keys = constraint[:confkey].map do |position|
        get_field_name(constraint[:referenced], position)
      end

      pkey = indexes_data.fetch(constraint[:referenced]).find { |index| index[:primary] }
      keys = [] if !pkey.nil? && keys == pkey.fetch(:columns) # this foreign key references a primary key

      foreign_keys[constraint[:table_name]] << {
        name:       constraint[:name],
        referenced: constraint[:referenced],
        fields:     fields,
        keys:       keys,
        on_update:  constraint[:on_update],
        on_delete:  constraint[:on_delete],
        deferrable: constraint[:deferrable]
      }
    else
      raise "Unknown constraint type #{constraint[:type].inspect}"
    end
  end

  [checks, foreign_keys]
end
get_field_name(table, position) click to toggle source
# File lib/db_schema/reader/postgres.rb, line 257
def get_field_name(table, position)
  columns_data.fetch(table).find do |column|
    column[:pos] == position
  end.fetch(:name).to_sym
end
index_expressions_data(indexes_data) click to toggle source
# File lib/db_schema/reader/postgres.rb, line 186
def index_expressions_data(indexes_data)
  all_positions, max_position = {}, 0

  indexes_data.each do |index_data|
    positions = index_data[:column_positions]
    expression_positions = positions.each_index.select do |i|
      positions[i].zero?
    end

    if expression_positions.any?
      all_positions[index_data[:index_oid]] = expression_positions
      max_position = [max_position, expression_positions.max].max
    end
  end

  if all_positions.any?
    connection[
      EXPRESSION_INDEXES_QUERY,
      Sequel.pg_array(all_positions.keys),
      Sequel.pg_array((1..max_position.succ).to_a)
    ].each_with_object({}) do |index_data, indexes_data|
      index_id = index_data[:index_id]
      expressions = all_positions[index_id].map { |pos| index_data[:definitions][pos] }

      indexes_data[index_id] = expressions
    end
  else
    {}
  end
end
indexes_data() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 159
def indexes_data
  @indexes_data ||= begin
    raw_data = connection[INDEXES_QUERY].map do |index_data|
      index_data.merge(
        column_positions: index_data[:column_positions].split(' ').map(&:to_i),
        index_options:    index_data[:index_options].split(' ').map(&:to_i)
      )
    end

    expressions_data = index_expressions_data(raw_data)

    raw_data.map do |index_data|
      columns = index_data[:column_positions].map do |position|
        if position.zero?
          expressions_data.fetch(index_data[:index_oid]).shift
        else
          get_field_name(index_data[:table_name], position)
        end
      end

      index_data.delete(:index_oid)
      index_data.delete(:column_positions)
      index_data.merge(columns: columns)
    end.group_by { |index| index[:table_name] }.tap { |h| h.default = [] }
  end
end
table_names() click to toggle source
# File lib/db_schema/reader/postgres.rb, line 149
def table_names
  @table_names ||= connection[TABLES_QUERY].to_a.map { |row| row[:table_name] }
end