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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83bb3c7245d9174e5d16e85e6de2a530b58ca445b570d692c5198682fc43b9ef
4
- data.tar.gz: cee17f919dab949c82942eae146a1d6182909ab6ef595e8cfc7d4c3f744d1ede
3
+ metadata.gz: 4b10da211ee91b4daa986b71b18fce7ac9a782da81ef92a1146f552368b84d66
4
+ data.tar.gz: 3893a581e2dac519021d6c86756333b980b3c587efcc5d13337e0fe98d9aaa42
5
5
  SHA512:
6
- metadata.gz: d655b0e5d55759932d7ab6646cb9ed9d0b699e6c96e6061863091eded7914c736b74c558e8a96d132ea94ef668376ae372a9eacf23f978d52540bf31c38c7c18
7
- data.tar.gz: 5e068572f70d2b1869e5dffceba9f681d0b153c0947da3fd7b35d746e7f33bbc6c2d5fa25d23c9dfa0f190e14d3304ec6f2940aa19168749aa05b303c572ea10
6
+ metadata.gz: 395b24342f9a752d5cda207a959ba905a9fc57c8b693faa838bf2978b7b1b08654e0c5a9901b8359d9af96afc70b36f5b199cfec04165ef9e6ad9b25df0f3ea6
7
+ data.tar.gz: fb5514dd6d9c724e1d136cf54abe31616f8cec9944522dd7385fbbb04ddf82f9c290c2935d9c7a55949c86b9c433df02fc43f63aa3cff6d93e3f995a07b38f76
data/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.3] - 2026-05-31
6
+
5
7
  ## [0.3.2] - 2026-05-31
6
8
 
7
9
  ### 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
- 'mysqldump',
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 mysqldump for #{table_names.size} table(s)...")
62
- stdout, stderr, status = Open3.capture3(env, *cmd)
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 `mysqldump`. Ensure the mysql client is installed and on PATH. stderr: #{stderr}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exwiw
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exwiw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia