Skip to content

Commit a4eeb2b

Browse files
authored
Merge pull request rails#46093 from gmcgibbon/dbconsole_to_adapter
Move dbconsole logic to Active Record connection adapter.
2 parents c267be4 + 4bcb8e4 commit a4eeb2b

File tree

12 files changed

+393
-259
lines changed

12 files changed

+393
-259
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,39 @@ def self.quoted_table_names # :nodoc:
8989
@quoted_table_names ||= {}
9090
end
9191

92+
def self.find_cmd_and_exec(commands, *args) # :doc:
93+
commands = Array(commands)
94+
95+
dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
96+
unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
97+
commands = commands.map { |cmd| "#{cmd}#{ext}" }
98+
end
99+
100+
full_path_command = nil
101+
found = commands.detect do |cmd|
102+
dirs_on_path.detect do |path|
103+
full_path_command = File.join(path, cmd)
104+
begin
105+
stat = File.stat(full_path_command)
106+
rescue SystemCallError
107+
else
108+
stat.file? && stat.executable?
109+
end
110+
end
111+
end
112+
113+
if found
114+
exec full_path_command, *args
115+
else
116+
abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
117+
end
118+
end
119+
120+
# Opens a database console session.
121+
def self.dbconsole(config, options = {})
122+
raise NotImplementedError
123+
end
124+
92125
def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
93126
super()
94127

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,36 @@ def dealloc(stmt)
5151
end
5252
end
5353

54+
class << self
55+
def dbconsole(config, options = {})
56+
mysql_config = config.configuration_hash
57+
58+
args = {
59+
host: "--host",
60+
port: "--port",
61+
socket: "--socket",
62+
username: "--user",
63+
encoding: "--default-character-set",
64+
sslca: "--ssl-ca",
65+
sslcert: "--ssl-cert",
66+
sslcapath: "--ssl-capath",
67+
sslcipher: "--ssl-cipher",
68+
sslkey: "--ssl-key",
69+
ssl_mode: "--ssl-mode"
70+
}.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
71+
72+
if mysql_config[:password] && options[:include_password]
73+
args << "--password=#{mysql_config[:password]}"
74+
elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
75+
args << "-p"
76+
end
77+
78+
args << config.database
79+
80+
find_cmd_and_exec(["mysql", "mysql5"], *args)
81+
end
82+
end
83+
5484
def get_database_version # :nodoc:
5585
full_version_string = get_full_version
5686
version_string = version_string(full_version_string)

activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99
module ActiveRecord
1010
module ConnectionHandling # :nodoc:
11+
def mysql2_connection_class
12+
ConnectionAdapters::Mysql2Adapter
13+
end
14+
1115
# Establishes a connection to the database that's used by all Active Record objects.
1216
def mysql2_connection(config)
13-
ConnectionAdapters::Mysql2Adapter.new(config)
17+
mysql2_connection_class.new(config)
1418
end
1519
end
1620

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@
2121

2222
module ActiveRecord
2323
module ConnectionHandling # :nodoc:
24+
def postgresql_connection_class
25+
ConnectionAdapters::PostgreSQLAdapter
26+
end
27+
2428
# Establishes a connection to the database that's used by all Active Record objects
2529
def postgresql_connection(config)
26-
ConnectionAdapters::PostgreSQLAdapter.new(config)
30+
postgresql_connection_class.new(config)
2731
end
2832
end
2933

@@ -72,6 +76,25 @@ def new_client(conn_params)
7276
raise ActiveRecord::ConnectionNotEstablished, error.message
7377
end
7478
end
79+
80+
def dbconsole(config, options = {})
81+
pg_config = config.configuration_hash
82+
83+
ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
84+
ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
85+
ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
86+
ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
87+
ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
88+
ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
89+
ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
90+
ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
91+
if pg_config[:variables]
92+
ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
93+
"-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
94+
end.join(" ")
95+
end
96+
find_cmd_and_exec("psql", config.database)
97+
end
7598
end
7699

77100
##

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515

1616
module ActiveRecord
1717
module ConnectionHandling # :nodoc:
18+
def sqlite3_connection_class
19+
ConnectionAdapters::SQLite3Adapter
20+
end
21+
1822
def sqlite3_connection(config)
19-
ConnectionAdapters::SQLite3Adapter.new(config)
23+
sqlite3_connection_class.new(config)
2024
end
2125
end
2226

@@ -40,6 +44,16 @@ def new_client(config)
4044
raise
4145
end
4246
end
47+
48+
def dbconsole(config, options = {})
49+
args = []
50+
51+
args << "-#{options[:mode]}" if options[:mode]
52+
args << "-header" if options[:header]
53+
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
54+
55+
find_cmd_and_exec("sqlite3", *args)
56+
end
4357
end
4458

4559
include SQLite3::Quoting

activerecord/lib/active_record/database_configurations/database_config.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ def adapter_method
1717
"#{adapter}_connection"
1818
end
1919

20+
def adapter_class_method
21+
"#{adapter}_connection_class"
22+
end
23+
2024
def host
2125
raise NotImplementedError
2226
end
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper"
4+
require "active_support/testing/method_call_assertions"
5+
6+
module ActiveRecord
7+
module ConnectionAdapters
8+
class Mysql2DbConsoleTest < ActiveRecord::Mysql2TestCase
9+
include ActiveSupport::Testing::MethodCallAssertions
10+
11+
def test_mysql
12+
config = make_db_config(adapter: "mysql2", database: "db")
13+
14+
assert_find_cmd_and_exec_called_with([%w[mysql mysql5], "db"]) do
15+
Mysql2Adapter.dbconsole(config)
16+
end
17+
end
18+
19+
def test_mysql_full
20+
config = make_db_config(
21+
adapter: "mysql2",
22+
database: "db",
23+
host: "localhost",
24+
port: 1234,
25+
socket: "socket",
26+
username: "user",
27+
password: "qwerty",
28+
encoding: "UTF-8",
29+
sslca: "/path/to/ca-cert.pem",
30+
sslcert: "/path/to/client-cert.pem",
31+
sslcapath: "/path/to/cacerts",
32+
sslcipher: "DHE-RSA-AES256-SHA",
33+
sslkey: "/path/to/client-key.pem",
34+
ssl_mode: "VERIFY_IDENTITY"
35+
)
36+
37+
args = [
38+
%w[mysql mysql5],
39+
"--host=localhost",
40+
"--port=1234",
41+
"--socket=socket",
42+
"--user=user",
43+
"--default-character-set=UTF-8",
44+
"--ssl-ca=/path/to/ca-cert.pem",
45+
"--ssl-cert=/path/to/client-cert.pem",
46+
"--ssl-capath=/path/to/cacerts",
47+
"--ssl-cipher=DHE-RSA-AES256-SHA",
48+
"--ssl-key=/path/to/client-key.pem",
49+
"--ssl-mode=VERIFY_IDENTITY",
50+
"-p", "db"
51+
]
52+
53+
assert_find_cmd_and_exec_called_with(args) do
54+
Mysql2Adapter.dbconsole(config)
55+
end
56+
end
57+
58+
def test_mysql_include_password
59+
config = make_db_config(adapter: "mysql2", database: "db", username: "user", password: "qwerty")
60+
61+
assert_find_cmd_and_exec_called_with([%w[mysql mysql5], "--user=user", "--password=qwerty", "db"]) do
62+
Mysql2Adapter.dbconsole(config, include_password: true)
63+
end
64+
end
65+
66+
private
67+
def make_db_config(config)
68+
ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
69+
end
70+
71+
def assert_find_cmd_and_exec_called_with(args, &block)
72+
assert_called_with(Mysql2Adapter, :find_cmd_and_exec, args, &block)
73+
end
74+
end
75+
end
76+
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper"
4+
require "active_support/testing/method_call_assertions"
5+
6+
module ActiveRecord
7+
module ConnectionAdapters
8+
class PostgresqlDbConsoleTest < ActiveRecord::PostgreSQLTestCase
9+
include ActiveSupport::Testing::MethodCallAssertions
10+
11+
ENV_VARS = %w(PGUSER PGHOST PGPORT PGPASSWORD PGSSLMODE PGSSLCERT PGSSLKEY PGSSLROOTCERT PGOPTIONS)
12+
13+
def run(*)
14+
preserve_pg_env do
15+
super
16+
end
17+
end
18+
19+
def test_postgresql
20+
config = make_db_config(adapter: "postgresql", database: "db")
21+
22+
assert_find_cmd_and_exec_called_with(["psql", "db"]) do
23+
PostgreSQLAdapter.dbconsole(config)
24+
end
25+
end
26+
27+
def test_postgresql_full
28+
config = make_db_config(
29+
adapter: "postgresql",
30+
database: "db",
31+
username: "user",
32+
password: "q1w2e3",
33+
host: "host",
34+
port: 5432,
35+
)
36+
37+
assert_find_cmd_and_exec_called_with(["psql", "db"]) do
38+
PostgreSQLAdapter.dbconsole(config)
39+
end
40+
41+
assert_equal "user", ENV["PGUSER"]
42+
assert_equal "host", ENV["PGHOST"]
43+
assert_equal "5432", ENV["PGPORT"]
44+
assert_not_equal "q1w2e3", ENV["PGPASSWORD"]
45+
end
46+
47+
def test_postgresql_with_ssl
48+
config = make_db_config(adapter: "postgresql", database: "db", sslmode: "verify-full", sslcert: "client.crt", sslkey: "client.key", sslrootcert: "root.crt")
49+
50+
assert_find_cmd_and_exec_called_with(["psql", "db"]) do
51+
PostgreSQLAdapter.dbconsole(config)
52+
end
53+
54+
assert_equal "verify-full", ENV["PGSSLMODE"]
55+
assert_equal "client.crt", ENV["PGSSLCERT"]
56+
assert_equal "client.key", ENV["PGSSLKEY"]
57+
assert_equal "root.crt", ENV["PGSSLROOTCERT"]
58+
end
59+
60+
def test_postgresql_include_password
61+
config = make_db_config(adapter: "postgresql", database: "db", username: "user", password: "q1w2e3")
62+
63+
assert_find_cmd_and_exec_called_with(["psql", "db"]) do
64+
PostgreSQLAdapter.dbconsole(config, include_password: true)
65+
end
66+
67+
assert_equal "user", ENV["PGUSER"]
68+
assert_equal "q1w2e3", ENV["PGPASSWORD"]
69+
end
70+
71+
def test_postgresql_include_variables
72+
config = make_db_config(adapter: "postgresql", database: "db", variables: { search_path: "my_schema, default, \\my_schema", statement_timeout: 5000, lock_timeout: ":default" })
73+
74+
assert_find_cmd_and_exec_called_with(["psql", "db"]) do
75+
PostgreSQLAdapter.dbconsole(config)
76+
end
77+
78+
assert_equal "-c search_path=my_schema,\\ default,\\ \\\\my_schema -c statement_timeout=5000", ENV["PGOPTIONS"]
79+
end
80+
81+
private
82+
def preserve_pg_env
83+
old_values = ENV_VARS.map { |var| ENV[var] }
84+
yield
85+
ensure
86+
ENV_VARS.zip(old_values).each { |var, value| ENV[var] = value }
87+
end
88+
89+
def make_db_config(config)
90+
ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
91+
end
92+
93+
def assert_find_cmd_and_exec_called_with(args, &block)
94+
assert_called_with(PostgreSQLAdapter, :find_cmd_and_exec, args, &block)
95+
end
96+
end
97+
end
98+
end

0 commit comments

Comments
 (0)