exwiw 0.9.15 → 0.9.17

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: 471697b8003fdbb3178382139b96d1954e4610691e1086d1336169cc5f26e572
4
- data.tar.gz: afd836794750c8ebd6b137f0067b386e8558a43b0c05f29df461e4e565257d11
3
+ metadata.gz: b4e2f7c1a407de6ba1787a074b477f15c2d52844db280ab3b46a7ea0ff53307d
4
+ data.tar.gz: eb16c182003ff444a9dcb0c50bdeb01b0d0c64d030e3a604cea97b4391eb9749
5
5
  SHA512:
6
- metadata.gz: 9be9e481528119cacda6da2cd7be4379803c8be4fc03c49e9361d07f578f7e70ea6b5dab6c639c2acde2d63ec23933ba8e40e6ac60cddd2063db56ff7876aafe
7
- data.tar.gz: 5e40c552665e7a4b24e4729c8b4e6abb5d0dcefd52b61baf42e747a93b46e6268c09db53f70b4f45b60d63e451bb0a4588a3968b1bef02181e2544c7d8c3c1b0
6
+ metadata.gz: 05b71a92ba8a702f8651812bf38b1eef09c65ff4d39c020e571ad8b9f999ffc6ee003dbd16db933355337fc4e497092f3a0272c885ebc401300b353a83bdf412
7
+ data.tar.gz: 26fcf8c9aa7047ca99a2a89d259be9c894ea3d8a072f0fe2fd5e3387a24e4206a3187f7a4bb921b0482669804997d1138db7239f9a80e10de5f8b1b8116b3cd1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.17] - 2026-08-03
6
+
7
+ ### Fixed
8
+
9
+ - **mysql: `insert-000-schema.sql` no longer carries the source server's `DEFINER=` clauses, so a restore into MySQL 8.2+ / 8.4 succeeds without the `SET_ANY_DEFINER` privilege.** `mysqldump` stamps every view and trigger with the account that owns it on the source (e.g. `/*!50013 DEFINER=user@host SQL SECURITY DEFINER */`). From MySQL 8.2, creating a stored object owned by another account needs `SET_ANY_DEFINER` — a privilege managed MySQL (RDS / Cloud SQL) does not grant — so the restore aborted, and a definer account absent on the target left an orphan object that broke later `CREATE USER` / `DROP USER`. A new `DdlPostprocessor.strip_definer_clauses` pass removes the clause (only from the version-gated comment `mysqldump` wraps it in, so a literal `DEFINER=` inside a trigger body or column `COMMENT` is untouched), so each object is created owned by the restoring account — MySQL's default when the clause is omitted. `SQL SECURITY DEFINER` / `INVOKER` is preserved as written; postgresql/sqlite are unaffected. Output for a database with no views or triggers is byte-identical. Note: a schema containing views or triggers is still not *re*-appliable (`--skip-add-drop-table` suppresses the `DROP` that would make it so) — tracked separately.
10
+
11
+ ## [0.9.16] - 2026-07-31
12
+
13
+ ### Fixed
14
+
15
+ - **MongoDB: a collection whose genuine (target-reachable) parents captured no ids now matches nothing instead of falling back to reference-parent constraints.** When the dump target legitimately owns no rows in a scoping chain (e.g. a just-created tenant with no rows in the anchor collection), the previous strict-AND fallback over reference parents — master data dumped in full — degenerated into a near-full-collection query: at best dumping cross-scope rows, at worst timing out or exceeding the BSON size limit on a large collection. Collections with no genuine parent at all (reachable only via reference data) keep the historical fallback.
16
+
5
17
  ## [0.9.15] - 2026-07-31
6
18
 
7
19
  ### Fixed
data/README.md CHANGED
@@ -106,7 +106,7 @@ The output dir is emptied before each export so it never mixes files from a prev
106
106
  idx means the order of the dump. bigger idx might depend on smaller idx,
107
107
  so you should import the dump in order.
108
108
 
109
- `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`.
109
+ `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`. For `mysql`, the source server's `DEFINER=user@host` stamp on views and triggers is stripped too, so restoring into a managed MySQL instance (which usually can't grant the privilege to recreate someone else's `DEFINER`) does not fail.
110
110
 
111
111
  you need to delete the records before importing the dump,
112
112
  `delete-{idx}-{table_name}.sql` will help you to do that.
@@ -476,9 +476,13 @@ module Exwiw
476
476
  # has a genuine parent to anchor on, reference-parent constraints are
477
477
  # dropped entirely.
478
478
  #
479
- # When NO genuine parent produced ids, the collection is not reachable from
480
- # the dump target; fall back to the historical strict-AND of whatever
481
- # constraints exist (bounded, preserves prior behavior).
479
+ # - When the collection DECLARES a genuine parent but none captured any id
480
+ # (the dump target legitimately owns no such rows e.g. a tenant created
481
+ # moments ago), it matches nothing: reference-parent constraints do not
482
+ # narrow to the target, so falling back to them degenerates into a
483
+ # near-full-collection query. Only a collection with NO genuine parent at
484
+ # all (reachable only via reference data) keeps the historical strict-AND
485
+ # fallback.
482
486
  #
