sequelizer 0.1.4 → 0.1.6
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/.claude/settings.local.json +64 -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/workflows/test.yml +1 -1
- data/.gitignore +6 -1
- data/.overcommit.yml +73 -0
- data/.rubocop.yml +167 -0
- data/CHANGELOG.md +24 -0
- data/CLAUDE.md +219 -0
- data/Gemfile +6 -2
- data/Gemfile.lock +158 -0
- data/Guardfile +1 -1
- data/Rakefile +28 -3
- data/lib/sequel/extensions/cold_col.rb +436 -0
- data/lib/sequel/extensions/db_opts.rb +65 -4
- data/lib/sequel/extensions/make_readyable.rb +148 -30
- data/lib/sequel/extensions/more_sql.rb +76 -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 +97 -18
- data/lib/sequelizer/options_hash.rb +2 -0
- data/lib/sequelizer/version.rb +3 -1
- data/lib/sequelizer/yaml_config.rb +9 -3
- data/lib/sequelizer.rb +65 -9
- data/sequelizer.gemspec +12 -7
- 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 +199 -28
- data/test/lib/sequel/extensions/test_more_sql.rb +132 -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 +14 -9
- data/test/lib/sequelizer/test_yaml_config.rb +13 -12
- data/test/test_helper.rb +36 -8
- metadata +107 -28
- data/lib/sequel/extensions/sqls.rb +0 -31
@@ -1,15 +1,44 @@
|
|
1
1
|
module Sequel
|
2
|
+
|
3
|
+
# = Usable
|
4
|
+
#
|
5
|
+
# Sequel extension that provides a convenient +use+ method for switching
|
6
|
+
# the current database/schema context. This is particularly useful for
|
7
|
+
# databases that support the USE statement like MySQL, SQL Server, and
|
8
|
+
# some big data engines.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# db.extension :usable
|
12
|
+
# db.use(:my_schema)
|
13
|
+
# # Executes: USE `my_schema`
|
2
14
|
module Usable
|
15
|
+
|
16
|
+
# Switches to the specified database or schema.
|
17
|
+
#
|
18
|
+
# Executes a USE statement to change the current database context.
|
19
|
+
# The schema name is properly quoted using the database's identifier
|
20
|
+
# quoting rules.
|
21
|
+
#
|
22
|
+
# @param schema_name [Symbol, String] the name of the schema/database to use
|
23
|
+
# @example
|
24
|
+
# db.use(:production_db)
|
25
|
+
# db.use('test_schema')
|
3
26
|
def use(schema_name)
|
4
27
|
run(use_sql(schema_name))
|
5
28
|
end
|
6
29
|
|
7
30
|
private
|
8
31
|
|
32
|
+
# Generates the USE SQL statement for the given schema name.
|
33
|
+
#
|
34
|
+
# @param schema_name [Symbol, String] the schema name to use
|
35
|
+
# @return [String] the USE SQL statement
|
9
36
|
def use_sql(schema_name)
|
10
|
-
"USE #{
|
37
|
+
"USE #{literal(schema_name)}"
|
11
38
|
end
|
39
|
+
|
12
40
|
end
|
13
41
|
|
14
42
|
Database.register_extension(:usable, Usable)
|
43
|
+
|
15
44
|
end
|
data/lib/sequelizer/cli.rb
CHANGED
@@ -3,39 +3,75 @@ require 'pp'
|
|
3
3
|
require_relative 'gemfile_modifier'
|
4
4
|
|
5
5
|
module Sequelizer
|
6
|
+
# = CLI
|
7
|
+
#
|
8
|
+
# Command line interface for Sequelizer gem using Thor.
|
9
|
+
# Provides commands for:
|
10
|
+
#
|
11
|
+
# * Updating Gemfile with database adapters
|
12
|
+
# * Initializing .env files with database configuration
|
13
|
+
# * Displaying current configuration
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# sequelizer update_gemfile
|
17
|
+
# sequelizer init_env --adapter postgres --host localhost
|
18
|
+
# sequelizer config
|
6
19
|
class CLI < Thor
|
7
|
-
|
20
|
+
|
21
|
+
desc 'update_gemfile',
|
22
|
+
'adds or replaces a line in your Gemfile to include the correct database adapter to work with Sequel'
|
8
23
|
option 'dry-run', type: :boolean, desc: 'Only prints out what it would do, but makes no changes'
|
9
24
|
option 'skip-bundle', type: :boolean, desc: "Don't run `bundle install` after modifying Gemfile"
|
25
|
+
# Updates the Gemfile to include the appropriate database adapter gem.
|
26
|
+
#
|
27
|
+
# This command analyzes your current database configuration and adds or updates
|
28
|
+
# the corresponding database adapter gem in your Gemfile. It supports various
|
29
|
+
# adapters including PostgreSQL, MySQL, SQLite, and JDBC-based adapters.
|
30
|
+
#
|
31
|
+
# @option options [Boolean] dry-run Only prints what would be done without making changes
|
32
|
+
# @option options [Boolean] skip-bundle Skip running `bundle install` after modification
|
10
33
|
def update_gemfile
|
11
34
|
GemfileModifier.new(options).modify
|
12
35
|
end
|
13
36
|
|
14
37
|
desc 'init_env', 'creates a .env file with the parameters listed'
|
15
38
|
option :adapter,
|
16
|
-
|
17
|
-
|
39
|
+
aliases: :a,
|
40
|
+
desc: 'adapter for database'
|
18
41
|
option :host,
|
19
|
-
|
20
|
-
|
21
|
-
|
42
|
+
aliases: :h,
|
43
|
+
banner: 'localhost',
|
44
|
+
desc: 'host for database'
|
22
45
|
option :username,
|
23
|
-
|
24
|
-
|
46
|
+
aliases: :u,
|
47
|
+
desc: 'username for database'
|
25
48
|
option :password,
|
26
|
-
|
27
|
-
|
49
|
+
aliases: :P,
|
50
|
+
desc: 'password for database'
|
28
51
|
option :port,
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
52
|
+
aliases: :p,
|
53
|
+
type: :numeric,
|
54
|
+
banner: '5432',
|
55
|
+
desc: 'port for database'
|
33
56
|
option :database,
|
34
|
-
|
35
|
-
|
57
|
+
aliases: :d,
|
58
|
+
desc: 'database for database'
|
36
59
|
option :search_path,
|
37
|
-
|
38
|
-
|
60
|
+
aliases: :s,
|
61
|
+
desc: 'schema for database (PostgreSQL only)'
|
62
|
+
# Creates a .env file with database configuration parameters.
|
63
|
+
#
|
64
|
+
# This command generates a .env file with SEQUELIZER_* environment variables
|
65
|
+
# based on the provided options. It will not overwrite an existing .env file.
|
66
|
+
#
|
67
|
+
# @option options [String] :adapter Database adapter (e.g., 'postgres', 'mysql2')
|
68
|
+
# @option options [String] :host Database host (default: 'localhost')
|
69
|
+
# @option options [String] :username Database username
|
70
|
+
# @option options [String] :password Database password
|
71
|
+
# @option options [Integer] :port Database port (default: 5432 for PostgreSQL)
|
72
|
+
# @option options [String] :database Database name
|
73
|
+
# @option options [String] :search_path PostgreSQL schema search path
|
74
|
+
# @raise [SystemExit] if .env file already exists
|
39
75
|
def init_env
|
40
76
|
if File.exist?('.env')
|
41
77
|
puts ".env already exists! I'm too cowardly to overwrite it!"
|
@@ -49,6 +85,11 @@ module Sequelizer
|
|
49
85
|
end
|
50
86
|
|
51
87
|
desc 'config', 'prints out the connection parameters'
|
88
|
+
# Displays the current database configuration and extensions.
|
89
|
+
#
|
90
|
+
# This command shows the resolved configuration options that would be used
|
91
|
+
# for database connections, including all merged sources and any Sequel
|
92
|
+
# extensions that would be loaded.
|
52
93
|
def config
|
53
94
|
opts = Options.new
|
54
95
|
pp opts.to_hash
|
@@ -56,10 +97,12 @@ module Sequelizer
|
|
56
97
|
end
|
57
98
|
|
58
99
|
private
|
100
|
+
|
59
101
|
def make_env(options)
|
60
102
|
options.map do |key, value|
|
61
103
|
"SEQUELIZER_#{key.upcase}=#{value}"
|
62
104
|
end.join("\n")
|
63
105
|
end
|
106
|
+
|
64
107
|
end
|
65
108
|
end
|
@@ -1,97 +1,79 @@
|
|
1
1
|
require 'sequel'
|
2
|
-
require 'cgi'
|
3
2
|
require_relative 'options'
|
4
3
|
|
5
4
|
module Sequelizer
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# = ConnectionMaker
|
6
|
+
#
|
7
|
+
# Class that handles loading/interpreting the database options and
|
8
|
+
# creates the Sequel connection. This class is responsible for:
|
9
|
+
#
|
10
|
+
# * Loading configuration from multiple sources
|
11
|
+
# * Creating standard Sequel database connections
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# maker = ConnectionMaker.new(adapter: 'postgres', host: 'localhost')
|
15
|
+
# db = maker.connection
|
8
16
|
class ConnectionMaker
|
9
|
-
# The options for Sequel.connect
|
10
|
-
attr :options
|
11
17
|
|
12
|
-
#
|
18
|
+
# @!attribute [r] options
|
19
|
+
# @return [Options] the database connection options
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
# Creates a new ConnectionMaker instance.
|
13
23
|
#
|
14
|
-
# If no options are provided, attempts to read options from
|
15
|
-
#
|
24
|
+
# If no options are provided, attempts to read options from multiple sources
|
25
|
+
# in order of precedence:
|
26
|
+
# 1. .env file
|
27
|
+
# 2. Environment variables
|
28
|
+
# 3. config/database.yml
|
29
|
+
# 4. ~/.config/sequelizer/database.yml
|
16
30
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# database
|
31
|
+
# @param options [Hash, nil] database connection options
|
32
|
+
# @option options [String] :adapter database adapter (e.g., 'postgres', 'mysql2')
|
33
|
+
# @option options [String] :host database host
|
34
|
+
# @option options [Integer] :port database port
|
35
|
+
# @option options [String] :database database name
|
36
|
+
# @option options [String] :username database username
|
37
|
+
# @option options [String] :password database password
|
38
|
+
# @option options [String] :search_path PostgreSQL schema search path
|
20
39
|
def initialize(options = nil)
|
21
40
|
@options = Options.new(options)
|
22
41
|
end
|
23
42
|
|
24
|
-
# Returns a Sequel connection to the database
|
43
|
+
# Returns a Sequel connection to the database.
|
44
|
+
#
|
45
|
+
# This method creates a standard Sequel database connection
|
46
|
+
# using the configured options.
|
47
|
+
#
|
48
|
+
# @return [Sequel::Database] configured database connection
|
49
|
+
# @raise [Sequel::Error] if connection fails
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# connection = maker.connection
|
53
|
+
# users = connection[:users].all
|
25
54
|
def connection
|
26
55
|
opts = options.to_hash
|
27
56
|
extensions = options.extensions
|
28
57
|
|
29
|
-
conn =
|
30
|
-
Sequel.connect(url, opts)
|
31
|
-
else
|
32
|
-
# Kerberos related options
|
33
|
-
realm = opts[:realm]
|
34
|
-
host_fqdn = opts[:host_fqdn] || opts[:host]
|
35
|
-
principal = opts[:principal]
|
36
|
-
|
37
|
-
adapter = opts[:adapter]
|
38
|
-
if adapter =~ /\Ajdbc_/
|
39
|
-
user = opts[:user]
|
40
|
-
password = opts[:password]
|
41
|
-
end
|
42
|
-
|
43
|
-
case opts[:adapter] && opts[:adapter].to_sym
|
44
|
-
when :jdbc_hive2
|
45
|
-
opts[:adapter] = :jdbc
|
46
|
-
auth = if realm
|
47
|
-
";principal=#{e principal}/#{e host_fqdn}@#{e realm}"
|
48
|
-
elsif user
|
49
|
-
";user=#{e user};password=#{e password}"
|
50
|
-
else
|
51
|
-
';auth=noSasl'
|
52
|
-
end
|
53
|
-
opts[:uri] = "jdbc:hive2://#{e opts[:host]}:#{opts.fetch(:port, 21050).to_i}/#{e(opts[:database] || 'default')}#{auth}"
|
54
|
-
when :jdbc_impala
|
55
|
-
opts[:adapter] = :jdbc
|
56
|
-
auth = if realm
|
57
|
-
";AuthMech=1;KrbServiceName=#{e principal};KrbAuthType=2;KrbHostFQDN=#{e host_fqdn};KrbRealm=#{e realm}"
|
58
|
-
elsif user
|
59
|
-
if password
|
60
|
-
";AuthMech=3;UID=#{e user};PWD=#{e password}"
|
61
|
-
else
|
62
|
-
";AuthMech=2;UID=#{e user}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
opts[:uri] = "jdbc:impala://#{e opts[:host]}:#{opts.fetch(:port, 21050).to_i}/#{e(opts[:database] || 'default')}#{auth}"
|
66
|
-
when :jdbc_postgres
|
67
|
-
opts[:adapter] = :jdbc
|
68
|
-
auth = "?user=#{user}#{"&password=#{password}" if password}" if user
|
69
|
-
opts[:uri] = "jdbc:postgresql://#{e opts[:host]}:#{opts.fetch(:port, 5432).to_i}/#{e(opts[:database])}#{auth}"
|
70
|
-
when :impala
|
71
|
-
opts[:database] ||= 'default'
|
72
|
-
opts[:port] ||= 21000
|
73
|
-
if principal
|
74
|
-
# realm doesn't seem to be used?
|
75
|
-
opts[:transport] = :sasl
|
76
|
-
opts[:sasl_params] = {
|
77
|
-
mechanism: "GSSAPI",
|
78
|
-
remote_host: host_fqdn,
|
79
|
-
remote_principal: principal
|
80
|
-
}
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
Sequel.connect(opts)
|
85
|
-
end
|
58
|
+
conn = create_sequel_connection(opts)
|
86
59
|
conn.extension(*extensions)
|
87
60
|
conn
|
88
61
|
end
|
89
62
|
|
90
63
|
private
|
91
64
|
|
92
|
-
def
|
93
|
-
|
65
|
+
def create_sequel_connection(opts)
|
66
|
+
if (url = opts.delete(:uri) || opts.delete(:url))
|
67
|
+
Sequel.connect(url, opts)
|
68
|
+
else
|
69
|
+
configure_adapter_specific_options(opts)
|
70
|
+
Sequel.connect(opts)
|
71
|
+
end
|
94
72
|
end
|
73
|
+
|
74
|
+
def configure_adapter_specific_options(opts)
|
75
|
+
# No adapter-specific configuration needed currently
|
76
|
+
end
|
77
|
+
|
95
78
|
end
|
96
79
|
end
|
97
|
-
|
@@ -4,25 +4,25 @@ module Sequelizer
|
|
4
4
|
# Creates a set of database configuration options from environment
|
5
5
|
# variables
|
6
6
|
class EnvConfig
|
7
|
+
|
7
8
|
# Any environment variables in the .env file are loaded and then
|
8
9
|
# any environment variable starting with SEQUELIZER_ will be used
|
9
10
|
# as an option for the database
|
10
11
|
def options
|
11
12
|
Dotenv.load
|
12
13
|
|
13
|
-
seq_config = ENV.keys.
|
14
|
+
seq_config = ENV.keys.grep(/^SEQUELIZER_/).each_with_object({}) do |key, config|
|
14
15
|
new_key = key.gsub(/^SEQUELIZER_/, '').downcase
|
15
|
-
config[new_key] = ENV
|
16
|
-
config
|
16
|
+
config[new_key] = ENV.fetch(key, nil)
|
17
17
|
end
|
18
18
|
|
19
|
-
db_config = ENV.keys.
|
19
|
+
db_config = ENV.keys.grep(/_DB_OPT_/).each_with_object({}) do |key, config|
|
20
20
|
new_key = key.downcase
|
21
|
-
config[new_key] = ENV
|
22
|
-
config
|
21
|
+
config[new_key] = ENV.fetch(key, nil)
|
23
22
|
end
|
24
23
|
|
25
24
|
db_config.merge(seq_config)
|
26
25
|
end
|
26
|
+
|
27
27
|
end
|
28
28
|
end
|
@@ -2,7 +2,8 @@ require_relative 'options'
|
|
2
2
|
|
3
3
|
module Sequelizer
|
4
4
|
class GemfileModifier
|
5
|
-
|
5
|
+
|
6
|
+
attr_reader :options
|
6
7
|
|
7
8
|
def initialize(options = {})
|
8
9
|
@options = options
|
@@ -14,14 +15,14 @@ module Sequelizer
|
|
14
15
|
modify_gemfile
|
15
16
|
run_bundle unless options['skip-bundle']
|
16
17
|
else
|
17
|
-
puts
|
18
|
+
puts 'Gemfile needs no modification'
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
22
|
private
|
22
23
|
|
23
24
|
def modify_gemfile
|
24
|
-
puts %
|
25
|
+
puts %(Adding "#{gem_line}" to Gemfile)
|
25
26
|
return if options['dry-run']
|
26
27
|
|
27
28
|
File.write(gemfile, modified_lines.join("\n"))
|
@@ -30,21 +31,21 @@ module Sequelizer
|
|
30
31
|
def proper_gem
|
31
32
|
opts = Options.new
|
32
33
|
@proper_gem ||= case opts.adapter
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
when 'postgres'
|
35
|
+
'pg'
|
36
|
+
when 'sqlite'
|
37
|
+
'sqlite3'
|
38
|
+
when 'mysql'
|
39
|
+
'mysql2'
|
40
|
+
when 'tinytds'
|
41
|
+
'tiny_tds'
|
42
|
+
when 'oracle'
|
43
|
+
'ruby-oci8'
|
44
|
+
when nil
|
45
|
+
raise 'No database adapter defined in your Sequelizer configuration'
|
46
|
+
else
|
47
|
+
raise "Don't know which database gem to use with adapter: #{opts.adapter}"
|
48
|
+
end
|
48
49
|
end
|
49
50
|
|
50
51
|
def gem_line
|
@@ -68,22 +69,23 @@ module Sequelizer
|
|
68
69
|
end
|
69
70
|
|
70
71
|
def modified_lines
|
71
|
-
gemfile_lines.
|
72
|
+
gemfile_lines.grep_v(Regexp.new(gem_line_comment)) + [full_gem_line]
|
72
73
|
end
|
73
74
|
|
74
75
|
def check_for_gemfile
|
75
76
|
return if gemfile.exist?
|
77
|
+
|
76
78
|
raise "Could not find Gemfile in current directory: #{Pathname.pwd}"
|
77
79
|
end
|
78
80
|
|
79
81
|
def run_bundle
|
80
|
-
puts
|
82
|
+
puts 'Running `bundle install` to update dependencies'
|
81
83
|
system('bundle install')
|
82
84
|
end
|
83
85
|
|
84
86
|
def gemfile
|
85
87
|
@gemfile ||= Pathname.new('Gemfile')
|
86
88
|
end
|
89
|
+
|
87
90
|
end
|
88
91
|
end
|
89
|
-
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Sequel
|
2
2
|
class ConnectionPool
|
3
|
+
|
3
4
|
# Return a new connection by calling the connection proc with the given server name,
|
4
5
|
# and checking for connection errors.
|
5
6
|
def make_new(server)
|
6
7
|
begin
|
7
8
|
conn = @db.connect(server)
|
8
|
-
if ac = @after_connect
|
9
|
+
if (ac = @after_connect)
|
9
10
|
case ac.arity
|
10
11
|
when 3
|
11
12
|
ac.call(conn, server, @db)
|
@@ -15,12 +16,13 @@ module Sequel
|
|
15
16
|
ac.call(conn)
|
16
17
|
end
|
17
18
|
end
|
18
|
-
rescue
|
19
|
-
raise Sequel.convert_exception_class(
|
19
|
+
rescue StandardError => e
|
20
|
+
raise Sequel.convert_exception_class(e, Sequel::DatabaseConnectionError)
|
20
21
|
end
|
21
|
-
raise(Sequel::DatabaseConnectionError,
|
22
|
+
raise(Sequel::DatabaseConnectionError, 'Connection parameters not valid') unless conn
|
23
|
+
|
22
24
|
conn
|
23
25
|
end
|
26
|
+
|
24
27
|
end
|
25
28
|
end
|
26
|
-
|
data/lib/sequelizer/options.rb
CHANGED
@@ -3,18 +3,61 @@ require_relative 'env_config'
|
|
3
3
|
require_relative 'options_hash'
|
4
4
|
|
5
5
|
module Sequelizer
|
6
|
+
# = Options
|
7
|
+
#
|
8
|
+
# Manages database connection options from multiple configuration sources.
|
9
|
+
# This class is responsible for:
|
10
|
+
#
|
11
|
+
# * Loading configuration from various sources (YAML files, environment variables, .env files)
|
12
|
+
# * Applying precedence rules for configuration sources
|
13
|
+
# * Processing adapter-specific options (especially PostgreSQL schema handling)
|
14
|
+
# * Managing Sequel extensions
|
15
|
+
# * Setting up after_connect callbacks
|
16
|
+
#
|
17
|
+
# == Configuration Sources (in order of precedence)
|
18
|
+
#
|
19
|
+
# 1. Passed options (highest priority)
|
20
|
+
# 2. .env file
|
21
|
+
# 3. Environment variables
|
22
|
+
# 4. config/database.yml
|
23
|
+
# 5. ~/.config/sequelizer/database.yml (lowest priority)
|
24
|
+
#
|
25
|
+
# @example Basic usage
|
26
|
+
# options = Options.new(adapter: 'postgres', host: 'localhost')
|
27
|
+
# hash = options.to_hash
|
28
|
+
#
|
29
|
+
# @example Loading from environment
|
30
|
+
# ENV['SEQUELIZER_ADAPTER'] = 'postgres'
|
31
|
+
# options = Options.new
|
32
|
+
# puts options.adapter # => 'postgres'
|
6
33
|
class Options
|
7
|
-
|
34
|
+
|
35
|
+
# @!attribute [r] extensions
|
36
|
+
# @return [Array<Symbol>] list of Sequel extensions to load
|
37
|
+
attr_reader :extensions
|
38
|
+
|
39
|
+
# Creates a new Options instance, processing configuration from multiple sources.
|
40
|
+
#
|
41
|
+
# @param options [Hash, String, nil] database connection options or connection URL
|
42
|
+
# If a Hash is provided, it will be merged with configuration from other sources.
|
43
|
+
# If a String is provided, it's treated as a database URL and returned as-is.
|
44
|
+
# If nil, configuration is loaded entirely from external sources.
|
8
45
|
def initialize(options = nil)
|
9
46
|
opts = fix_options(options)
|
10
47
|
@options, @extensions = filter_extensions(opts)
|
11
48
|
end
|
12
49
|
|
50
|
+
# Returns the processed options as a hash suitable for Sequel.connect.
|
51
|
+
#
|
52
|
+
# @return [Hash] the database connection options
|
13
53
|
def to_hash
|
14
54
|
@options
|
15
55
|
end
|
16
56
|
|
17
|
-
|
57
|
+
# Define accessor methods for common database options
|
58
|
+
%w[adapter database username password search_path].each do |name|
|
59
|
+
# @!method #{name}
|
60
|
+
# @return [String, nil] the #{name} option value
|
18
61
|
define_method(name) do
|
19
62
|
@options[name]
|
20
63
|
end
|
@@ -22,9 +65,14 @@ module Sequelizer
|
|
22
65
|
|
23
66
|
private
|
24
67
|
|
68
|
+
# Creates an after_connect callback proc that handles custom callbacks
|
69
|
+
# and applies database-specific options.
|
70
|
+
#
|
71
|
+
# @param opts [Hash] options hash that may contain an :after_connect callback
|
72
|
+
# @return [Proc] callback to be executed after database connection
|
25
73
|
def make_ac(opts)
|
26
|
-
|
27
|
-
if ac = opts[:after_connect]
|
74
|
+
proc do |conn, server, db|
|
75
|
+
if (ac = opts[:after_connect])
|
28
76
|
ac.arity == 2 ? ac.call(conn, server) : ac.call(conn)
|
29
77
|
end
|
30
78
|
db.extension :db_opts
|
@@ -32,19 +80,27 @@ module Sequelizer
|
|
32
80
|
end
|
33
81
|
end
|
34
82
|
|
83
|
+
# Processes and fixes the passed options, handling various input types
|
84
|
+
# and applying adapter-specific transformations.
|
85
|
+
#
|
35
86
|
# If passed a hash, scans hash for certain options and sets up hash
|
36
|
-
# to be fed to Sequel.connect
|
87
|
+
# to be fed to Sequel.connect. Handles PostgreSQL schema setup and
|
88
|
+
# timeout conversion.
|
89
|
+
#
|
90
|
+
# If passed anything else (like a string that represents a database URL),
|
91
|
+
# the value is returned without modification.
|
37
92
|
#
|
38
|
-
#
|
39
|
-
#
|
93
|
+
# @param passed_options [Hash, String, nil] the options to process
|
94
|
+
# @return [Hash, String] processed options or original string
|
40
95
|
def fix_options(passed_options)
|
41
96
|
return passed_options unless passed_options.nil? || passed_options.is_a?(Hash)
|
97
|
+
|
42
98
|
opts = OptionsHash.new(passed_options || {}).to_hash
|
43
99
|
sequelizer_options = db_config(opts).merge(opts)
|
44
100
|
|
45
101
|
if sequelizer_options[:adapter] =~ /^postgres/
|
46
102
|
sequelizer_options[:adapter] = 'postgres'
|
47
|
-
paths = %w
|
103
|
+
paths = %w[search_path schema_search_path schema].map { |key| sequelizer_options.delete(key) }.compact
|
48
104
|
|
49
105
|
unless paths.empty?
|
50
106
|
sequelizer_options[:search_path] = paths.first
|
@@ -62,10 +118,14 @@ module Sequelizer
|
|
62
118
|
sequelizer_options.merge(after_connect: make_ac(sequelizer_options))
|
63
119
|
end
|
64
120
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
121
|
+
# Loads database configuration from external sources in order of precedence.
|
122
|
+
# Sources checked (in order):
|
123
|
+
# - ~/.config/sequelizer.yml (if it exists and not ignored)
|
124
|
+
# - config/database.yml (if it exists and not ignored)
|
125
|
+
# - environment variables (including .env file if not ignored)
|
126
|
+
#
|
127
|
+
# @param opts [Hash] base options that may contain ignore flags
|
128
|
+
# @return [OptionsHash] merged configuration from all sources
|
69
129
|
def db_config(opts)
|
70
130
|
@db_config ||= begin
|
71
131
|
opts = OptionsHash.new(opts)
|
@@ -77,14 +137,19 @@ module Sequelizer
|
|
77
137
|
end
|
78
138
|
|
79
139
|
# Returns a proc that should be executed after Sequel connects to the
|
80
|
-
#
|
140
|
+
# database.
|
141
|
+
#
|
142
|
+
# For PostgreSQL connections with a search_path defined, this proc will:
|
143
|
+
# 1. Create each schema in the search path if it doesn't exist
|
144
|
+
# 2. Set the search_path for the connection
|
81
145
|
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
146
|
+
# @param search_path [String] comma-separated list of PostgreSQL schemas
|
147
|
+
# @return [Proc] callback to execute after connection
|
148
|
+
# @example
|
149
|
+
# callback = after_connect('public,app_schema')
|
150
|
+
# # When called, creates schemas and sets search_path
|
86
151
|
def after_connect(search_path)
|
87
|
-
|
152
|
+
proc do |conn|
|
88
153
|
search_path.split(',').map(&:strip).each do |schema|
|
89
154
|
conn.execute("CREATE SCHEMA IF NOT EXISTS #{schema}")
|
90
155
|
end
|
@@ -92,6 +157,19 @@ module Sequelizer
|
|
92
157
|
end
|
93
158
|
end
|
94
159
|
|
160
|
+
# Extracts Sequel extension configuration from options.
|
161
|
+
#
|
162
|
+
# Looks for keys starting with 'extension_' and converts them to
|
163
|
+
# extension names. The extension keys are removed from the options
|
164
|
+
# hash and returned separately.
|
165
|
+
#
|
166
|
+
# @param options [Hash] options hash that may contain extension keys
|
167
|
+
# @return [Array<Hash, Array>] tuple of [filtered_options, extensions]
|
168
|
+
# @example
|
169
|
+
# opts = { adapter: 'postgres', extension_pg_json: true, extension_pg_array: true }
|
170
|
+
# filtered_opts, exts = filter_extensions(opts)
|
171
|
+
# # filtered_opts => { adapter: 'postgres' }
|
172
|
+
# # exts => [:pg_json, :pg_array]
|
95
173
|
def filter_extensions(options)
|
96
174
|
extension_regexp = /^extension_/
|
97
175
|
extension_keys = options.keys.select { |k| k.to_s =~ extension_regexp }
|
@@ -101,5 +179,6 @@ module Sequelizer
|
|
101
179
|
end
|
102
180
|
[options, extensions]
|
103
181
|
end
|
182
|
+
|
104
183
|
end
|
105
184
|
end
|