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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/exwiw/adapter/mongodb_adapter.rb +17 -3
- data/lib/exwiw/adapter/mysql_adapter.rb +2 -1
- data/lib/exwiw/ddl_postprocessor.rb +38 -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: b4e2f7c1a407de6ba1787a074b477f15c2d52844db280ab3b46a7ea0ff53307d
|
|
4
|
+
data.tar.gz: eb16c182003ff444a9dcb0c50bdeb01b0d0c64d030e3a604cea97b4391eb9749
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
480
|
-
#
|
|
481
|
-
#
|
|
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.
|
|
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