sequelizer 0.1.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/LICENSE +21 -0
- data/README.md +28 -14
- data/config/platforms/base.csv +5 -0
- data/config/platforms/rdbms/athena.csv +4 -0
- data/config/platforms/rdbms/postgres.csv +3 -0
- data/config/platforms/rdbms/snowflake.csv +1 -0
- data/config/platforms/rdbms/spark.csv +3 -0
- data/lib/sequel/extensions/cold_col.rb +41 -17
- data/lib/sequel/extensions/funky.rb +136 -0
- data/lib/sequel/extensions/make_readyable.rb +18 -10
- data/lib/sequel/extensions/platform.rb +300 -0
- data/lib/sequel/extensions/smart_select_remove.rb +101 -0
- data/lib/sequelizer/options.rb +15 -6
- data/lib/sequelizer/version.rb +1 -1
- data/lib/sequelizer/yaml_config.rb +0 -1
- metadata +90 -57
- data/.claude/settings.local.json +0 -64
- data/.devcontainer/.p10k.zsh +0 -1713
- data/.devcontainer/.zshrc +0 -29
- data/.devcontainer/Dockerfile +0 -137
- data/.devcontainer/copy-claude-credentials.sh +0 -32
- data/.devcontainer/devcontainer.json +0 -102
- data/.devcontainer/init-firewall.sh +0 -123
- data/.devcontainer/setup-credentials.sh +0 -95
- data/.github/workflows/test.yml +0 -14
- data/.gitignore +0 -40
- data/.overcommit.yml +0 -73
- data/.rubocop.yml +0 -167
- data/CLAUDE.md +0 -219
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -158
- data/Guardfile +0 -28
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -35
- data/examples/database.with_one_set_of_options.yml +0 -15
- data/examples/database.yml +0 -30
- data/examples/dot_env.txt +0 -10
- data/sequelizer.gemspec +0 -35
- data/test/lib/sequel/extensions/test_cold_col.rb +0 -251
- data/test/lib/sequel/extensions/test_db_opts.rb +0 -42
- data/test/lib/sequel/extensions/test_make_readyable.rb +0 -259
- data/test/lib/sequel/extensions/test_more_sql.rb +0 -132
- data/test/lib/sequel/extensions/test_settable.rb +0 -109
- data/test/lib/sequel/extensions/test_sql_recorder.rb +0 -231
- data/test/lib/sequel/extensions/test_unionize.rb +0 -76
- data/test/lib/sequel/extensions/test_usable.rb +0 -15
- data/test/lib/sequelizer/test_connection_maker.rb +0 -123
- data/test/lib/sequelizer/test_env_config.rb +0 -37
- data/test/lib/sequelizer/test_gemfile_modifier.rb +0 -88
- data/test/lib/sequelizer/test_options.rb +0 -83
- data/test/lib/sequelizer/test_yaml_config.rb +0 -83
- data/test/test_helper.rb +0 -44
data/sequelizer.gemspec
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
lib = File.expand_path('lib', __dir__)
|
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
-
require 'sequelizer/version'
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = 'sequelizer'
|
|
7
|
-
spec.version = Sequelizer::VERSION
|
|
8
|
-
spec.authors = ['Ryan Duryea']
|
|
9
|
-
spec.email = ['aguynamedryan@gmail.com']
|
|
10
|
-
spec.summary = 'Sequel database connections via config/database.yml or .env'
|
|
11
|
-
spec.description = 'Easily establish a connection to a database via Sequel gem using options specified in config/database.yml or .env files'
|
|
12
|
-
spec.homepage = 'https://github.com/outcomesinsights/sequelizer'
|
|
13
|
-
spec.license = 'MIT'
|
|
14
|
-
|
|
15
|
-
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
-
spec.require_paths = ['lib']
|
|
18
|
-
spec.required_ruby_version = '>= 3.2.0'
|
|
19
|
-
|
|
20
|
-
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
21
|
-
spec.add_development_dependency 'guard', '~> 2.0'
|
|
22
|
-
spec.add_development_dependency 'guard-minitest', '~> 2.3'
|
|
23
|
-
spec.add_development_dependency 'minitest', '~> 5.3'
|
|
24
|
-
spec.add_development_dependency 'pg', '~> 1.0'
|
|
25
|
-
spec.add_development_dependency 'rake', '~> 12.0'
|
|
26
|
-
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
27
|
-
spec.add_development_dependency 'rubocop-minitest', '~> 0.25'
|
|
28
|
-
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
29
|
-
spec.add_dependency 'activesupport', '~> 7.0'
|
|
30
|
-
spec.add_dependency 'dotenv', '~> 2.1'
|
|
31
|
-
spec.add_dependency 'hashie', '~> 3.2'
|
|
32
|
-
spec.add_dependency 'sequel', '~> 5.93'
|
|
33
|
-
spec.add_dependency 'thor', '~> 1.0'
|
|
34
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
35
|
-
end
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../../../test_helper'
|
|
4
|
-
require_relative '../../../../lib/sequel/extensions/cold_col'
|
|
5
|
-
|
|
6
|
-
describe Sequel::ColdColDatabase do
|
|
7
|
-
let(:db) do
|
|
8
|
-
@db = Sequel.mock.extension(:cold_col)
|
|
9
|
-
@db.cold_col_registry.set_schemas({
|
|
10
|
-
Sequel.lit('tab1') => [[:col1]],
|
|
11
|
-
Sequel.lit('tab2') => [[:col2]],
|
|
12
|
-
Sequel.lit('tab3') => [[:col3], [:col4]],
|
|
13
|
-
Sequel.lit('q.tab4') => [[:col5]],
|
|
14
|
-
})
|
|
15
|
-
@db.extend_datasets do
|
|
16
|
-
def supports_cte?
|
|
17
|
-
true
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Add with method to mock database to support CTEs
|
|
22
|
-
def @db.with(*args)
|
|
23
|
-
dataset.with(*args)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
@db
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def expect_columns(ds, *cols)
|
|
30
|
-
_(ds.columns).must_equal(cols)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'should know columns from select * FROM tab' do
|
|
34
|
-
expect_columns(db[:tab1], :col1)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'should know columns after append' do
|
|
38
|
-
expect_columns(db[:tab1].select_append(Sequel.function(:min, :col1).as(:mini)), :col1, :mini)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it 'should know columns after select_all' do
|
|
42
|
-
expect_columns(db[:tab1].select_all, :col1)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it 'should know columns after select_all(:tab1)' do
|
|
46
|
-
expect_columns(db[:tab1].select_all(:tab1), :col1)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it 'should know columns after from_self' do
|
|
50
|
-
expect_columns(db[:tab1].from_self, :col1)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it 'should know columns after a CTE' do
|
|
54
|
-
ds = db[:cte1]
|
|
55
|
-
.with(:cte1, db[:tab1])
|
|
56
|
-
expect_columns(ds, :col1)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it 'should know columns after a JOIN' do
|
|
60
|
-
ds = db[:tab1]
|
|
61
|
-
.join(:tab2)
|
|
62
|
-
expect_columns(ds, :col1, :col2)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it 'should know columns after a different kind of JOIN' do
|
|
66
|
-
ds = db[:tab1]
|
|
67
|
-
.join(db[:tab2])
|
|
68
|
-
expect_columns(ds, :col1, :col2)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
it 'should know columns from a JOIN and CTE' do
|
|
72
|
-
ds = db[:tab1]
|
|
73
|
-
.with(:cte1, db[:tab2])
|
|
74
|
-
.join(db[:cte1])
|
|
75
|
-
expect_columns(ds, :col1, :col2)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it 'should know columns from a select_all JOIN' do
|
|
79
|
-
ds = db[:tab1]
|
|
80
|
-
.join(db[:tab2], { Sequel[:tab1][:col1] => Sequel[:tab2][:col3] })
|
|
81
|
-
.select_all(:tab1)
|
|
82
|
-
expect_columns(ds, :col1)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
it 'should know columns from an aliased select_all JOIN' do
|
|
86
|
-
ds = db[:tab1].from_self(alias: :l)
|
|
87
|
-
.join(db[:tab2], { col3: :col1 })
|
|
88
|
-
.select_all(:l)
|
|
89
|
-
expect_columns(ds, :col1)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it 'should know columns from an aliased select_all and added rhs column JOIN' do
|
|
93
|
-
ds = db[:tab1].from_self(alias: :l)
|
|
94
|
-
.join(db[:tab2], { col3: :col1 }, table_alias: :r)
|
|
95
|
-
.select_all(:l)
|
|
96
|
-
.select_append(Sequel[:r][:col4])
|
|
97
|
-
expect_columns(ds, :col1, :col4)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
it 'should know columns from an aliased select_all rhs JOIN' do
|
|
101
|
-
ds = db[:tab1].from_self(alias: :l)
|
|
102
|
-
.join(db[:tab2], { col3: :col1 }, table_alias: :r)
|
|
103
|
-
.select_all(:r)
|
|
104
|
-
expect_columns(ds, :col2)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
it 'should know columns from a directly aliased select_all rhs JOIN' do
|
|
108
|
-
ds = db[:tab1].from_self(alias: :l)
|
|
109
|
-
.join(:tab2, { col3: :col1 }, table_alias: :r)
|
|
110
|
-
.select_all(:r)
|
|
111
|
-
expect_columns(ds, :col2)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it 'should know columns from a a qualified JOIN' do
|
|
115
|
-
ds = db[:tab1].from_self(alias: :l)
|
|
116
|
-
.join(Sequel[:q][:tab4], { col3: :col1 }, table_alias: :r)
|
|
117
|
-
.select_all(:r)
|
|
118
|
-
expect_columns(ds, :col5)
|
|
119
|
-
end
|
|
120
|
-
it 'should remember columns from ctas' do
|
|
121
|
-
db.create_table(:ctas_table, as: db.select(Sequel[1].as(:a)))
|
|
122
|
-
ds = db[:ctas_table]
|
|
123
|
-
expect_columns(ds, :a)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it 'should remember columns from create table' do
|
|
127
|
-
db.create_table(:ddl_table) do
|
|
128
|
-
String :a
|
|
129
|
-
end
|
|
130
|
-
ds = db[:ddl_table]
|
|
131
|
-
expect_columns(ds, :a)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
it 'should remember columns from view' do
|
|
135
|
-
db.create_view(:ctas_view, db.select(Sequel[1].as(:a)))
|
|
136
|
-
ds = db[:ctas_view]
|
|
137
|
-
expect_columns(ds, :a)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
it 'should ignore columns when asked, thus avoiding an issue with string-only SQL' do
|
|
141
|
-
assert_raises(NoMethodError) { db.create_view(:ctas_view, 'SELECT 1 AS A') }
|
|
142
|
-
db.create_view(:ctas_view, 'SELECT 1 AS A', dont_record: true)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
it 'should handle load_schema with empty file' do
|
|
146
|
-
require 'tempfile'
|
|
147
|
-
require 'yaml'
|
|
148
|
-
|
|
149
|
-
Tempfile.create(['schema', '.yml']) do |f|
|
|
150
|
-
f.write({}.to_yaml)
|
|
151
|
-
f.flush
|
|
152
|
-
|
|
153
|
-
db.load_schema(f.path)
|
|
154
|
-
# Should not raise error and schemas should remain unchanged
|
|
155
|
-
expect_columns(db[:tab1], :col1)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
it 'should handle add_table_schema with symbol and string table names' do
|
|
160
|
-
db.add_table_schema(:new_table, [[:col_a, {}], [:col_b, {}]])
|
|
161
|
-
db.add_table_schema('string_table', [[:col_c, {}]])
|
|
162
|
-
|
|
163
|
-
expect_columns(db[:new_table], :col_a, :col_b)
|
|
164
|
-
expect_columns(db[:string_table], :col_c)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
it 'should handle complex nested CTEs' do
|
|
168
|
-
ds = db.with(:cte1, db[:tab1])
|
|
169
|
-
.with(:cte2, db[:cte1].select(:col1))
|
|
170
|
-
.from(:cte2)
|
|
171
|
-
expect_columns(ds, :col1)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
it 'should handle qualified table names in schema' do
|
|
175
|
-
expect_columns(db[Sequel[:q][:tab4]], :col5)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
it 'should handle aliased expressions in select' do
|
|
179
|
-
ds = db[:tab1].select(Sequel[:col1].as(:renamed_col))
|
|
180
|
-
expect_columns(ds, :renamed_col)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
it 'should handle function calls with aliases' do
|
|
184
|
-
ds = db[:tab1].select(Sequel.function(:count, :col1).as(:count_col1))
|
|
185
|
-
expect_columns(ds, :count_col1)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
it 'should handle multiple table joins with mixed syntax' do
|
|
189
|
-
ds = db[:tab1]
|
|
190
|
-
.join(:tab2, { col2: :col1 })
|
|
191
|
-
.join(db[:tab3].as(:t3), { col3: :col1 })
|
|
192
|
-
expect_columns(ds, :col1, :col2, :col3, :col4)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
it 'should handle recursive schema merging with load_schema' do
|
|
196
|
-
require 'tempfile'
|
|
197
|
-
require 'yaml'
|
|
198
|
-
|
|
199
|
-
# First schema file
|
|
200
|
-
Tempfile.create(['schema1', '.yml']) do |f1|
|
|
201
|
-
f1.write({ 'initial_table' => { columns: { 'col_x' => {} } } }.to_yaml)
|
|
202
|
-
f1.flush
|
|
203
|
-
db.load_schema(f1.path)
|
|
204
|
-
|
|
205
|
-
# Second schema file
|
|
206
|
-
Tempfile.create(['schema2', '.yml']) do |f2|
|
|
207
|
-
f2.write({ 'second_table' => { columns: { 'col_y' => {} } } }.to_yaml)
|
|
208
|
-
f2.flush
|
|
209
|
-
db.load_schema(f2.path)
|
|
210
|
-
|
|
211
|
-
# Both schemas should be available
|
|
212
|
-
expect_columns(db[:initial_table], :col_x)
|
|
213
|
-
expect_columns(db[:second_table], :col_y)
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Additional tests to ensure refactoring preserves behavior
|
|
219
|
-
describe 'Internal schema lookup behavior' do
|
|
220
|
-
it 'should prioritize created tables over schemas' do
|
|
221
|
-
db.add_table_schema(:priority_test, [[:schema_col, {}]])
|
|
222
|
-
db.create_table(:priority_test) do
|
|
223
|
-
String :created_col
|
|
224
|
-
end
|
|
225
|
-
expect_columns(db[:priority_test], :created_col)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
it 'should handle deeply nested WITH clauses' do
|
|
229
|
-
ds = db[:cte1]
|
|
230
|
-
.with(:cte1, db[:tab1])
|
|
231
|
-
.with(:cte2, db[:cte1])
|
|
232
|
-
.with(:cte3, db[:cte2])
|
|
233
|
-
.from(:cte3)
|
|
234
|
-
expect_columns(ds, :col1)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
it 'should handle mixed aliased and non-aliased sources' do
|
|
238
|
-
ds = db[:tab1].from_self(alias: :t1)
|
|
239
|
-
.join(:tab2, { col2: :col1 })
|
|
240
|
-
.join(db[:tab3].as(:t3), { col3: :col1 })
|
|
241
|
-
.select_all(:t1)
|
|
242
|
-
.select_append(Sequel[:t3][:col4])
|
|
243
|
-
expect_columns(ds, :col1, :col4)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
it 'should handle column lookup with empty select lists' do
|
|
247
|
-
ds = db.from(db[:tab1].where(id: 1))
|
|
248
|
-
expect_columns(ds, :col1)
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
require_relative '../../../test_helper'
|
|
2
|
-
require 'sequel'
|
|
3
|
-
require 'sequel/extensions/db_opts'
|
|
4
|
-
|
|
5
|
-
class TestDbOpts < Minitest::Test
|
|
6
|
-
|
|
7
|
-
def with_fake_database_type_and_options(db_type, opts = {})
|
|
8
|
-
db = Sequel.mock
|
|
9
|
-
db.define_singleton_method(:database_type) { db_type }
|
|
10
|
-
db.define_singleton_method(:opts) { opts }
|
|
11
|
-
db.extension :db_opts
|
|
12
|
-
yield db
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def sql_for(db_type, options)
|
|
16
|
-
with_fake_database_type_and_options(db_type, options) do |db|
|
|
17
|
-
db.synchronize do |conn|
|
|
18
|
-
db.db_opts.apply(conn)
|
|
19
|
-
return db.sqls
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def test_should_detect_options_for_appropriate_db
|
|
25
|
-
assert_equal(['SET flim=flam'], sql_for(:postgres, postgres_db_opt_flim: :flam))
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def test_should_ignore_options_for_inappropriate_db
|
|
29
|
-
assert_equal(['SET flim=flam'], sql_for(:postgres, postgres_db_opt_flim: :flam, other_db_opt_foo: :bar))
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_should_ignore_non_db_opts
|
|
33
|
-
assert_empty(sql_for(:postgres, postgres_flim: :flam))
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def test_should_properly_quote_awkward_values
|
|
37
|
-
assert_equal(["SET str='hello there'", "SET hyphen='i-like-hyphens-though-they-are-dumb'"],
|
|
38
|
-
sql_for(:postgres, postgres_db_opt_str: 'hello there',
|
|
39
|
-
postgres_db_opt_hyphen: 'i-like-hyphens-though-they-are-dumb'))
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
end
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
require_relative '../../../test_helper'
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
require 'tmpdir'
|
|
4
|
-
require 'pathname'
|
|
5
|
-
require 'sequel'
|
|
6
|
-
require 'sequel/extensions/make_readyable'
|
|
7
|
-
|
|
8
|
-
class TestUsable < Minitest::Test
|
|
9
|
-
|
|
10
|
-
def setup
|
|
11
|
-
# These features are mostly intended for Spark, but sqlite is a close enough
|
|
12
|
-
# mock that we'll just roll with it
|
|
13
|
-
@db = Sequel.mock(host: :spark)
|
|
14
|
-
@db.extension :make_readyable
|
|
15
|
-
def @db.tables(opts = {})
|
|
16
|
-
case opts[:schema]
|
|
17
|
-
when :schema1
|
|
18
|
-
[:a]
|
|
19
|
-
when :schema2
|
|
20
|
-
%i[a b]
|
|
21
|
-
when :schema3
|
|
22
|
-
%i[a b]
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def test_should_call_use_schema
|
|
28
|
-
@db.make_ready(use_schema: :some_schema)
|
|
29
|
-
|
|
30
|
-
assert_equal(['USE `some_schema`'], @db.sqls)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def test_should_create_views_based_on_tables_in_search_paths
|
|
34
|
-
@db.make_ready(search_path: %i[schema1 schema2 schema3])
|
|
35
|
-
|
|
36
|
-
assert_equal([
|
|
37
|
-
'CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`',
|
|
38
|
-
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
39
|
-
], @db.sqls)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def test_should_create_views_based_on_tables_in_search_paths_passed_as_strings
|
|
43
|
-
@db.make_ready(search_path: %w[schema1 schema2 schema3])
|
|
44
|
-
|
|
45
|
-
assert_equal([
|
|
46
|
-
'CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`',
|
|
47
|
-
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
48
|
-
], @db.sqls)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def test_should_create_views_based_on_tables_in_search_paths_accepts_except
|
|
52
|
-
@db.make_ready(search_path: %i[schema1 schema2 schema3], except: :a)
|
|
53
|
-
|
|
54
|
-
assert_equal([
|
|
55
|
-
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
56
|
-
], @db.sqls)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def test_should_create_views_based_on_tables_in_search_paths_accepts_only
|
|
60
|
-
@db.make_ready(search_path: %i[schema1 schema2 schema3], only: :b)
|
|
61
|
-
|
|
62
|
-
assert_equal([
|
|
63
|
-
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
64
|
-
], @db.sqls)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def test_should_create_views_based_on_path
|
|
68
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
69
|
-
a_file = dir.join('a.parquet')
|
|
70
|
-
b_file = dir.join('b.parquet')
|
|
71
|
-
FileUtils.touch(a_file.to_s)
|
|
72
|
-
FileUtils.touch(b_file.to_s)
|
|
73
|
-
|
|
74
|
-
@db.make_ready(search_path: [:schema1, a_file, b_file, :schema2])
|
|
75
|
-
sqls = @db.sqls.dup
|
|
76
|
-
|
|
77
|
-
assert_equal('CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`', sqls[0])
|
|
78
|
-
assert_match(%r{CREATE TEMPORARY VIEW `b` USING parquet OPTIONS \('path'='/tmp/[^/]+/b.parquet'\)}, sqls[1])
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def test_should_create_views_format_based_on_path
|
|
82
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
83
|
-
a_file = dir.join('a.parquet')
|
|
84
|
-
b_file = dir.join('b.delta')
|
|
85
|
-
c_file = dir.join('c.csv')
|
|
86
|
-
FileUtils.touch(a_file.to_s)
|
|
87
|
-
FileUtils.touch(b_file.to_s)
|
|
88
|
-
FileUtils.touch(c_file.to_s)
|
|
89
|
-
|
|
90
|
-
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
91
|
-
sqls = @db.sqls.dup
|
|
92
|
-
|
|
93
|
-
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/a.parquet'\)}, sqls[0])
|
|
94
|
-
assert_match(%r{CREATE TEMPORARY VIEW `b` USING delta OPTIONS \('path'='/tmp/[^/]+/b.delta'\)}, sqls[1])
|
|
95
|
-
assert_match(%r{CREATE TEMPORARY VIEW `c` USING csv OPTIONS \('path'='/tmp/[^/]+/c.csv'\)}, sqls[2])
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def test_should_create_a_single_view_if_multiple_files_have_the_same_name
|
|
99
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
100
|
-
a_file = dir.join('a.parquet')
|
|
101
|
-
b_file = dir.join('a.delta')
|
|
102
|
-
c_file = dir.join('a.csv')
|
|
103
|
-
FileUtils.touch(a_file.to_s)
|
|
104
|
-
FileUtils.touch(b_file.to_s)
|
|
105
|
-
FileUtils.touch(c_file.to_s)
|
|
106
|
-
|
|
107
|
-
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
108
|
-
sqls = @db.sqls.dup
|
|
109
|
-
|
|
110
|
-
assert_equal(1, sqls.size)
|
|
111
|
-
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/a.parquet'\)}, sqls[0])
|
|
112
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/a.delta'\)}, sqls[1])
|
|
113
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/a.csv'\)}, sqls[2])
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def test_should_create_a_single_view_if_multiple_files_have_the_same_name_and_are_in_different_directories
|
|
117
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
118
|
-
a_file = dir / 'one' / 'a.parquet'
|
|
119
|
-
b_file = dir / 'two' / 'a.delta'
|
|
120
|
-
c_file = dir / 'three' / 'a.csv'
|
|
121
|
-
FileUtils.mkdir_p(a_file.dirname)
|
|
122
|
-
FileUtils.mkdir_p(b_file.dirname)
|
|
123
|
-
FileUtils.mkdir_p(c_file.dirname)
|
|
124
|
-
FileUtils.touch(a_file.to_s)
|
|
125
|
-
FileUtils.touch(b_file.to_s)
|
|
126
|
-
FileUtils.touch(c_file.to_s)
|
|
127
|
-
|
|
128
|
-
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
129
|
-
sqls = @db.sqls.dup
|
|
130
|
-
|
|
131
|
-
assert_equal(1, sqls.size)
|
|
132
|
-
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
133
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/two/a.delta'\)}, sqls[1])
|
|
134
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/three/a.csv'\)}, sqls[2])
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def test_should_create_view_from_compact_style_path
|
|
138
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
139
|
-
a_file = dir / 'one' / 'a.parquet'
|
|
140
|
-
b_file = dir / 'two' / 'b.delta'
|
|
141
|
-
c_file = dir / 'three' / 'c.csv'
|
|
142
|
-
FileUtils.mkdir_p(a_file.dirname)
|
|
143
|
-
FileUtils.mkdir_p(b_file.dirname)
|
|
144
|
-
FileUtils.mkdir_p(c_file.dirname)
|
|
145
|
-
FileUtils.touch(a_file.to_s)
|
|
146
|
-
FileUtils.touch(b_file.to_s)
|
|
147
|
-
FileUtils.touch(c_file.to_s)
|
|
148
|
-
|
|
149
|
-
@db.make_ready(search_path: [Dir["#{dir}/{one,two,three}"].map { |path| Pathname.new(path).glob('*') }])
|
|
150
|
-
sqls = @db.sqls.dup
|
|
151
|
-
|
|
152
|
-
assert_equal(3, sqls.size)
|
|
153
|
-
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
154
|
-
assert_match(%r{CREATE TEMPORARY VIEW `b` USING delta OPTIONS \('path'='/tmp/[^/]+/two/b.delta'\)}, sqls[1])
|
|
155
|
-
assert_match(%r{CREATE TEMPORARY VIEW `c` USING csv OPTIONS \('path'='/tmp/[^/]+/three/c.csv'\)}, sqls[2])
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def test_should_create_view_from_compact_style_path_with_multiple_files
|
|
159
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
160
|
-
a_file = dir / 'one' / 'a.parquet'
|
|
161
|
-
b_file = dir / 'two' / 'a.delta'
|
|
162
|
-
c_file = dir / 'three' / 'a.csv'
|
|
163
|
-
FileUtils.mkdir_p(a_file.dirname)
|
|
164
|
-
FileUtils.mkdir_p(b_file.dirname)
|
|
165
|
-
FileUtils.mkdir_p(c_file.dirname)
|
|
166
|
-
FileUtils.touch(a_file.to_s)
|
|
167
|
-
FileUtils.touch(b_file.to_s)
|
|
168
|
-
FileUtils.touch(c_file.to_s)
|
|
169
|
-
|
|
170
|
-
@db.make_ready(search_path: [Dir["#{dir}/{one,two,three}"].map { |path| Pathname.new(path).glob('*') }])
|
|
171
|
-
sqls = @db.sqls.dup
|
|
172
|
-
|
|
173
|
-
assert_equal(1, sqls.size)
|
|
174
|
-
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
175
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/two/a.delta'\)}, sqls[1])
|
|
176
|
-
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/three/a.csv'\)}, sqls[2])
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def test_duckdb_external_file_support
|
|
180
|
-
# Test DuckDB - uses read_* functions for external files
|
|
181
|
-
duckdb_db = Sequel.mock
|
|
182
|
-
duckdb_db.extension :make_readyable
|
|
183
|
-
def duckdb_db.database_type
|
|
184
|
-
:duckdb
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
188
|
-
parquet_file = dir / 'test.parquet'
|
|
189
|
-
csv_file = dir / 'test.csv'
|
|
190
|
-
json_file = dir / 'test.json'
|
|
191
|
-
FileUtils.touch(parquet_file.to_s)
|
|
192
|
-
FileUtils.touch(csv_file.to_s)
|
|
193
|
-
FileUtils.touch(json_file.to_s)
|
|
194
|
-
|
|
195
|
-
# Test parquet file
|
|
196
|
-
duckdb_db.make_ready(search_path: [parquet_file])
|
|
197
|
-
|
|
198
|
-
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_parquet\('.*test\.parquet'\)/, duckdb_db.sqls.last)
|
|
199
|
-
|
|
200
|
-
# Test CSV file
|
|
201
|
-
duckdb_db.sqls.clear
|
|
202
|
-
duckdb_db.make_ready(search_path: [csv_file])
|
|
203
|
-
|
|
204
|
-
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_csv_auto\('.*test\.csv'\)/, duckdb_db.sqls.last)
|
|
205
|
-
|
|
206
|
-
# Test JSON file
|
|
207
|
-
duckdb_db.sqls.clear
|
|
208
|
-
duckdb_db.make_ready(search_path: [json_file])
|
|
209
|
-
|
|
210
|
-
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_json_auto\('.*test\.json'\)/, duckdb_db.sqls.last)
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def test_unsupported_file_format_for_duckdb
|
|
214
|
-
# Test unsupported file format for DuckDB
|
|
215
|
-
duckdb_db = Sequel.mock
|
|
216
|
-
duckdb_db.extension :make_readyable
|
|
217
|
-
def duckdb_db.database_type
|
|
218
|
-
:duckdb
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
222
|
-
orc_file = dir / 'test.orc'
|
|
223
|
-
FileUtils.touch(orc_file.to_s)
|
|
224
|
-
|
|
225
|
-
error = assert_raises(Sequel::Error) do
|
|
226
|
-
duckdb_db.make_ready(search_path: [orc_file])
|
|
227
|
-
end
|
|
228
|
-
assert_match(/Unsupported file format 'orc' for DuckDB/, error.message)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def test_duckdb_directory_support
|
|
232
|
-
# Test DuckDB with directory paths (globbing)
|
|
233
|
-
duckdb_db = Sequel.mock
|
|
234
|
-
duckdb_db.extension :make_readyable
|
|
235
|
-
def duckdb_db.database_type
|
|
236
|
-
:duckdb
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Mock directory? to return true
|
|
240
|
-
dir = Pathname.new(Dir.mktmpdir)
|
|
241
|
-
def dir.directory?
|
|
242
|
-
true
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
file_sourcerer = Sequel::ReadyMaker::FileSourcerer.new(duckdb_db, dir)
|
|
246
|
-
|
|
247
|
-
# Override format to return parquet
|
|
248
|
-
def file_sourcerer.format
|
|
249
|
-
'parquet'
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# For directory support, DuckDB should use globbing pattern
|
|
253
|
-
file_sourcerer.create_view(:test_table)
|
|
254
|
-
|
|
255
|
-
assert_match(%r{CREATE VIEW \w*test_table\w* AS SELECT \* FROM read_parquet\('.*/\*\*/\*\.parquet'\)},
|
|
256
|
-
duckdb_db.sqls.last)
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
end
|