sequelizer 0.1.6 → 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/.github/dependabot.yml +18 -0
- data/.github/workflows/dependabot-auto-merge.yml +36 -0
- data/.github/workflows/test.yml +44 -9
- data/AGENTS.md +126 -0
- data/CHANGELOG.md +17 -0
- data/CLAUDE.md +11 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +84 -53
- 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 +2 -2
- data/lib/sequel/extensions/funky.rb +136 -0
- data/lib/sequel/extensions/make_readyable.rb +0 -2
- data/lib/sequel/extensions/platform.rb +301 -0
- data/lib/sequelizer/options.rb +5 -1
- data/lib/sequelizer/version.rb +1 -1
- data/lib/sequelizer/yaml_config.rb +0 -1
- data/sequelizer.gemspec +10 -7
- data/test/lib/sequel/extensions/test_cold_col.rb +2 -2
- data/test/lib/sequel/extensions/test_make_readyable.rb +0 -1
- data/test/lib/sequel/extensions/test_platform.rb +222 -0
- data/test/lib/sequelizer/test_connection_maker.rb +2 -2
- data/test/lib/sequelizer/test_gemfile_modifier.rb +2 -2
- data/test/lib/sequelizer/test_options.rb +28 -0
- data/test/test_helper.rb +1 -0
- metadata +97 -19
- data/.claude/settings.local.json +0 -64
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# The platform extension provides a unified interface for platform-specific
|
|
5
|
+
# database behavior. It uses KVCSV configuration files to define capabilities
|
|
6
|
+
# and preferences, with Ruby classes only for function translations.
|
|
7
|
+
#
|
|
8
|
+
# DB.extension :platform
|
|
9
|
+
# DB.platform.supports?(:cte) # => true (from config)
|
|
10
|
+
# DB.platform.prefers?(:cte) # => true (from config)
|
|
11
|
+
# DB.platform[:interim_schema] # => nil (from config)
|
|
12
|
+
# DB.platform.date_diff(from, to) # => Sequel expression (from code)
|
|
13
|
+
#
|
|
14
|
+
# Configuration is loaded from CSV files in config/platforms/:
|
|
15
|
+
# - base.csv: conservative defaults
|
|
16
|
+
# - rdbms/<adapter>.csv: RDBMS-specific overrides
|
|
17
|
+
# - Additional configs can be stacked via platform_configs option
|
|
18
|
+
#
|
|
19
|
+
# Related module: Sequel::Platform
|
|
20
|
+
|
|
21
|
+
require 'kvcsv'
|
|
22
|
+
|
|
23
|
+
module Sequel
|
|
24
|
+
|
|
25
|
+
# The Platform module provides database platform abstraction through
|
|
26
|
+
# configuration-driven capabilities and code-driven function translations.
|
|
27
|
+
module Platform
|
|
28
|
+
|
|
29
|
+
# Base platform class with KVCSV config loading and default implementations.
|
|
30
|
+
# Subclasses override function translation methods for platform-specific SQL.
|
|
31
|
+
class Base
|
|
32
|
+
|
|
33
|
+
attr_reader :db, :config
|
|
34
|
+
|
|
35
|
+
# Initialize platform with database connection and config paths.
|
|
36
|
+
#
|
|
37
|
+
# @param db [Sequel::Database] The database connection
|
|
38
|
+
# @param config_paths [Array<String>] Paths to CSV config files (stacked in order)
|
|
39
|
+
def initialize(db, *config_paths)
|
|
40
|
+
@db = db
|
|
41
|
+
@config = config_paths.empty? ? {} : KVCSV::Settings.new(*config_paths)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if the platform supports a feature.
|
|
45
|
+
#
|
|
46
|
+
# @param feature [Symbol] Feature name (e.g., :cte, :temp_tables)
|
|
47
|
+
# @return [Boolean] true if supported
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# platform.supports?(:cte) # checks supports_cte in config
|
|
51
|
+
# platform.supports?(:temp_tables) # checks supports_temp_tables in config
|
|
52
|
+
def supports?(feature)
|
|
53
|
+
config[:"supports_#{feature}"] || false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if the platform prefers a feature (may support but not prefer).
|
|
57
|
+
#
|
|
58
|
+
# @param feature [Symbol] Feature name (e.g., :cte, :parquet)
|
|
59
|
+
# @return [Boolean] true if preferred
|
|
60
|
+
#
|
|
61
|
+
# @example
|
|
62
|
+
# platform.prefers?(:cte) # Spark supports CTEs but doesn't prefer them
|
|
63
|
+
# platform.prefers?(:parquet) # Spark prefers parquet format
|
|
64
|
+
def prefers?(feature)
|
|
65
|
+
config[:"prefers_#{feature}"] || false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Access arbitrary config values.
|
|
69
|
+
#
|
|
70
|
+
# @param key [Symbol] Config key
|
|
71
|
+
# @return [Object] Config value or nil
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# platform[:interim_schema] # => "scratch"
|
|
75
|
+
# platform[:schema_switching_method] # => "use"
|
|
76
|
+
def [](key)
|
|
77
|
+
config[key]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Fetch config value with default.
|
|
81
|
+
#
|
|
82
|
+
# @param key [Symbol] Config key
|
|
83
|
+
# @param default [Object] Default value if key not found
|
|
84
|
+
# @return [Object] Config value or default
|
|
85
|
+
def fetch(key, default = nil)
|
|
86
|
+
config.respond_to?(:fetch) ? config.fetch(key, default) : (config[key] || default)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ---- Function translations (override in subclasses) ----
|
|
90
|
+
|
|
91
|
+
# Calculate date difference between two dates.
|
|
92
|
+
#
|
|
93
|
+
# @param from [Symbol, Sequel::SQL::Expression] Start date
|
|
94
|
+
# @param to [Symbol, Sequel::SQL::Expression] End date
|
|
95
|
+
# @return [Sequel::SQL::Expression] Date difference expression
|
|
96
|
+
def date_diff(from, to)
|
|
97
|
+
Sequel.function(:datediff, from, to)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Cast expression to date type.
|
|
101
|
+
#
|
|
102
|
+
# @param expr [Object] Expression to cast
|
|
103
|
+
# @return [Sequel::SQL::Cast] Cast expression
|
|
104
|
+
def cast_date(expr)
|
|
105
|
+
Sequel.cast(expr, Date)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Parse string to date with format.
|
|
109
|
+
#
|
|
110
|
+
# @param value [Object] String value to parse
|
|
111
|
+
# @param format [String] Date format string
|
|
112
|
+
# @return [Sequel::SQL::Expression] Parsed date expression
|
|
113
|
+
def str_to_date(value, format)
|
|
114
|
+
Sequel.function(:to_date, value, format)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Calculate days between two dates.
|
|
118
|
+
#
|
|
119
|
+
# @param from [Symbol, Sequel::SQL::Expression] Start date
|
|
120
|
+
# @param to [Symbol, Sequel::SQL::Expression] End date
|
|
121
|
+
# @return [Sequel::SQL::Expression] Days between expression
|
|
122
|
+
def days_between(from, to)
|
|
123
|
+
date_diff(from, to)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# PostgreSQL platform with Postgres-specific function translations.
|
|
129
|
+
class Postgres < Base
|
|
130
|
+
|
|
131
|
+
def date_diff(from, to)
|
|
132
|
+
# Postgres uses date subtraction
|
|
133
|
+
Sequel.lit('(? - ?)', to, from)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def days_between(from, to)
|
|
137
|
+
# Postgres date subtraction returns integer days
|
|
138
|
+
Sequel.lit('(? - ?)', to, from)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Spark platform with Spark SQL-specific function translations.
|
|
144
|
+
class Spark < Base
|
|
145
|
+
|
|
146
|
+
def date_diff(from, to)
|
|
147
|
+
# Spark datediff has reversed argument order (end, start)
|
|
148
|
+
Sequel.function(:datediff, to, from)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def str_to_date(value, format)
|
|
152
|
+
Sequel.function(:to_date, Sequel.cast_string(value), format)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Snowflake platform with Snowflake-specific function translations.
|
|
158
|
+
class Snowflake < Base
|
|
159
|
+
|
|
160
|
+
def date_diff(from, to)
|
|
161
|
+
# Snowflake requires unit parameter
|
|
162
|
+
Sequel.function(:datediff, 'day', from, to)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def days_between(from, to)
|
|
166
|
+
Sequel.function(:datediff, 'day', from, to)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Athena platform (Presto/Trino based) with Athena-specific function translations.
|
|
172
|
+
class Athena < Base
|
|
173
|
+
|
|
174
|
+
def date_diff(from, to)
|
|
175
|
+
# Athena/Presto uses date_diff with unit
|
|
176
|
+
Sequel.function(:date_diff, 'day', from, to)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def days_between(from, to)
|
|
180
|
+
Sequel.function(:date_diff, 'day', from, to)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Map adapter schemes to platform classes
|
|
186
|
+
PLATFORM_CLASSES = {
|
|
187
|
+
postgres: Postgres,
|
|
188
|
+
postgresql: Postgres,
|
|
189
|
+
spark: Spark,
|
|
190
|
+
athena: Athena,
|
|
191
|
+
presto: Athena,
|
|
192
|
+
trino: Athena,
|
|
193
|
+
snowflake: Snowflake,
|
|
194
|
+
}.freeze
|
|
195
|
+
|
|
196
|
+
# Map adapter schemes to config file names
|
|
197
|
+
ADAPTER_CONFIG_NAMES = {
|
|
198
|
+
postgres: 'postgres',
|
|
199
|
+
postgresql: 'postgres',
|
|
200
|
+
spark: 'spark',
|
|
201
|
+
athena: 'athena',
|
|
202
|
+
presto: 'athena',
|
|
203
|
+
trino: 'athena',
|
|
204
|
+
snowflake: 'snowflake',
|
|
205
|
+
}.freeze
|
|
206
|
+
|
|
207
|
+
class << self
|
|
208
|
+
|
|
209
|
+
# Find the config directory, searching gem paths
|
|
210
|
+
def config_dir
|
|
211
|
+
@config_dir ||= find_config_dir
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Allow overriding config dir for testing
|
|
215
|
+
attr_writer :config_dir
|
|
216
|
+
|
|
217
|
+
private
|
|
218
|
+
|
|
219
|
+
def find_config_dir
|
|
220
|
+
# Check relative to this file (gem's config)
|
|
221
|
+
gem_config = File.expand_path('../../../config/platforms', __dir__)
|
|
222
|
+
return gem_config if File.directory?(gem_config)
|
|
223
|
+
|
|
224
|
+
# Fallback to working directory
|
|
225
|
+
local_config = File.join(Dir.pwd, 'config/platforms')
|
|
226
|
+
return local_config if File.directory?(local_config)
|
|
227
|
+
|
|
228
|
+
nil
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Build config paths for the given adapter
|
|
234
|
+
#
|
|
235
|
+
# @param adapter_scheme [Symbol] Database adapter scheme
|
|
236
|
+
# @param extra_configs [Array<String>] Additional config paths to stack
|
|
237
|
+
# @return [Array<String>] Ordered config paths
|
|
238
|
+
def self.config_paths_for(adapter_scheme, extra_configs = [])
|
|
239
|
+
paths = []
|
|
240
|
+
|
|
241
|
+
if config_dir
|
|
242
|
+
base_config = File.join(config_dir, 'base.csv')
|
|
243
|
+
paths << base_config if File.exist?(base_config)
|
|
244
|
+
|
|
245
|
+
adapter_name = ADAPTER_CONFIG_NAMES[adapter_scheme]
|
|
246
|
+
if adapter_name
|
|
247
|
+
rdbms_config = File.join(config_dir, 'rdbms', "#{adapter_name}.csv")
|
|
248
|
+
paths << rdbms_config if File.exist?(rdbms_config)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
paths.concat(extra_configs.select { |p| File.exist?(p) })
|
|
253
|
+
paths
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Build platform instance for database
|
|
257
|
+
#
|
|
258
|
+
# @param db [Sequel::Database] Database connection
|
|
259
|
+
# @param extra_configs [Array<String>] Additional config paths
|
|
260
|
+
# @return [Platform::Base] Platform instance
|
|
261
|
+
def self.build_platform(db, extra_configs = [])
|
|
262
|
+
adapter = effective_adapter(db)
|
|
263
|
+
platform_class = PLATFORM_CLASSES.fetch(adapter, Base)
|
|
264
|
+
config_paths = config_paths_for(adapter, extra_configs)
|
|
265
|
+
platform_class.new(db, *config_paths)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Detect the effective adapter for platform selection.
|
|
269
|
+
# For mock databases, use database_type or host option; otherwise use adapter_scheme.
|
|
270
|
+
#
|
|
271
|
+
# @param db [Sequel::Database] Database connection
|
|
272
|
+
# @return [Symbol] Effective adapter type
|
|
273
|
+
def self.effective_adapter(db)
|
|
274
|
+
return db.adapter_scheme unless db.adapter_scheme == :mock
|
|
275
|
+
|
|
276
|
+
# Mock databases: try database_type first (set for known types like postgres, spark)
|
|
277
|
+
db_type = db.database_type
|
|
278
|
+
return db_type if db_type && db_type != :mock
|
|
279
|
+
|
|
280
|
+
# Fall back to host option (for unknown types like snowflake, athena)
|
|
281
|
+
db.opts[:host]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Extension hook - called when extension is loaded
|
|
285
|
+
def self.extended(db)
|
|
286
|
+
extra_configs = db.opts[:platform_configs] || []
|
|
287
|
+
db.instance_variable_set(:@platform, build_platform(db, extra_configs))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Access the platform instance
|
|
291
|
+
#
|
|
292
|
+
# @return [Platform::Base] Platform instance for this database
|
|
293
|
+
def platform
|
|
294
|
+
@platform
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
Database.register_extension(:platform, Platform)
|
|
300
|
+
|
|
301
|
+
end
|
data/lib/sequelizer/options.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'uri'
|
|
1
2
|
require_relative 'yaml_config'
|
|
2
3
|
require_relative 'env_config'
|
|
3
4
|
require_relative 'options_hash'
|
|
@@ -98,7 +99,10 @@ module Sequelizer
|
|
|
98
99
|
opts = OptionsHash.new(passed_options || {}).to_hash
|
|
99
100
|
sequelizer_options = db_config(opts).merge(opts)
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
adapter = sequelizer_options[:adapter]
|
|
103
|
+
adapter ||= URI.parse(sequelizer_options[:url].to_s).scheme if sequelizer_options[:url]
|
|
104
|
+
|
|
105
|
+
if adapter =~ /^postgres/
|
|
102
106
|
sequelizer_options[:adapter] = 'postgres'
|
|
103
107
|
paths = %w[search_path schema_search_path schema].map { |key| sequelizer_options.delete(key) }.compact
|
|
104
108
|
|
data/lib/sequelizer/version.rb
CHANGED
data/sequelizer.gemspec
CHANGED
|
@@ -17,18 +17,21 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.require_paths = ['lib']
|
|
18
18
|
spec.required_ruby_version = '>= 3.2.0'
|
|
19
19
|
|
|
20
|
-
spec.add_development_dependency 'bundler', '
|
|
20
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
21
21
|
spec.add_development_dependency 'guard', '~> 2.0'
|
|
22
|
-
spec.add_development_dependency 'guard-minitest', '~>
|
|
23
|
-
spec.add_development_dependency 'minitest', '~>
|
|
22
|
+
spec.add_development_dependency 'guard-minitest', '~> 3.0'
|
|
23
|
+
spec.add_development_dependency 'minitest', '~> 6.0'
|
|
24
|
+
spec.add_development_dependency 'minitest-mock', '~> 5.27'
|
|
24
25
|
spec.add_development_dependency 'pg', '~> 1.0'
|
|
25
|
-
spec.add_development_dependency 'rake', '~>
|
|
26
|
+
spec.add_development_dependency 'rake', '~> 13.3'
|
|
26
27
|
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
27
28
|
spec.add_development_dependency 'rubocop-minitest', '~> 0.25'
|
|
28
29
|
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
29
|
-
spec.add_dependency 'activesupport', '
|
|
30
|
-
spec.add_dependency 'dotenv', '
|
|
31
|
-
spec.add_dependency 'hashie', '
|
|
30
|
+
spec.add_dependency 'activesupport', '>= 7', '< 9'
|
|
31
|
+
spec.add_dependency 'dotenv', '>= 2.1', '< 4.0'
|
|
32
|
+
spec.add_dependency 'hashie', '>= 3.2', '< 6.0'
|
|
33
|
+
spec.add_dependency 'kvcsv', '~> 0.1'
|
|
34
|
+
spec.add_dependency 'pp'
|
|
32
35
|
spec.add_dependency 'sequel', '~> 5.93'
|
|
33
36
|
spec.add_dependency 'thor', '~> 1.0'
|
|
34
37
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
@@ -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
|
|
@@ -15,10 +15,10 @@ class TestConnectionMaker < Minitest::Test
|
|
|
15
15
|
|
|
16
16
|
def with_ignored_yaml_config(opts = {}); end
|
|
17
17
|
|
|
18
|
-
def with_yaml_config(options = {}, &
|
|
18
|
+
def with_yaml_config(options = {}, &)
|
|
19
19
|
yaml_config = Sequelizer::YamlConfig.new
|
|
20
20
|
yaml_config.stub(:options, options) do
|
|
21
|
-
Sequelizer::YamlConfig.stub
|
|
21
|
+
Sequelizer::YamlConfig.stub(:new, yaml_config, &)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -79,9 +79,9 @@ class TestGemfileModifier < Minitest::Test
|
|
|
79
79
|
pn_mock.expect(:exist?, true)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
def stub_modifying_methods(obj, &
|
|
82
|
+
def stub_modifying_methods(obj, &)
|
|
83
83
|
obj.stub(:system, nil) do
|
|
84
|
-
obj.stub(:puts, nil, &
|
|
84
|
+
obj.stub(:puts, nil, &)
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
@@ -72,6 +72,34 @@ class TestOptions < Minitest::Test
|
|
|
72
72
|
assert_equal(['SELECT * FROM "table"'], db.sqls)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
def test_url_based_connection_processes_search_path
|
|
76
|
+
options = Sequelizer::Options.new(
|
|
77
|
+
Sequelizer::OptionsHash.new(url: 'postgres://localhost/mydb', search_path: 'my_schema'),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert_equal('my_schema', options.search_path)
|
|
81
|
+
assert_instance_of(Proc, options.to_hash[:after_connect])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_url_based_connection_processes_schema_as_search_path
|
|
85
|
+
options = Sequelizer::Options.new(
|
|
86
|
+
Sequelizer::OptionsHash.new(url: 'postgresql://localhost/mydb', schema: 'my_schema'),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert_equal('my_schema', options.search_path)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_url_based_connection_skips_search_path_processing_for_non_postgres
|
|
93
|
+
options = Sequelizer::Options.new(
|
|
94
|
+
Sequelizer::OptionsHash.new(url: 'mysql2://localhost/mydb', search_path: 'my_schema'),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# search_path key remains but is NOT processed (no schema creation callback)
|
|
98
|
+
assert_equal('my_schema', options.search_path)
|
|
99
|
+
# adapter should not be normalized to 'postgres'
|
|
100
|
+
assert_nil(options.adapter)
|
|
101
|
+
end
|
|
102
|
+
|
|
75
103
|
def test_handles_extensions_passed_in
|
|
76
104
|
options = Sequelizer::Options.new(extension_example_one: 1, extension_example_two: 1, not_an_extension_example: 1)
|
|
77
105
|
|