class DbSubsetter::Table

A database in the database to be subset or imported

Attributes

name[RW]

Public Class Methods

new(name, database, exporter) click to toggle source
# File lib/db_subsetter/table.rb, line 6
def initialize(name, database, exporter)
  @name = name
  @exporter = exporter
  @database = database
  @full_table = @ignored = false
end

Public Instance Methods

arel_table() click to toggle source
# File lib/db_subsetter/table.rb, line 90
def arel_table
  @arel_table ||= Arel::Table.new(@name)
end
export() click to toggle source

FIXME: move the raw SQL into another class

# File lib/db_subsetter/table.rb, line 41
def export
  print "Exporting: #{@name} (#{pages} pages)" if verbose

  rows_exported = 0
  @exporter.output.execute("CREATE TABLE #{@name.underscore} ( data TEXT )")
  0.upto(pages - 1).each do |page|
    records_for_page(page).each_slice(Exporter::INSERT_BATCH_SIZE) do |rows|
      data = rows.map { |x| @exporter.sanitize_row(@name, x) }.map(&:to_json)

      @exporter.output.execute("INSERT INTO #{@name.underscore} (data) VALUES #{Array.new(rows.size) { '(?)' }.join(',')}", data)
      rows_exported += rows.size
    end

    print '.' if verbose
  end
  puts '' if verbose
  columns = ActiveRecord::Base.connection.columns(@name).map(&:name)
  @exporter.output.execute('INSERT INTO tables VALUES (?, ?, ?)', [@name, rows_exported, columns.to_json])
end
exportability_issues() click to toggle source
# File lib/db_subsetter/table.rb, line 65
def exportability_issues
  return @exportability_issues if @exportability_issues

  @exportability_issues = []
  begin
    puts "Verifying: #{@name} (#{filtered_row_count}/#{total_row_count})" if verbose
    @exportability_issues << 'Multiple pages but no primary key' if pages > 1 && primary_key.blank?
    @exportability_issues << "Too many rows (#{filtered_row_count})" if filtered_row_count > @exporter.max_filtered_rows
  rescue CircularRelationError
    @exportability_issues << 'Circular relations through this table'
  end
  @exportability_issues
end
exportable?() click to toggle source
# File lib/db_subsetter/table.rb, line 61
def exportable?
  exportability_issues.empty?
end
filtered_ids() click to toggle source
# File lib/db_subsetter/table.rb, line 79
def filtered_ids
  return @id_cache if @id_cache

  raise CircularRelationError if @loaded_ids
  @loaded_ids = true

  sql = filtered_records.project(:id).to_sql

  @id_cache = ActiveRecord::Base.connection.select_rows(sql).flatten
end
filtered_row_count() click to toggle source
# File lib/db_subsetter/table.rb, line 35
def filtered_row_count
  query = filtered_records.project(Arel.sql('count(1) AS num_rows'))
  ActiveRecord::Base.connection.select_one(query.to_sql)['num_rows'].to_i # rails-4.2+pg needs to_i
end
ignore!() click to toggle source

FIXME: these 4 methods don't feel quite like the correct API yet

# File lib/db_subsetter/table.rb, line 14
def ignore!
  @ignored = true
end
ignored?() click to toggle source
# File lib/db_subsetter/table.rb, line 26
def ignored?
  @ignored
end
primary_key() click to toggle source
# File lib/db_subsetter/table.rb, line 94
def primary_key
  ActiveRecord::Base.connection.primary_key(@name)
end
relations() click to toggle source
# File lib/db_subsetter/table.rb, line 98
def relations
  ActiveRecord::Base.connection.foreign_keys(@name).map { |x| Relation.new(x, @database) }
end
subset_in_full!() click to toggle source
# File lib/db_subsetter/table.rb, line 18
def subset_in_full!
  @subset_in_full = true
end
subset_in_full?() click to toggle source
# File lib/db_subsetter/table.rb, line 22
def subset_in_full?
  @subset_in_full
end
total_row_count() click to toggle source
# File lib/db_subsetter/table.rb, line 30
def total_row_count
  query = arel_table.project('count(1) AS num_rows')
  ActiveRecord::Base.connection.select_one(query.to_sql)['num_rows'].to_i # rails-4.2+pg needs to_i
end

Private Instance Methods

filter_foreign_keys(query) click to toggle source
# File lib/db_subsetter/table.rb, line 118
def filter_foreign_keys(query)
  relations.each do |relation|
    query = relation.apply_subset(query)
  end
  query
end
filtered_records() click to toggle source
# File lib/db_subsetter/table.rb, line 108
def filtered_records
  return arel_table if @exporter.nil? || @exporter.filter.nil?
  query = @exporter.filter.apply(self, arel_table)

  if total_row_count > @exporter.max_filtered_rows
    query = filter_foreign_keys(query)
  end
  query
end
pages() click to toggle source
# File lib/db_subsetter/table.rb, line 134
def pages
  @page_count ||= (filtered_row_count / Exporter::SELECT_BATCH_SIZE.to_f).ceil
end
records_for_page(page) click to toggle source
# File lib/db_subsetter/table.rb, line 125
def records_for_page(page)
  query = filtered_records
  query = query.order(arel_table[primary_key]) if primary_key

  query = query.skip(page * Exporter::SELECT_BATCH_SIZE).take(Exporter::SELECT_BATCH_SIZE) if pages > 1
  sql = query.project(Arel.sql('*')).to_sql
  ActiveRecord::Base.connection.select_rows(sql)
end
verbose() click to toggle source
# File lib/db_subsetter/table.rb, line 104
def verbose
  @exporter.verbose?
end