483
487
  # A belongs_to whose parent produced no ids contributes no constraint: either
484
488
  # the parent matched nothing, or it is not dumped here (e.g. an embedded
@@ -520,6 +524,16 @@ module Exwiw
520
524
  target << [relation.foreign_key, values]
521
525
  end
522
526
 
527
+ if genuine_clauses.empty? && reference_clauses.any? && config.belongs_tos.any? { |r| genuine.include?(r.table_name) }
528
+ @logger.warn(
529
+ " Collection '#{config.name}' is scoped by genuine parent(s) that captured no ids " \
530
+ "for this dump target; constraining it to match no rows instead of falling back to " \
531
+ "reference-parent constraints (which do not narrow to the target and can devolve " \
532
+ "into a full-collection scan)."
533
+ )
534
+ return { config.primary_key => { "$in" => [] } }
535
+ end
536
+
523
537
  filter =
524
538
  if genuine_clauses.any?
525
539
  anchor_index = (0...genuine_clauses.size).min_by { |i| genuine_clauses[i][1].size }
@@ -136,7 +136,8 @@ module Exwiw
136
136
  raise "mysqldump failed (exit #{status.exitstatus}): #{stderr}"
137
137
  end
138
138
 
139
- idempotent = DdlPostprocessor.add_if_not_exists_to_create_table(stdout)
139
+ idempotent = DdlPostprocessor.strip_definer_clauses(stdout)
140
+ idempotent = DdlPostprocessor.add_if_not_exists_to_create_table(idempotent)
140
141
 
141
142
  File.open(output_path, 'w') do |file|
142
143
  file.puts("-- Auto-generated by exwiw via mysqldump. Idempotent CREATE TABLE statements for mysql.")
@@ -64,6 +64,44 @@ module Exwiw
64
64
  sql.gsub(/^[ \t]*CREATE\s+(?:OR\s+REPLACE\s+)?(?:CONSTRAINT\s+)?TRIGGER\b[^;]*;\r?\n?/i, "")
65
65
  end
66
66
 
67
+ # A user@host pair as mysqldump writes it. Each side is independently a
68
+ # backtick-quoted identifier (doubled backticks escape), a single- or
69
+ # double-quoted string, or a bare word (`root@localhost`). A definer may
70
+ # also be CURRENT_USER / CURRENT_USER(), which has no @host.
71
+ DEFINER_ACCOUNT_PART =
72
+ /(?:`(?:[^`]|``)*`|'(?:[^']|'')*'|"(?:[^"]|"")*"|[A-Za-z0-9_$.%\-]+)/.freeze
73
+
74
+ DEFINER_CLAUSE =
75
+ /DEFINER[ \t]*=[ \t]*
76
+ (?:CURRENT_USER(?:[ \t]*\([ \t]*\))?
77
+ |#{DEFINER_ACCOUNT_PART}(?:@#{DEFINER_ACCOUNT_PART})?)/xi.freeze
78
+
79
+ # mysqldump wraps every view/trigger/routine/event DEFINER in a versioned
80
+ # comment, e.g. `/*!50013 DEFINER=`u`@`h` SQL SECURITY DEFINER */` or the
81
+ # trigger form `/*!50017 DEFINER=`u`@`h`*/`. From MySQL 8.2, creating a
82
+ # stored object owned by another account needs SET_ANY_DEFINER, which
83
+ # managed MySQL (RDS / Cloud SQL) does not grant, and a nonexistent
84
+ # definer leaves an orphan object that later breaks CREATE USER / DROP
85
+ # USER. Omitting the clause defaults the definer to CURRENT_USER (the
86
+ # restoring account), sidestepping both.
87
+ #
88
+ # Anchoring on the versioned comment, rather than gsubbing DEFINER=
89
+ # anywhere, keeps this from touching a literal "DEFINER=" inside a
90
+ # trigger body string or a column COMMENT. SQL SECURITY DEFINER|INVOKER
91
+ # is preserved — it governs runtime privileges, not who the definer is.
92
+ DEFINER_COMMENT_RE =
93
+ %r{/\*!(?<ver>\d{5})[ \t]+#{DEFINER_CLAUSE}[ \t]*
94
+ (?<rest>(?:(?!\*/).)*?)[ \t]*\*/(?<trail>[ \t]*)}xi.freeze
95
+
96
+ def strip_definer_clauses(sql)
97
+ sql.gsub(DEFINER_COMMENT_RE) do
98
+ m = Regexp.last_match
99
+ # Drop the comment whole if DEFINER was its only content (trigger
100
+ # form); otherwise keep the surviving content (e.g. SQL SECURITY).
101
+ m[:rest].empty? ? "" : "/*!#{m[:ver]} #{m[:rest]} */#{m[:trail]}"
102
+ end
103
+ end
104
+
67
105
  # A bare `CREATE TYPE ... AS ENUM (...)` (as a full-database pg_dump emits,
68
106
  # unlike a `--table` dump, which omits enum types) is not idempotent: a
69
107
  # second restore raises `duplicate_object`. Wrap each in a DO block that
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.9.15"
4
+ VERSION = "0.9.17"
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.9.15
4
+ version: 0.9.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia