-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of MakeReadyable
- Loading branch information
1 parent
d91c98b
commit 76210f0
Showing
3 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|