Module: Qry

Extended by:
Qry
Included in:
Qry
Defined in:
lib/qry.rb,
lib/qry/rails.rb,
lib/qry/manager.rb,
lib/qry/version.rb,
lib/qry/interface.rb,
lib/qry/rails/engine.rb,
lib/qry/null_instrumenter.rb

Defined Under Namespace

Modules: Rails Classes: NullInstrumenter

Constant Summary collapse

Manager =
Ivo.new(:qry, :migrations_path, :schema_path, :host_whitelist) do
  def current_version?
    Sequel::Migrator.is_current?(qry.sequel_db, migrations_path)
  end

  def load_schema
    ensure_valid_host

    File.read(schema_path).split(';').map(&:strip).reject(&:empty?).each do |sql|
      qry.run(sql)
    end
  end

  def migrate(version: nil)
    version = Integer(version) if version

    if version
      puts "Migrating to version #{version}"
      Sequel::Migrator.run(qry.sequel_db, migrations_path, target: version)
    else
      puts "Migrating to latest"
      Sequel::Migrator.run(qry.sequel_db, migrations_path)
    end

    dump_schema
  end

  def drop_all_tables
    ensure_valid_host

    fetch_table_names.each do |table_name|
      qry.run('drop table ?', Sequel.identifier(table_name))
    end
  end

  private

  def ensure_valid_host
    unless host_whitelist.include?(qry.host)
      raise "Host must be one of: #{host_whitelist.join(', ')}"
    end
  end

  def fetch_table_names
    sql = <<~SQL
      select t.table_name
      from information_schema.tables t
      where t.table_schema = ?
    SQL

    qry.fetch(sql, qry.name).map(&:table_name)
  end

  def dump_schema
    sqls = fetch_table_names.map do |table_name|
      create_table_sql = qry
        .fetch('show create table ?', Sequel.identifier(table_name))
        .map { |row| row.public_send('Create Table') }[0]
      create_table_sql_without_auto_increment = create_table_sql.gsub(/AUTO_INCREMENT=\d+\s/, '')
      "#{create_table_sql_without_auto_increment};"
    end

    schema_info = qry.fetch('select si.version from schema_info si limit 1')[0]

    sqls << "insert into schema_info (version) values (#{schema_info.version});"

    schema_sql = "#{sqls.join "\n\n"}\n"

    File.open(schema_path, 'w') do |file|
      file.write(schema_sql)
    end
  end
end
VERSION =
"1.0.0"
Interface =
Ivo.new(:sequel_db, :instrumenter) do
  extend Forwardable

  def_delegators(:sequel_db, :transaction, :disconnect, :url)

  def adapter
    sequel_db.opts[:adapter]
  end

  def user
    sequel_db.opts[:user]
  end

  def password
    sequel_db.opts[:password]
  end

  def host
    sequel_db.opts[:host]
  end

  def name
    sequel_db.opts[:database]
  end

  def fetch(sql, *binds)
    instrumenter.instrument('qry.fetch', qry: self) do
      sequel_db.fetch(sql, *binds).map { |row| Ivo.(row) }
    end
  end

  def insert(sql, *binds)
    instrumenter.instrument('qry.insert', qry: self) do
      sequel_db.fetch(sql, *binds).insert
    end
  end

  def update(sql, *binds)
    instrumenter.instrument('qry.update', qry: self) do
      sequel_db.fetch(sql, *binds).update
    end
  end

  def delete(sql, *binds)
    instrumenter.instrument('qry.delete', qry: self) do
      sequel_db.fetch(sql, *binds).delete
    end
  end

  def run(sql, *binds)
    sql = sequel_db.fetch(sql, *binds).sql unless binds.empty?

    instrumenter.instrument('qry.run', qry: self) do
      sequel_db.run(sql)
    end
  end

  def insert_row(table, data = nil, extra_sql = nil)
    data ||= {}

    columns = data.map { |column, _| Sequel.identifier(column.to_s) }
    values = data.map { |_, value| value }
    placeholders = data.map { '?' }.join(', ')

    sql = <<~SQL % {columns: placeholders, values: placeholders, extra: extra_sql}
      insert into ? (%{columns})
      values (%{values})
      %{extra}
    SQL

    if !table.is_a?(Sequel::SQL::Identifier) && !table.is_a?(Sequel::SQL::QualifiedIdentifier)
      table = Sequel.identifier(table)
    end

    insert(sql, table, *columns, *values)
  end

  def insert_rows(table, columns, values_lists, extra_sql = nil)
    return if values_lists.empty?

    columns = columns.map { |column| Sequel.identifier(column.to_s) }
    placeholders = columns.map { '?' }.join(', ')
    values = values_lists.map { "(%{placeholders})" }.join(', ') % {placeholders: placeholders}

    sql = <<~SQL % {columns: placeholders, values: values, extra: extra_sql}
      insert into ? (%{columns})
      values %{values}
      %{extra}
    SQL

    if !table.is_a?(Sequel::SQL::Identifier) && !table.is_a?(Sequel::SQL::QualifiedIdentifier)
      table = Sequel.identifier(table)
    end

    insert(sql, table, *columns, *values_lists.flatten)
  end

  def update_rows(table, updates, where)
    return if updates.empty?

    binds = [Sequel.identifier(table.to_s)]

    updates.each do |column, value|
      binds << Sequel.identifier(column.to_s) << value
    end

    where.each do |column, value|
      binds << Sequel.identifier(column.to_s) << value
    end

    sql = <<~SQL
      update ?
      set %{updates}
      where %{where}
    SQL

    sql = sql % {
      updates: updates.map { "? = ?" }.join(", "),
      where: where.map { "? = ?" }.join(" and "),
    }

    update(sql, *binds)
  end
end

Instance Method Summary collapse

Instance Method Details

#connect(**args) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/qry.rb', line 11

def connect(**args)
  instrumenter = args.delete(:instrumenter) || NullInstrumenter.new

  sequel_args = if args.key?(:url)
    url = args.delete(:url)
    [url, args]
  else
    [args]
  end

  if block_given?
    Sequel.connect(*sequel_args) do |sequel_db|
      yield Interface.with(
        sequel_db: sequel_db,
        instrumenter: instrumenter,
      )
    end
  else
    Interface.with(
      sequel_db: Sequel.connect(*sequel_args),
      instrumenter: instrumenter,
    )
  end
end