Skip to content

Commit

Permalink
Initial implementation of MakeReadyable
Browse files Browse the repository at this point in the history
  • Loading branch information
aguynamedryan committed Jan 31, 2024
1 parent d91c98b commit 76210f0
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in sequelizer.gemspec
gemspec

group :test do
gem "sequel-hexspace", github: "outcomesinsights/sequel-hexspace", branch: "master"
end
76 changes: 76 additions & 0 deletions lib/sequel/extensions/make_readyable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module Sequel
module MakeReadyable
def make_ready(opts = {})
self.extension :usable
ReadyMaker.new(self, opts).run
end
end

class ReadyMaker
attr_reader :db, :opts

def initialize(db, opts)
@db = db
@opts = opts
end

def run
if opts[:use_schema]
db.use(opts[:use_schema])
end
only_tables = Array(opts[:only])
created_views = (Array(opts[:except]) || [])
(opts[:search_path] || []).each do |schema|
source = get_source(db, schema)
tables = source.tables(schema: schema) - created_views
tables &= only_tables unless only_tables.empty?
tables.each do |table|
create_view(source, table, schema)
created_views << table
end
end
end

def create_view(source, table, schema)
if schema.to_s =~ %r{/}
source.create_view(table, temp: true)
else
source.create_view(table, db[Sequel.qualify(schema, table)], temp: true)
end
end

def get_source(db, schema)
if schema.to_s =~ %r{/}
FileSourcerer.new(db, Pathname.new(schema))
else
db
end
end

class FileSourcerer
attr_reader :db, :schema
def initialize(db, schema)
@db = db
@schema = schema
end

def tables(opts = {})
[schema.basename(".*").to_s.to_sym]
end

def create_view(table, opts = {})
db.create_view(table, {
temp: true,
using: format,
options: { path: schema.expand_path }
}.merge(opts))
end

def format
schema.extname[1..-1]
end
end
end

Database.register_extension(:make_readyable, MakeReadyable)
end
81 changes: 81 additions & 0 deletions test/lib/sequel/extensions/test_make_readyable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require_relative "../../../test_helper"
require "fileutils"
require "pathname"
require "sequel"
require "sequel/extensions/make_readyable"

class TestUsable < Minitest::Test
def setup
# These features are mostly intended for Spark, but sqlite is a close enough
# mock that we'll just roll with it
@db = Sequel.mock(host: :spark)
@db.extension :make_readyable
def @db.tables(opts = {})
case opts[:schema]
when :schema1
[:a]
when :schema2
[:a, :b]
when :schema3
[:a, :b]
end
end
end

def test_should_call_use_schema
@db.make_ready(use_schema: :some_schema)
assert_equal(["USE `some_schema`"], @db.sqls)
end

def test_should_create_views_based_on_tables_in_search_paths
@db.make_ready(search_path: [:schema1, :schema2, :schema3])
assert_equal([
"CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`",
"CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`"
], @db.sqls)
end

def test_should_create_views_based_on_tables_in_search_paths_accepts_except
@db.make_ready(search_path: [:schema1, :schema2, :schema3], except: :a)
assert_equal([
"CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`"
], @db.sqls)
end

def test_should_create_views_based_on_tables_in_search_paths_accepts_only
@db.make_ready(search_path: [:schema1, :schema2, :schema3], only: :b)
assert_equal([
"CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`"
], @db.sqls)
end

def test_should_create_views_based_on_path
dir = Pathname.new(Dir.mktmpdir)
a_file = dir + "a.parquet"
b_file = dir + "b.parquet"
FileUtils.touch(a_file.to_s)
FileUtils.touch(b_file.to_s)

@db.make_ready(search_path: [:schema1, a_file, b_file, :schema2])
sqls = @db.sqls.dup
assert_equal("CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`", sqls[0])
assert_match(%r{CREATE TEMPORARY VIEW `b` USING parquet OPTIONS \('path'='/tmp/[^/]+/b.parquet'\)}, sqls[1])
end

def test_should_create_views_format_based_on_path
dir = Pathname.new(Dir.mktmpdir)
a_file = dir + "a.parquet"
b_file = dir + "b.delta"
c_file = dir + "c.csv"
FileUtils.touch(a_file.to_s)
FileUtils.touch(b_file.to_s)
FileUtils.touch(c_file.to_s)

@db.make_ready(search_path: [a_file, b_file, c_file])
sqls = @db.sqls.dup
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/a.parquet'\)}, sqls[0])
assert_match(%r{CREATE TEMPORARY VIEW `b` USING delta OPTIONS \('path'='/tmp/[^/]+/b.delta'\)}, sqls[1])
assert_match(%r{CREATE TEMPORARY VIEW `c` USING csv OPTIONS \('path'='/tmp/[^/]+/c.csv'\)}, sqls[2])
end
end

0 comments on commit 76210f0

Please sign in to comment.