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
|
@@ -0,0 +1,300 @@
|
|
|
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
|
+
# Returns the effective database type for platform selection.
|
|
269
|
+
#
|
|
270
|
+
# For real connections, database_type is authoritative. For mock
|
|
271
|
+
# connections whose shared adapter isn't installed (e.g. snowflake,
|
|
272
|
+
# athena), database_type returns :mock; fall back to opts[:host].
|
|
273
|
+
#
|
|
274
|
+
# @param db [Sequel::Database] Database connection
|
|
275
|
+
# @return [Symbol] Effective database type
|
|
276
|
+
def self.effective_adapter(db)
|
|
277
|
+
db_type = db.database_type
|
|
278
|
+
return db_type unless db_type == :mock
|
|
279
|
+
|
|
280
|
+
db.opts[:host]&.to_sym
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Extension hook - called when extension is loaded
|
|
284
|
+
def self.extended(db)
|
|
285
|
+
extra_configs = db.opts[:platform_configs] || []
|
|
286
|
+
db.instance_variable_set(:@platform, build_platform(db, extra_configs))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Access the platform instance
|
|
290
|
+
#
|
|
291
|
+
# @return [Platform::Base] Platform instance for this database
|
|
292
|
+
def platform
|
|
293
|
+
@platform
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
Database.register_extension(:platform, Platform)
|
|
299
|
+
|
|
300
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# The smart_select_remove extension overrides select_remove to resolve
|
|
5
|
+
# column information from the dataset's expression tree before falling
|
|
6
|
+
# back to the original select_remove behavior (which executes SQL via
|
|
7
|
+
# columns!).
|
|
8
|
+
#
|
|
9
|
+
# This is essential for lazy query building patterns where datasets
|
|
10
|
+
# reference tables that may not exist yet at query-build time. By
|
|
11
|
+
# walking through from_self chains and inspecting inner SELECT lists,
|
|
12
|
+
# this extension can determine which columns to keep without touching
|
|
13
|
+
# the database.
|
|
14
|
+
#
|
|
15
|
+
# You should load this extension into all of a database's datasets:
|
|
16
|
+
#
|
|
17
|
+
# DB.extension(:smart_select_remove)
|
|
18
|
+
#
|
|
19
|
+
# This automatically loads the select_remove extension as well.
|
|
20
|
+
#
|
|
21
|
+
# Related module: Sequel::SmartSelectRemove
|
|
22
|
+
|
|
23
|
+
module Sequel
|
|
24
|
+
|
|
25
|
+
module SmartSelectRemove
|
|
26
|
+
|
|
27
|
+
# Override select_remove to attempt static column resolution before
|
|
28
|
+
# falling back to the original implementation.
|
|
29
|
+
#
|
|
30
|
+
# When the dataset has no explicit SELECT clause, walks through
|
|
31
|
+
# from_self chains to find inner column expressions and resolves
|
|
32
|
+
# them to outer-scope symbol names. This avoids the columns! call
|
|
33
|
+
# that the original select_remove uses, which would execute SQL.
|
|
34
|
+
#
|
|
35
|
+
# @param cols [Array<Symbol>] columns to remove
|
|
36
|
+
# @return [Sequel::Dataset] dataset with columns removed
|
|
37
|
+
def select_remove(*cols)
|
|
38
|
+
# If there's an explicit select, original behavior handles it
|
|
39
|
+
return super if @opts[:select] && !@opts[:select].empty?
|
|
40
|
+
|
|
41
|
+
# Try to resolve columns by walking the from_self chain
|
|
42
|
+
if (inner_cols = resolve_selected_columns)
|
|
43
|
+
outer_cols = inner_cols.map { |c| resolve_outer_column_name(c) }
|
|
44
|
+
# All columns must be resolvable; bail to super if any are opaque
|
|
45
|
+
return super if outer_cols.any?(&:nil?)
|
|
46
|
+
|
|
47
|
+
return select(*(outer_cols - cols))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Walk through from_self chains to find the innermost explicit
|
|
56
|
+
# SELECT list. Returns nil if no explicit selection is found.
|
|
57
|
+
#
|
|
58
|
+
# @param ds [Sequel::Dataset] the dataset to inspect
|
|
59
|
+
# @return [Array, nil] the inner SELECT expressions, or nil
|
|
60
|
+
def resolve_selected_columns(ds = self)
|
|
61
|
+
opts = ds.opts
|
|
62
|
+
if (sel = opts[:select]) && !sel.empty?
|
|
63
|
+
sel
|
|
64
|
+
elsif (from = opts[:from]&.first)
|
|
65
|
+
case from
|
|
66
|
+
when Sequel::Dataset
|
|
67
|
+
resolve_selected_columns(from)
|
|
68
|
+
when Sequel::SQL::AliasedExpression
|
|
69
|
+
resolve_selected_columns(from.expression) if from.expression.is_a?(Sequel::Dataset)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Map an inner SELECT expression to its outer-scope column name.
|
|
75
|
+
# After from_self, inner expressions become subquery output columns
|
|
76
|
+
# referenced by their alias or base name in the outer scope.
|
|
77
|
+
#
|
|
78
|
+
# @param col [Object] a column expression from the inner SELECT
|
|
79
|
+
# @return [Symbol, nil] the outer column name, or nil if unresolvable
|
|
80
|
+
def resolve_outer_column_name(col)
|
|
81
|
+
case col
|
|
82
|
+
when Symbol
|
|
83
|
+
col
|
|
84
|
+
when Sequel::SQL::AliasedExpression
|
|
85
|
+
a = col.alias
|
|
86
|
+
a.is_a?(Symbol) ? a : a.to_sym
|
|
87
|
+
when Sequel::SQL::QualifiedIdentifier
|
|
88
|
+
resolve_outer_column_name(col.column)
|
|
89
|
+
when Sequel::SQL::Identifier
|
|
90
|
+
col.value.to_sym
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
Database.register_extension(:smart_select_remove) do |db|
|
|
97
|
+
db.extension(:select_remove)
|
|
98
|
+
db.extend_datasets(SmartSelectRemove)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
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
|
|
|
@@ -143,17 +147,22 @@ module Sequelizer
|
|
|
143
147
|
# 1. Create each schema in the search path if it doesn't exist
|
|
144
148
|
# 2. Set the search_path for the connection
|
|
145
149
|
#
|
|
146
|
-
# @param search_path [String]
|
|
150
|
+
# @param search_path [String, Array<String>] PostgreSQL schema names as a
|
|
151
|
+
# comma-separated string, an Array of schema names, or a mixed Array
|
|
152
|
+
# containing comma-separated strings
|
|
147
153
|
# @return [Proc] callback to execute after connection
|
|
148
|
-
# @example
|
|
154
|
+
# @example String input
|
|
149
155
|
# callback = after_connect('public,app_schema')
|
|
150
|
-
#
|
|
156
|
+
# @example Array input
|
|
157
|
+
# callback = after_connect(['public', 'app_schema'])
|
|
151
158
|
def after_connect(search_path)
|
|
159
|
+
schemas = Array(search_path).flat_map { |p| p.to_s.split(',') }.map(&:strip)
|
|
160
|
+
path_string = schemas.join(', ')
|
|
152
161
|
proc do |conn|
|
|
153
|
-
|
|
162
|
+
schemas.each do |schema|
|
|
154
163
|
conn.execute("CREATE SCHEMA IF NOT EXISTS #{schema}")
|
|
155
164
|
end
|
|
156
|
-
conn.execute("SET search_path TO #{
|
|
165
|
+
conn.execute("SET search_path TO #{path_string}")
|
|
157
166
|
end
|
|
158
167
|
end
|
|
159
168
|
|
data/lib/sequelizer/version.rb
CHANGED