lostapathy/db_subsetter

View on GitHub
lib/db_subsetter/importer.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'sqlite3'

module DbSubsetter
  # Manages importing a subset of data
  class Importer
    def initialize(filename)
      raise ArgumentError, 'invalid input file' unless File.exist?(filename)

      @data = SQLite3::Database.new(filename)
      @dialect = case ActiveRecord::Base.connection_config[:adapter]
                 when 'mysql2'
                   DbSubsetter::Dialect::MySQL
                 when 'postgresql'
                   DbSubsetter::Dialect::Postgres
                 when 'sqlite3'
                   DbSubsetter::Dialect::Sqlite
                 when 'sqlserver'
                   DbSubsetter::Dialect::MSSQL
                 else
                   DbSubsetter::Dialect::Generic
                 end
    end

    def tables
      all_tables = []
      @data.execute('SELECT name FROM tables') do |row|
        all_tables << row[0]
      end
      all_tables
    end

    def import(verbose = true)
      @verbose = verbose
      @dialect.import do
        tables.each do |table|
          import_table(table)
        end
      end
    end

    private

    def import_table(table)
      $stdout.sync
      started_at = Time.now
      print "Importing #{table}" if @verbose
      @dialect.truncate_table(table)

      ActiveRecord::Base.connection.begin_db_transaction

      all_rows = @data.execute("SELECT data FROM #{table.underscore}")
      all_rows.each_slice(@dialect::INSERT_BATCH_SIZE) do |rows|
        quoted_rows = rows.map { |row| '(' + quoted_values(row).join(',') + ')' }.join(',')
        insert_sql = "INSERT INTO #{quoted_table_name(table)} (#{quoted_column_names(table).join(',')}) VALUES #{quoted_rows}"
        ActiveRecord::Base.connection.execute(insert_sql)
        print '.' if @verbose
      end

      ActiveRecord::Base.connection.commit_db_transaction
      puts " (#{(Time.now - started_at).round(3)}s)" if @verbose
    end

    def quoted_values(row)
      out = JSON.parse(row[0])
      out = out.map { |x| ActiveRecord::Base.connection.type_cast(x, nil) }
      out = out.map { |x| ActiveRecord::Base.connection.quote(x) }
      out
    end

    def columns(table)
      raw = @data.execute('SELECT columns FROM tables WHERE name = ?', [table]).first[0]
      JSON.parse(raw)
    end

    def quoted_table_name(table)
      ActiveRecord::Base.connection.quote_table_name(table)
    end

    def quoted_column_names(table)
      columns(table).map { |column| ActiveRecord::Base.connection.quote_column_name(column) }
    end
  end
end