sequelizer 0.1.4 → 0.2.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/.beads/.gitignore +54 -0
- data/.beads/.jsonl.lock +0 -0
- data/.beads/.migration-hint-ts +1 -0
- data/.beads/README.md +81 -0
- data/.beads/config.yaml +42 -0
- data/.beads/issues.jsonl +20 -0
- data/.beads/metadata.json +7 -0
- data/.coderabbit.yaml +94 -0
- data/.devcontainer/.p10k.zsh +1713 -0
- data/.devcontainer/.zshrc +29 -0
- data/.devcontainer/Dockerfile +137 -0
- data/.devcontainer/copy-claude-credentials.sh +32 -0
- data/.devcontainer/devcontainer.json +102 -0
- data/.devcontainer/init-firewall.sh +123 -0
- data/.devcontainer/setup-credentials.sh +95 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/dependabot-auto-merge.yml +36 -0
- data/.github/workflows/test.yml +44 -9
- data/.gitignore +6 -1
- data/.overcommit.yml +73 -0
- data/.rubocop.yml +167 -0
- data/AGENTS.md +126 -0
- data/CHANGELOG.md +41 -0
- data/CLAUDE.md +230 -0
- data/Gemfile +6 -2
- data/Gemfile.lock +189 -0
- data/Guardfile +1 -1
- data/Rakefile +28 -3
- 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 +436 -0
- data/lib/sequel/extensions/db_opts.rb +65 -4
- data/lib/sequel/extensions/funky.rb +136 -0
- data/lib/sequel/extensions/make_readyable.rb +146 -30
- data/lib/sequel/extensions/more_sql.rb +76 -0
- data/lib/sequel/extensions/platform.rb +301 -0
- data/lib/sequel/extensions/settable.rb +64 -0
- data/lib/sequel/extensions/sql_recorder.rb +85 -0
- data/lib/sequel/extensions/unionize.rb +169 -0
- data/lib/sequel/extensions/usable.rb +30 -1
- data/lib/sequelizer/cli.rb +61 -18
- data/lib/sequelizer/connection_maker.rb +54 -72
- data/lib/sequelizer/env_config.rb +6 -6
- data/lib/sequelizer/gemfile_modifier.rb +23 -21
- data/lib/sequelizer/monkey_patches/database_in_after_connect.rb +7 -5
- data/lib/sequelizer/options.rb +102 -19
- data/lib/sequelizer/options_hash.rb +2 -0
- data/lib/sequelizer/version.rb +3 -1
- data/lib/sequelizer/yaml_config.rb +9 -4
- data/lib/sequelizer.rb +65 -9
- data/sequelizer.gemspec +20 -12
- data/test/lib/sequel/extensions/test_cold_col.rb +251 -0
- data/test/lib/sequel/extensions/test_db_opts.rb +10 -8
- data/test/lib/sequel/extensions/test_make_readyable.rb +198 -28
- data/test/lib/sequel/extensions/test_more_sql.rb +132 -0
- data/test/lib/sequel/extensions/test_platform.rb +222 -0
- data/test/lib/sequel/extensions/test_settable.rb +109 -0
- data/test/lib/sequel/extensions/test_sql_recorder.rb +231 -0
- data/test/lib/sequel/extensions/test_unionize.rb +76 -0
- data/test/lib/sequel/extensions/test_usable.rb +5 -2
- data/test/lib/sequelizer/test_connection_maker.rb +21 -17
- data/test/lib/sequelizer/test_env_config.rb +5 -2
- data/test/lib/sequelizer/test_gemfile_modifier.rb +7 -6
- data/test/lib/sequelizer/test_options.rb +42 -9
- data/test/lib/sequelizer/test_yaml_config.rb +13 -12
- data/test/test_helper.rb +37 -8
- metadata +196 -39
- data/lib/sequel/extensions/sqls.rb +0 -31
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
require_relative
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'sequel'
|
|
5
|
+
require 'sequel/extensions/make_readyable'
|
|
6
6
|
|
|
7
7
|
class TestUsable < Minitest::Test
|
|
8
|
+
|
|
8
9
|
def setup
|
|
9
10
|
# These features are mostly intended for Spark, but sqlite is a close enough
|
|
10
11
|
# mock that we'll just roll with it
|
|
@@ -15,74 +16,243 @@ class TestUsable < Minitest::Test
|
|
|
15
16
|
when :schema1
|
|
16
17
|
[:a]
|
|
17
18
|
when :schema2
|
|
18
|
-
[
|
|
19
|
+
%i[a b]
|
|
19
20
|
when :schema3
|
|
20
|
-
[
|
|
21
|
+
%i[a b]
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def test_should_call_use_schema
|
|
26
27
|
@db.make_ready(use_schema: :some_schema)
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
assert_equal(['USE `some_schema`'], @db.sqls)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def test_should_create_views_based_on_tables_in_search_paths
|
|
31
|
-
@db.make_ready(search_path: [
|
|
33
|
+
@db.make_ready(search_path: %i[schema1 schema2 schema3])
|
|
34
|
+
|
|
32
35
|
assert_equal([
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
'CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`',
|
|
37
|
+
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
38
|
+
], @db.sqls)
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def test_should_create_views_based_on_tables_in_search_paths_passed_as_strings
|
|
39
|
-
@db.make_ready(search_path: [
|
|
42
|
+
@db.make_ready(search_path: %w[schema1 schema2 schema3])
|
|
43
|
+
|
|
40
44
|
assert_equal([
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
'CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`',
|
|
46
|
+
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
47
|
+
], @db.sqls)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
def test_should_create_views_based_on_tables_in_search_paths_accepts_except
|
|
47
|
-
@db.make_ready(search_path: [
|
|
51
|
+
@db.make_ready(search_path: %i[schema1 schema2 schema3], except: :a)
|
|
52
|
+
|
|
48
53
|
assert_equal([
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
55
|
+
], @db.sqls)
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
def test_should_create_views_based_on_tables_in_search_paths_accepts_only
|
|
54
|
-
@db.make_ready(search_path: [
|
|
59
|
+
@db.make_ready(search_path: %i[schema1 schema2 schema3], only: :b)
|
|
60
|
+
|
|
55
61
|
assert_equal([
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
'CREATE TEMPORARY VIEW `b` AS SELECT * FROM `schema2`.`b`',
|
|
63
|
+
], @db.sqls)
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
def test_should_create_views_based_on_path
|
|
61
67
|
dir = Pathname.new(Dir.mktmpdir)
|
|
62
|
-
a_file = dir
|
|
63
|
-
b_file = dir
|
|
68
|
+
a_file = dir.join('a.parquet')
|
|
69
|
+
b_file = dir.join('b.parquet')
|
|
64
70
|
FileUtils.touch(a_file.to_s)
|
|
65
71
|
FileUtils.touch(b_file.to_s)
|
|
66
72
|
|
|
67
73
|
@db.make_ready(search_path: [:schema1, a_file, b_file, :schema2])
|
|
68
74
|
sqls = @db.sqls.dup
|
|
69
|
-
|
|
75
|
+
|
|
76
|
+
assert_equal('CREATE TEMPORARY VIEW `a` AS SELECT * FROM `schema1`.`a`', sqls[0])
|
|
70
77
|
assert_match(%r{CREATE TEMPORARY VIEW `b` USING parquet OPTIONS \('path'='/tmp/[^/]+/b.parquet'\)}, sqls[1])
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
def test_should_create_views_format_based_on_path
|
|
74
81
|
dir = Pathname.new(Dir.mktmpdir)
|
|
75
|
-
a_file = dir
|
|
76
|
-
b_file = dir
|
|
77
|
-
c_file = dir
|
|
82
|
+
a_file = dir.join('a.parquet')
|
|
83
|
+
b_file = dir.join('b.delta')
|
|
84
|
+
c_file = dir.join('c.csv')
|
|
78
85
|
FileUtils.touch(a_file.to_s)
|
|
79
86
|
FileUtils.touch(b_file.to_s)
|
|
80
87
|
FileUtils.touch(c_file.to_s)
|
|
81
88
|
|
|
82
89
|
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
83
90
|
sqls = @db.sqls.dup
|
|
91
|
+
|
|
84
92
|
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/a.parquet'\)}, sqls[0])
|
|
85
93
|
assert_match(%r{CREATE TEMPORARY VIEW `b` USING delta OPTIONS \('path'='/tmp/[^/]+/b.delta'\)}, sqls[1])
|
|
86
94
|
assert_match(%r{CREATE TEMPORARY VIEW `c` USING csv OPTIONS \('path'='/tmp/[^/]+/c.csv'\)}, sqls[2])
|
|
87
95
|
end
|
|
96
|
+
|
|
97
|
+
def test_should_create_a_single_view_if_multiple_files_have_the_same_name
|
|
98
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
99
|
+
a_file = dir.join('a.parquet')
|
|
100
|
+
b_file = dir.join('a.delta')
|
|
101
|
+
c_file = dir.join('a.csv')
|
|
102
|
+
FileUtils.touch(a_file.to_s)
|
|
103
|
+
FileUtils.touch(b_file.to_s)
|
|
104
|
+
FileUtils.touch(c_file.to_s)
|
|
105
|
+
|
|
106
|
+
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
107
|
+
sqls = @db.sqls.dup
|
|
108
|
+
|
|
109
|
+
assert_equal(1, sqls.size)
|
|
110
|
+
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/a.parquet'\)}, sqls[0])
|
|
111
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/a.delta'\)}, sqls[1])
|
|
112
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/a.csv'\)}, sqls[2])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_should_create_a_single_view_if_multiple_files_have_the_same_name_and_are_in_different_directories
|
|
116
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
117
|
+
a_file = dir / 'one' / 'a.parquet'
|
|
118
|
+
b_file = dir / 'two' / 'a.delta'
|
|
119
|
+
c_file = dir / 'three' / 'a.csv'
|
|
120
|
+
FileUtils.mkdir_p(a_file.dirname)
|
|
121
|
+
FileUtils.mkdir_p(b_file.dirname)
|
|
122
|
+
FileUtils.mkdir_p(c_file.dirname)
|
|
123
|
+
FileUtils.touch(a_file.to_s)
|
|
124
|
+
FileUtils.touch(b_file.to_s)
|
|
125
|
+
FileUtils.touch(c_file.to_s)
|
|
126
|
+
|
|
127
|
+
@db.make_ready(search_path: [a_file, b_file, c_file])
|
|
128
|
+
sqls = @db.sqls.dup
|
|
129
|
+
|
|
130
|
+
assert_equal(1, sqls.size)
|
|
131
|
+
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
132
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/two/a.delta'\)}, sqls[1])
|
|
133
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/three/a.csv'\)}, sqls[2])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_should_create_view_from_compact_style_path
|
|
137
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
138
|
+
a_file = dir / 'one' / 'a.parquet'
|
|
139
|
+
b_file = dir / 'two' / 'b.delta'
|
|
140
|
+
c_file = dir / 'three' / 'c.csv'
|
|
141
|
+
FileUtils.mkdir_p(a_file.dirname)
|
|
142
|
+
FileUtils.mkdir_p(b_file.dirname)
|
|
143
|
+
FileUtils.mkdir_p(c_file.dirname)
|
|
144
|
+
FileUtils.touch(a_file.to_s)
|
|
145
|
+
FileUtils.touch(b_file.to_s)
|
|
146
|
+
FileUtils.touch(c_file.to_s)
|
|
147
|
+
|
|
148
|
+
@db.make_ready(search_path: [Dir["#{dir}/{one,two,three}"].map { |path| Pathname.new(path).glob('*') }])
|
|
149
|
+
sqls = @db.sqls.dup
|
|
150
|
+
|
|
151
|
+
assert_equal(3, sqls.size)
|
|
152
|
+
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
153
|
+
assert_match(%r{CREATE TEMPORARY VIEW `b` USING delta OPTIONS \('path'='/tmp/[^/]+/two/b.delta'\)}, sqls[1])
|
|
154
|
+
assert_match(%r{CREATE TEMPORARY VIEW `c` USING csv OPTIONS \('path'='/tmp/[^/]+/three/c.csv'\)}, sqls[2])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_should_create_view_from_compact_style_path_with_multiple_files
|
|
158
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
159
|
+
a_file = dir / 'one' / 'a.parquet'
|
|
160
|
+
b_file = dir / 'two' / 'a.delta'
|
|
161
|
+
c_file = dir / 'three' / 'a.csv'
|
|
162
|
+
FileUtils.mkdir_p(a_file.dirname)
|
|
163
|
+
FileUtils.mkdir_p(b_file.dirname)
|
|
164
|
+
FileUtils.mkdir_p(c_file.dirname)
|
|
165
|
+
FileUtils.touch(a_file.to_s)
|
|
166
|
+
FileUtils.touch(b_file.to_s)
|
|
167
|
+
FileUtils.touch(c_file.to_s)
|
|
168
|
+
|
|
169
|
+
@db.make_ready(search_path: [Dir["#{dir}/{one,two,three}"].map { |path| Pathname.new(path).glob('*') }])
|
|
170
|
+
sqls = @db.sqls.dup
|
|
171
|
+
|
|
172
|
+
assert_equal(1, sqls.size)
|
|
173
|
+
assert_match(%r{CREATE TEMPORARY VIEW `a` USING parquet OPTIONS \('path'='/tmp/[^/]+/one/a.parquet'\)}, sqls[0])
|
|
174
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING delta OPTIONS \('path'='/tmp/[^/]+/two/a.delta'\)}, sqls[1])
|
|
175
|
+
refute_match(%r{CREATE TEMPORARY VIEW `a` USING csv OPTIONS \('path'='/tmp/[^/]+/three/a.csv'\)}, sqls[2])
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_duckdb_external_file_support
|
|
179
|
+
# Test DuckDB - uses read_* functions for external files
|
|
180
|
+
duckdb_db = Sequel.mock
|
|
181
|
+
duckdb_db.extension :make_readyable
|
|
182
|
+
def duckdb_db.database_type
|
|
183
|
+
:duckdb
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
187
|
+
parquet_file = dir / 'test.parquet'
|
|
188
|
+
csv_file = dir / 'test.csv'
|
|
189
|
+
json_file = dir / 'test.json'
|
|
190
|
+
FileUtils.touch(parquet_file.to_s)
|
|
191
|
+
FileUtils.touch(csv_file.to_s)
|
|
192
|
+
FileUtils.touch(json_file.to_s)
|
|
193
|
+
|
|
194
|
+
# Test parquet file
|
|
195
|
+
duckdb_db.make_ready(search_path: [parquet_file])
|
|
196
|
+
|
|
197
|
+
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_parquet\('.*test\.parquet'\)/, duckdb_db.sqls.last)
|
|
198
|
+
|
|
199
|
+
# Test CSV file
|
|
200
|
+
duckdb_db.sqls.clear
|
|
201
|
+
duckdb_db.make_ready(search_path: [csv_file])
|
|
202
|
+
|
|
203
|
+
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_csv_auto\('.*test\.csv'\)/, duckdb_db.sqls.last)
|
|
204
|
+
|
|
205
|
+
# Test JSON file
|
|
206
|
+
duckdb_db.sqls.clear
|
|
207
|
+
duckdb_db.make_ready(search_path: [json_file])
|
|
208
|
+
|
|
209
|
+
assert_match(/CREATE VIEW \w*test\w* AS SELECT \* FROM read_json_auto\('.*test\.json'\)/, duckdb_db.sqls.last)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def test_unsupported_file_format_for_duckdb
|
|
213
|
+
# Test unsupported file format for DuckDB
|
|
214
|
+
duckdb_db = Sequel.mock
|
|
215
|
+
duckdb_db.extension :make_readyable
|
|
216
|
+
def duckdb_db.database_type
|
|
217
|
+
:duckdb
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
221
|
+
orc_file = dir / 'test.orc'
|
|
222
|
+
FileUtils.touch(orc_file.to_s)
|
|
223
|
+
|
|
224
|
+
error = assert_raises(Sequel::Error) do
|
|
225
|
+
duckdb_db.make_ready(search_path: [orc_file])
|
|
226
|
+
end
|
|
227
|
+
assert_match(/Unsupported file format 'orc' for DuckDB/, error.message)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def test_duckdb_directory_support
|
|
231
|
+
# Test DuckDB with directory paths (globbing)
|
|
232
|
+
duckdb_db = Sequel.mock
|
|
233
|
+
duckdb_db.extension :make_readyable
|
|
234
|
+
def duckdb_db.database_type
|
|
235
|
+
:duckdb
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Mock directory? to return true
|
|
239
|
+
dir = Pathname.new(Dir.mktmpdir)
|
|
240
|
+
def dir.directory?
|
|
241
|
+
true
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
file_sourcerer = Sequel::ReadyMaker::FileSourcerer.new(duckdb_db, dir)
|
|
245
|
+
|
|
246
|
+
# Override format to return parquet
|
|
247
|
+
def file_sourcerer.format
|
|
248
|
+
'parquet'
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# For directory support, DuckDB should use globbing pattern
|
|
252
|
+
file_sourcerer.create_view(:test_table)
|
|
253
|
+
|
|
254
|
+
assert_match(%r{CREATE VIEW \w*test_table\w* AS SELECT \* FROM read_parquet\('.*/\*\*/\*\.parquet'\)},
|
|
255
|
+
duckdb_db.sqls.last)
|
|
256
|
+
end
|
|
257
|
+
|
|
88
258
|
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'sequel'
|
|
5
|
+
require 'sequel/extensions/more_sql'
|
|
6
|
+
|
|
7
|
+
class TestMoreSql < Minitest::Test
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@db = Sequel.mock
|
|
11
|
+
@db.extension :more_sql
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_create_schema_with_symbol_name
|
|
15
|
+
@db.create_schema(:test_schema)
|
|
16
|
+
|
|
17
|
+
sqls = @db.sqls
|
|
18
|
+
|
|
19
|
+
assert_equal 1, sqls.length
|
|
20
|
+
assert_equal 'CREATE SCHEMA test_schema', sqls.first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_create_schema_with_string_name
|
|
24
|
+
@db.create_schema('my_schema')
|
|
25
|
+
|
|
26
|
+
sqls = @db.sqls
|
|
27
|
+
|
|
28
|
+
assert_equal 1, sqls.length
|
|
29
|
+
assert_equal "CREATE SCHEMA 'my_schema'", sqls.first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_create_schema_with_if_not_exists_option
|
|
33
|
+
@db.create_schema(:analytics, if_not_exists: true)
|
|
34
|
+
|
|
35
|
+
sqls = @db.sqls
|
|
36
|
+
|
|
37
|
+
assert_equal 1, sqls.length
|
|
38
|
+
assert_equal 'CREATE SCHEMA IF NOT EXISTS analytics', sqls.first
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_create_schema_without_if_not_exists_option
|
|
42
|
+
@db.create_schema(:reports, if_not_exists: false)
|
|
43
|
+
|
|
44
|
+
sqls = @db.sqls
|
|
45
|
+
|
|
46
|
+
assert_equal 1, sqls.length
|
|
47
|
+
assert_equal 'CREATE SCHEMA reports', sqls.first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_create_schema_with_empty_options
|
|
51
|
+
@db.create_schema(:staging, {})
|
|
52
|
+
|
|
53
|
+
sqls = @db.sqls
|
|
54
|
+
|
|
55
|
+
assert_equal 1, sqls.length
|
|
56
|
+
assert_equal 'CREATE SCHEMA staging', sqls.first
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_create_schema_with_special_characters_in_name
|
|
60
|
+
@db.create_schema('schema-with-dashes')
|
|
61
|
+
|
|
62
|
+
sqls = @db.sqls
|
|
63
|
+
|
|
64
|
+
assert_equal 1, sqls.length
|
|
65
|
+
assert_equal "CREATE SCHEMA 'schema-with-dashes'", sqls.first
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_create_schema_returns_nil
|
|
69
|
+
result = @db.create_schema(:test)
|
|
70
|
+
|
|
71
|
+
assert_nil result
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_create_schema_multiple_calls
|
|
75
|
+
@db.create_schema(:first_schema)
|
|
76
|
+
@db.create_schema(:second_schema, if_not_exists: true)
|
|
77
|
+
@db.create_schema('third_schema')
|
|
78
|
+
|
|
79
|
+
sqls = @db.sqls
|
|
80
|
+
|
|
81
|
+
assert_equal 3, sqls.length
|
|
82
|
+
assert_equal 'CREATE SCHEMA first_schema', sqls[0]
|
|
83
|
+
assert_equal 'CREATE SCHEMA IF NOT EXISTS second_schema', sqls[1]
|
|
84
|
+
assert_equal "CREATE SCHEMA 'third_schema'", sqls[2]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_extension_registration
|
|
88
|
+
assert_respond_to Sequel::Database, :extension
|
|
89
|
+
|
|
90
|
+
db = Sequel.mock
|
|
91
|
+
|
|
92
|
+
assert_respond_to db, :extension
|
|
93
|
+
|
|
94
|
+
db.extension :more_sql
|
|
95
|
+
|
|
96
|
+
assert_respond_to db, :create_schema
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_create_schema_with_qualified_name
|
|
100
|
+
@db.create_schema(Sequel[:public][:my_schema])
|
|
101
|
+
|
|
102
|
+
sqls = @db.sqls
|
|
103
|
+
|
|
104
|
+
assert_equal 1, sqls.length
|
|
105
|
+
assert_match(/CREATE SCHEMA/, sqls.first)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_create_schema_with_identifier
|
|
109
|
+
@db.create_schema(Sequel.identifier(:test_schema))
|
|
110
|
+
|
|
111
|
+
sqls = @db.sqls
|
|
112
|
+
|
|
113
|
+
assert_equal 1, sqls.length
|
|
114
|
+
assert_equal 'CREATE SCHEMA test_schema', sqls.first
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_create_schema_handles_nil_options
|
|
118
|
+
@db.create_schema(:test_schema)
|
|
119
|
+
|
|
120
|
+
sqls = @db.sqls
|
|
121
|
+
|
|
122
|
+
assert_equal 1, sqls.length
|
|
123
|
+
assert_equal 'CREATE SCHEMA test_schema', sqls.first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_private_create_schema_sql_method_not_accessible
|
|
127
|
+
assert_raises(NoMethodError) do
|
|
128
|
+
@db.create_schema_sql(:test, {})
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
require 'sequel'
|
|
3
|
+
require 'sequel/extensions/platform'
|
|
4
|
+
|
|
5
|
+
class TestPlatform < Minitest::Test
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
# Point to the gem's config directory
|
|
9
|
+
@original_config_dir = Sequel::Platform.config_dir
|
|
10
|
+
Sequel::Platform.config_dir = File.expand_path('../../../../config/platforms', __dir__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def teardown
|
|
14
|
+
Sequel::Platform.config_dir = @original_config_dir
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# --- Extension Registration ---
|
|
18
|
+
|
|
19
|
+
def test_should_register_extension
|
|
20
|
+
db = Sequel.mock(host: :postgres)
|
|
21
|
+
|
|
22
|
+
assert_respond_to db, :extension
|
|
23
|
+
|
|
24
|
+
db.extension :platform
|
|
25
|
+
|
|
26
|
+
assert_respond_to db, :platform
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_platform_returns_platform_instance
|
|
30
|
+
db = Sequel.mock(host: :postgres)
|
|
31
|
+
db.extension :platform
|
|
32
|
+
|
|
33
|
+
assert_kind_of Sequel::Platform::Base, db.platform
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# --- Platform Class Selection ---
|
|
37
|
+
|
|
38
|
+
def test_selects_postgres_platform_for_postgres_adapter
|
|
39
|
+
db = Sequel.mock(host: :postgres)
|
|
40
|
+
db.extension :platform
|
|
41
|
+
|
|
42
|
+
assert_kind_of Sequel::Platform::Postgres, db.platform
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_selects_spark_platform_for_spark_adapter
|
|
46
|
+
db = Sequel.mock(host: :spark)
|
|
47
|
+
db.extension :platform
|
|
48
|
+
|
|
49
|
+
assert_kind_of Sequel::Platform::Spark, db.platform
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_selects_snowflake_platform_for_snowflake_adapter
|
|
53
|
+
db = Sequel.mock(host: :snowflake)
|
|
54
|
+
db.extension :platform
|
|
55
|
+
|
|
56
|
+
assert_kind_of Sequel::Platform::Snowflake, db.platform
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_selects_athena_platform_for_athena_adapter
|
|
60
|
+
db = Sequel.mock(host: :athena)
|
|
61
|
+
db.extension :platform
|
|
62
|
+
|
|
63
|
+
assert_kind_of Sequel::Platform::Athena, db.platform
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_selects_athena_platform_for_presto_adapter
|
|
67
|
+
db = Sequel.mock(host: :presto)
|
|
68
|
+
db.extension :platform
|
|
69
|
+
|
|
70
|
+
assert_kind_of Sequel::Platform::Athena, db.platform
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_selects_base_platform_for_unknown_adapter
|
|
74
|
+
db = Sequel.mock(host: :sqlite)
|
|
75
|
+
db.extension :platform
|
|
76
|
+
|
|
77
|
+
assert_instance_of Sequel::Platform::Base, db.platform
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# --- Config Loading ---
|
|
81
|
+
|
|
82
|
+
def test_supports_returns_boolean_from_config
|
|
83
|
+
db = Sequel.mock(host: :postgres)
|
|
84
|
+
db.extension :platform
|
|
85
|
+
|
|
86
|
+
assert db.platform.supports?(:cte)
|
|
87
|
+
assert db.platform.supports?(:cte_recursive)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_supports_returns_false_for_unsupported_feature
|
|
91
|
+
db = Sequel.mock(host: :athena)
|
|
92
|
+
db.extension :platform
|
|
93
|
+
|
|
94
|
+
refute db.platform.supports?(:temp_tables)
|
|
95
|
+
refute db.platform.supports?(:cte_recursive)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_prefers_returns_boolean_from_config
|
|
99
|
+
db = Sequel.mock(host: :postgres)
|
|
100
|
+
db.extension :platform
|
|
101
|
+
|
|
102
|
+
assert db.platform.prefers?(:cte)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_prefers_returns_false_when_not_preferred
|
|
106
|
+
db = Sequel.mock(host: :spark)
|
|
107
|
+
db.extension :platform
|
|
108
|
+
|
|
109
|
+
refute db.platform.prefers?(:cte)
|
|
110
|
+
assert db.platform.prefers?(:parquet)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_bracket_access_returns_config_values
|
|
114
|
+
db = Sequel.mock(host: :postgres)
|
|
115
|
+
db.extension :platform
|
|
116
|
+
|
|
117
|
+
assert_equal 'search_path', db.platform[:schema_switching_method]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_config_stacking_overrides_base
|
|
121
|
+
db = Sequel.mock(host: :athena)
|
|
122
|
+
db.extension :platform
|
|
123
|
+
|
|
124
|
+
# Athena overrides base supports_temp_tables from true to false
|
|
125
|
+
refute db.platform.supports?(:temp_tables)
|
|
126
|
+
# Athena overrides drop_table_needs_unquoted from false to true
|
|
127
|
+
assert db.platform[:drop_table_needs_unquoted]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# --- Function Translations ---
|
|
131
|
+
|
|
132
|
+
def test_postgres_date_diff_uses_subtraction
|
|
133
|
+
db = Sequel.mock(host: :postgres)
|
|
134
|
+
db.extension :platform
|
|
135
|
+
|
|
136
|
+
expr = db.platform.date_diff(:start_date, :end_date)
|
|
137
|
+
|
|
138
|
+
# Should produce subtraction syntax - use dataset.literal to get SQL
|
|
139
|
+
sql = db.literal(expr)
|
|
140
|
+
|
|
141
|
+
assert_includes sql, '-'
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_spark_date_diff_uses_datediff_function
|
|
145
|
+
db = Sequel.mock(host: :spark)
|
|
146
|
+
db.extension :platform
|
|
147
|
+
|
|
148
|
+
expr = db.platform.date_diff(:start_date, :end_date)
|
|
149
|
+
|
|
150
|
+
# Should use datediff function with reversed args
|
|
151
|
+
sql = db.literal(expr)
|
|
152
|
+
|
|
153
|
+
assert_includes sql.downcase, 'datediff'
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_snowflake_date_diff_includes_day_unit
|
|
157
|
+
db = Sequel.mock(host: :snowflake)
|
|
158
|
+
db.extension :platform
|
|
159
|
+
|
|
160
|
+
expr = db.platform.date_diff(:start_date, :end_date)
|
|
161
|
+
|
|
162
|
+
sql = db.literal(expr)
|
|
163
|
+
|
|
164
|
+
assert_includes sql.downcase, 'datediff'
|
|
165
|
+
assert_includes sql, 'day'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_athena_date_diff_uses_date_diff_function
|
|
169
|
+
db = Sequel.mock(host: :athena)
|
|
170
|
+
db.extension :platform
|
|
171
|
+
|
|
172
|
+
expr = db.platform.date_diff(:start_date, :end_date)
|
|
173
|
+
|
|
174
|
+
sql = db.literal(expr)
|
|
175
|
+
|
|
176
|
+
assert_includes sql.downcase, 'date_diff'
|
|
177
|
+
assert_includes sql, 'day'
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def test_cast_date_returns_cast_expression
|
|
181
|
+
db = Sequel.mock(host: :postgres)
|
|
182
|
+
db.extension :platform
|
|
183
|
+
|
|
184
|
+
expr = db.platform.cast_date(:some_column)
|
|
185
|
+
|
|
186
|
+
assert_kind_of Sequel::SQL::Cast, expr
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# --- Edge Cases ---
|
|
190
|
+
|
|
191
|
+
def test_supports_unknown_feature_returns_false
|
|
192
|
+
db = Sequel.mock(host: :postgres)
|
|
193
|
+
db.extension :platform
|
|
194
|
+
|
|
195
|
+
refute db.platform.supports?(:nonexistent_feature)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_prefers_unknown_feature_returns_false
|
|
199
|
+
db = Sequel.mock(host: :postgres)
|
|
200
|
+
db.extension :platform
|
|
201
|
+
|
|
202
|
+
refute db.platform.prefers?(:nonexistent_feature)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_bracket_access_unknown_key_returns_nil
|
|
206
|
+
db = Sequel.mock(host: :postgres)
|
|
207
|
+
db.extension :platform
|
|
208
|
+
|
|
209
|
+
assert_nil db.platform[:nonexistent_key]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def test_fetch_with_default
|
|
213
|
+
db = Sequel.mock(host: :postgres)
|
|
214
|
+
db.extension :platform
|
|
215
|
+
|
|
216
|
+
# Known key
|
|
217
|
+
assert_equal 'search_path', db.platform.fetch(:schema_switching_method, 'default')
|
|
218
|
+
# Unknown key
|
|
219
|
+
assert_equal 'default', db.platform.fetch(:unknown_key, 'default')
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
end
|