exwiw 0.3.2 → 0.3.3
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 +2 -0
- data/README.md +8 -12
- data/lib/exwiw/adapter/mysql_adapter.rb +19 -4
- data/lib/exwiw/adapter/mysql_client.rb +18 -0
- data/lib/exwiw/determine_table_processing_order.rb +24 -0
- data/lib/exwiw/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b10da211ee91b4daa986b71b18fce7ac9a782da81ef92a1146f552368b84d66
|
|
4
|
+
data.tar.gz: 3893a581e2dac519021d6c86756333b980b3c587efcc5d13337e0fe98d9aaa42
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 395b24342f9a752d5cda207a959ba905a9fc57c8b693faa838bf2978b7b1b08654e0c5a9901b8359d9af96afc70b36f5b199cfec04165ef9e6ad9b25df0f3ea6
|
|
7
|
+
data.tar.gz: fb5514dd6d9c724e1d136cf54abe31616f8cec9944522dd7385fbbb04ddf82f9c290c2935d9c7a55949c86b9c433df02fc43f63aa3cff6d93e3f995a07b38f76
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -42,22 +42,18 @@ gem install exwiw
|
|
|
42
42
|
- sqlite
|
|
43
43
|
- mongodb (experimental, see [MongoDB notes](#mongodb-notes))
|
|
44
44
|
|
|
45
|
-
Adapter names are driver-agnostic — they name the database, not the Ruby driver
|
|
46
|
-
or Rails ActiveRecord adapter. For backward compatibility and to absorb the Rails
|
|
47
|
-
adapter spelling, the following aliases are also accepted (case-insensitive):
|
|
48
|
-
|
|
49
|
-
| `--adapter` value | Aliases accepted |
|
|
50
|
-
| --- | --- |
|
|
51
|
-
| `mysql` | `mysql2` |
|
|
52
|
-
| `sqlite` | `sqlite3` |
|
|
53
|
-
|
|
54
|
-
So `--adapter=mysql2` and `--adapter=mysql` both select the same MySQL adapter.
|
|
55
|
-
|
|
56
45
|
For MySQL, exwiw connects through whichever of the `mysql2` or `trilogy` gem is
|
|
57
46
|
available (preferring `mysql2`), so an app on either driver works without any
|
|
58
47
|
extra setup. There is no separate `trilogy` adapter name — pass `--adapter=mysql`
|
|
59
48
|
either way.
|
|
60
49
|
|
|
50
|
+
Set `EXWIW_MYSQL_DRIVER=trilogy` (or `mysql2`) to force a specific driver. This
|
|
51
|
+
is useful when the `mysql2` gem is linked against a `libmysqlclient` that can no
|
|
52
|
+
longer load the server's auth plugin — e.g. a MySQL 9.x client drops the
|
|
53
|
+
`mysql_native_password` plugin and raises `Authentication plugin
|
|
54
|
+
'mysql_native_password' cannot be loaded` on connect. The pure-Ruby `trilogy`
|
|
55
|
+
driver implements that auth handshake itself and sidesteps the issue.
|
|
56
|
+
|
|
61
57
|
## Usage
|
|
62
58
|
|
|
63
59
|
exwiw has two subcommands:
|
|
@@ -108,7 +104,7 @@ This command will generate sql files in the `dump` directory.
|
|
|
108
104
|
idx means the order of the dump. bigger idx might depend on smaller idx,
|
|
109
105
|
so you should import the dump in order.
|
|
110
106
|
|
|
111
|
-
`insert-000-schema.sql` is generated by shelling out to the database client tools (`mysqldump` for `mysql`, `pg_dump` for `postgresql`, and the sqlite3 driver for `sqlite`), so the corresponding client must be available on PATH when running exwiw. The output is post-processed to make it idempotent: `CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS` (where the engine supports it), and PostgreSQL's `ALTER TABLE ... ADD CONSTRAINT` statements are wrapped in `DO $$ ... EXCEPTION WHEN duplicate_object`.
|
|
107
|
+
`insert-000-schema.sql` is generated by shelling out to the database client tools (`mysqldump` for `mysql`, `pg_dump` for `postgresql`, and the sqlite3 driver for `sqlite`), so the corresponding client must be available on PATH when running exwiw. For `mysql`, set `EXWIW_MYSQLDUMP` to point at a specific `mysqldump` binary when the one on PATH is incompatible with the server (e.g. a MySQL 9.x `mysqldump` cannot load `mysql_native_password` against a server still using that auth plugin — `EXWIW_MYSQLDUMP=/path/to/mysql@8.0/bin/mysqldump`). The output is post-processed to make it idempotent: `CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS` (where the engine supports it), and PostgreSQL's `ALTER TABLE ... ADD CONSTRAINT` statements are wrapped in `DO $$ ... EXCEPTION WHEN duplicate_object`.
|
|
112
108
|
|
|
113
109
|
you need to delete the records before importing the dump,
|
|
114
110
|
`delete-{idx}-{table_name}.sql` will help you to do that.
|
|
@@ -34,8 +34,16 @@ module Exwiw
|
|
|
34
34
|
return
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# The mysqldump binary is invoked directly (not via the mysql2/trilogy
|
|
38
|
+
# driver), so point EXWIW_MYSQLDUMP at a specific binary when the one on
|
|
39
|
+
# PATH is incompatible with the server — e.g. a MySQL 9.x client whose
|
|
40
|
+
# mysqldump cannot load `mysql_native_password` ("plugin ... cannot be
|
|
41
|
+
# loaded", exit 2) against a server still using that auth plugin.
|
|
42
|
+
mysqldump_bin = ENV['EXWIW_MYSQLDUMP']
|
|
43
|
+
mysqldump_bin = 'mysqldump' if mysqldump_bin.nil? || mysqldump_bin.empty?
|
|
44
|
+
|
|
37
45
|
cmd = [
|
|
38
|
-
|
|
46
|
+
mysqldump_bin,
|
|
39
47
|
"--host=#{@connection_config.host}",
|
|
40
48
|
"--port=#{@connection_config.port}",
|
|
41
49
|
"--user=#{@connection_config.user}",
|
|
@@ -58,11 +66,18 @@ module Exwiw
|
|
|
58
66
|
]
|
|
59
67
|
env = { 'MYSQL_PWD' => @connection_config.password.to_s }
|
|
60
68
|
|
|
61
|
-
@logger.debug(" Running
|
|
62
|
-
stdout, stderr, status =
|
|
69
|
+
@logger.debug(" Running #{mysqldump_bin} for #{table_names.size} table(s)...")
|
|
70
|
+
stdout, stderr, status =
|
|
71
|
+
begin
|
|
72
|
+
Open3.capture3(env, *cmd)
|
|
73
|
+
rescue Errno::ENOENT
|
|
74
|
+
raise "Failed to run `#{mysqldump_bin}`. Ensure the mysql client is installed and on PATH, " \
|
|
75
|
+
"or set EXWIW_MYSQLDUMP to a mysqldump binary."
|
|
76
|
+
end
|
|
63
77
|
unless status.success?
|
|
64
78
|
if stderr.include?('command not found') || stderr.empty?
|
|
65
|
-
raise "Failed to run
|
|
79
|
+
raise "Failed to run `#{mysqldump_bin}`. Ensure the mysql client is installed and on PATH, " \
|
|
80
|
+
"or set EXWIW_MYSQLDUMP to a mysqldump binary. stderr: #{stderr}"
|
|
66
81
|
end
|
|
67
82
|
raise "mysqldump failed (exit #{status.exitstatus}): #{stderr}"
|
|
68
83
|
end
|
|
@@ -18,9 +18,27 @@ module Exwiw
|
|
|
18
18
|
# Immutable value object: a query's column names and its rows.
|
|
19
19
|
Result = Data.define(:fields, :rows)
|
|
20
20
|
|
|
21
|
+
DRIVERS = [:mysql2, :trilogy].freeze
|
|
22
|
+
|
|
21
23
|
# Pick the available driver, preferring mysql2 (exwiw's historical default).
|
|
22
24
|
# require returns false when already loaded, so this is safe to call repeatedly.
|
|
25
|
+
#
|
|
26
|
+
# Set EXWIW_MYSQL_DRIVER=trilogy to force the pure-Ruby trilogy driver. This
|
|
27
|
+
# is useful when the mysql2 gem is linked against a libmysqlclient that can
|
|
28
|
+
# no longer load the server's auth plugin (e.g. MySQL 9.x client dropped the
|
|
29
|
+
# `mysql_native_password` plugin .so, raising "Authentication plugin
|
|
30
|
+
# 'mysql_native_password' cannot be loaded" on connect).
|
|
23
31
|
def self.detect_driver
|
|
32
|
+
forced = ENV['EXWIW_MYSQL_DRIVER']
|
|
33
|
+
if forced && !forced.empty?
|
|
34
|
+
sym = forced.to_sym
|
|
35
|
+
unless DRIVERS.include?(sym)
|
|
36
|
+
raise ArgumentError,
|
|
37
|
+
"EXWIW_MYSQL_DRIVER must be one of #{DRIVERS.join(', ')}, got #{forced.inspect}."
|
|
38
|
+
end
|
|
39
|
+
return sym
|
|
40
|
+
end
|
|
41
|
+
|
|
24
42
|
require 'mysql2'
|
|
25
43
|
:mysql2
|
|
26
44
|
rescue LoadError
|
|
@@ -24,6 +24,10 @@ module Exwiw
|
|
|
24
24
|
not_resolved_names.empty?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
if tables_with_no_dependencies.empty?
|
|
28
|
+
raise ArgumentError, build_cycle_error_message(table_by_name, ordered_table_names)
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
tables_with_no_dependencies.each do |table|
|
|
28
32
|
ordered_table_names << table.name
|
|
29
33
|
table_by_name.delete(table.name)
|
|
@@ -38,5 +42,25 @@ module Exwiw
|
|
|
38
42
|
acc << relation.table_name
|
|
39
43
|
end
|
|
40
44
|
end
|
|
45
|
+
|
|
46
|
+
# When no table can be resolved but some remain, the belongs_to graph
|
|
47
|
+
# contains a cycle (e.g. A belongs_to B and B belongs_to A). A topological
|
|
48
|
+
# order cannot exist, so report the offending tables instead of looping
|
|
49
|
+
# forever.
|
|
50
|
+
private_class_method def cycle_diagnostics(table_by_name, ordered_table_names)
|
|
51
|
+
table_by_name.values.map do |table|
|
|
52
|
+
unresolved = (compute_table_dependencies(table) - ordered_table_names - [table.name]).uniq
|
|
53
|
+
" #{table.name} -> #{unresolved.join(', ')}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private_class_method def build_cycle_error_message(table_by_name, ordered_table_names)
|
|
58
|
+
"Circular belongs_to dependency detected among tables: " \
|
|
59
|
+
"#{table_by_name.keys.sort.join(', ')}. " \
|
|
60
|
+
"A processing order cannot be determined. " \
|
|
61
|
+
"Remove one of the belongs_to entries forming the cycle.\n" \
|
|
62
|
+
"Unresolved dependencies:\n" \
|
|
63
|
+
"#{cycle_diagnostics(table_by_name, ordered_table_names).join("\n")}"
|
|
64
|
+
end
|
|
41
65
|
end
|
|
42
66
|
end
|
data/lib/exwiw/version.rb
CHANGED