exwiw 0.2.9 → 0.3.0
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 +3 -1
- data/docs/plans/2026-05-31-ids-column-for-sql-adapters.md +93 -0
- data/lib/exwiw/adapter/mysql2_adapter.rb +9 -0
- data/lib/exwiw/adapter/postgresql_adapter.rb +9 -0
- data/lib/exwiw/adapter/sqlite3_adapter.rb +9 -0
- data/lib/exwiw/cli.rb +41 -18
- data/lib/exwiw/query_ast.rb +20 -1
- data/lib/exwiw/query_ast_builder.rb +37 -15
- data/lib/exwiw/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e62483afc43054ae026c232502f40f6c1a92381a9c2c833ef1f7d603f6b845ae
|
|
4
|
+
data.tar.gz: 660a006272b9156bbc98af2a0c288f7943bdd40523adffc45de713b36a5d0426
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 988813923c5e8a2aa1fd499c3af3a2d5467fc9a77b60664b1cd8d245ed1837a1cf43dc88a872bbbcfb7951902624e6b61fb50d290af6716a06c1fe8bb376ae6a
|
|
7
|
+
data.tar.gz: d8f751b794c20658c643ab2aeca769b01f7b50e9acc46c9605a4d7121d0d5fa8041bfbdad8021ede6bbfefc8cb36a937509120f25b7bdd059aaf6989f4c94e00
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -67,6 +67,8 @@ exwiw \
|
|
|
67
67
|
--log-level=info
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
By default `--ids` are matched against the target table's primary key. `--ids-column=COLUMN` matches them against a different column instead (e.g. `--target-table=users --ids=alice@example.com --ids-column=email`). Related tables are still extracted correctly: their foreign keys are resolved through the target via a subquery (`WHERE fk IN (SELECT pk FROM target WHERE COLUMN IN (...))`), so only the target table's filter column changes. This is the SQL-adapter counterpart of the mongodb `--ids-field`; the two are mutually exclusive and each is rejected by the other adapter family. Note: if `COLUMN` is itself masked, re-running `delete-*` against an already-imported (masked) dump won't match, so prefer a stable natural key.
|
|
71
|
+
|
|
70
72
|
When `--target-table` and `--ids` are omitted, exwiw dumps all tables defined in `--config-dir`:
|
|
71
73
|
|
|
72
74
|
```bash
|
|
@@ -412,7 +414,7 @@ The MongoDB adapter is experimental. To use it:
|
|
|
412
414
|
- The MongoDB adapter consumes a separate config type, `MongodbCollectionConfig`, with MongoDB-native naming. Use `fields` (instead of the SQL adapters' `columns`), and set `"primary_key": "_id"`. Foreign keys (`shop_id`, `user_id`, ...) stay as ordinary fields.
|
|
413
415
|
- `--ids` values are coerced to the type actually stored in `_id` before filtering: integer-looking ids become `Integer`, 24-char hex ids become `BSON::ObjectId` (Mongoid's default `_id` type — a plain String would never match an ObjectId), and any other string is left as-is.
|
|
414
416
|
- `--target-collection=COLLECTION` is a mongodb-only alias of `--target-table` (use whichever reads better for MongoDB). Specifying both, or using `--target-collection` with a non-mongodb adapter, is an error.
|
|
415
|
-
- `--ids-field=FIELD` matches `--ids` against `FIELD` on the target collection instead of its primary key (e.g. `--target-collection=users --ids=a@example.com --ids-field=email`). Downstream foreign-key propagation still keys off the primary key, so only the target collection's filter changes. Unlike the primary-key path, the supplied ids are **not** type-coerced (the stored type of a custom field is unknown), so pass values matching the field's actual type. This flag is
|
|
417
|
+
- `--ids-field=FIELD` matches `--ids` against `FIELD` on the target collection instead of its primary key (e.g. `--target-collection=users --ids=a@example.com --ids-field=email`). Downstream foreign-key propagation still keys off the primary key, so only the target collection's filter changes. Unlike the primary-key path, the supplied ids are **not** type-coerced (the stored type of a custom field is unknown), so pass values matching the field's actual type. This flag is **mongodb-only**; the SQL adapters use `--ids-column` instead (see below).
|
|
416
418
|
- Output is JSON Lines (`insert-{idx}-{collection}.jsonl`) using MongoDB Extended JSON (relaxed mode). Import with `mongoimport`:
|
|
417
419
|
```bash
|
|
418
420
|
mongoimport --db app_dev --collection users --file dump/insert-002-users.jsonl
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# `--ids-column` を SQL アダプタに実装
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
MongoDB アダプタには `--ids-field` フラグがあり、`--ids` を対象テーブルの主キー以外の
|
|
6
|
+
フィールドにマッチさせられる(`lib/exwiw/adapter/mongodb_adapter.rb:48-65`)。一方で
|
|
7
|
+
SQL アダプタ(mysql2 / postgresql / sqlite3)では未実装で、CLI が明示的に拒否している
|
|
8
|
+
(`lib/exwiw/cli.rb:183-189` の TODO、`lib/exwiw/query_ast_builder.rb:109-118` の TODO)。
|
|
9
|
+
|
|
10
|
+
このプランでは同等の機能を SQL アダプタにも提供する。命名・ゲーティングは既存の
|
|
11
|
+
`--target-table` / `--target-collection` の分け方を踏襲し、**アダプタ別に厳密分離**する:
|
|
12
|
+
|
|
13
|
+
- `--ids-field` … mongodb 専用(既存)
|
|
14
|
+
- `--ids-column` … SQL アダプタ専用(新規)
|
|
15
|
+
- 両方同時指定は拒否、不適合アダプタとの組み合わせも拒否
|
|
16
|
+
|
|
17
|
+
内部的には双方とも `DumpTarget#ids_field` に集約される(`--target-collection` が
|
|
18
|
+
`@target_table_name` に畳まれるのと同じパターン)。
|
|
19
|
+
|
|
20
|
+
## 変更内容
|
|
21
|
+
|
|
22
|
+
### 1. `lib/exwiw/cli.rb` — フラグ定義・畳み込み・バリデーション
|
|
23
|
+
|
|
24
|
+
- **インスタンス変数追加**(`initialize`, 42行目付近): `@ids_column = nil` を追加。
|
|
25
|
+
- **フラグ定義**(`parser`, 309行目付近): `--ids-field` の直後に追加。
|
|
26
|
+
```ruby
|
|
27
|
+
opts.on("--ids-column=[COLUMN]", "Column on the target table that --ids is matched against. Defaults to the primary key. (sql adapters only)") { |v| @ids_column = v }
|
|
28
|
+
```
|
|
29
|
+
- **エイリアス畳み込み**: `resolve_target_collection_alias!`(210行目)に倣い
|
|
30
|
+
`resolve_ids_column_alias!` を新設し `validate_options!` の冒頭で呼ぶ。挙動:
|
|
31
|
+
- `--ids-field` と `--ids-column` の同時指定を拒否
|
|
32
|
+
("Specify only one of --ids-field and --ids-column")。
|
|
33
|
+
- `--ids-column` を mongodb で使った場合は拒否
|
|
34
|
+
("--ids-column is only supported by the sql adapters (use --ids-field)")。
|
|
35
|
+
- 問題なければ `@ids_field = @ids_column` に畳み込む。
|
|
36
|
+
- **`--ids-field` の検証更新**(175-190行目):
|
|
37
|
+
- `--target-table` 必須チェックは「`@ids_field` が立っていれば」で共通化されるため、
|
|
38
|
+
畳み込み後はそのまま両方をカバーする(メッセージは ids_field/ids_column を
|
|
39
|
+
使った側に合わせて出し分けるか、汎用文言にする — 実装時に調整)。
|
|
40
|
+
- mongodb 限定チェック(186-189行目)は `--ids-field` 用に維持
|
|
41
|
+
("--ids-field is currently only supported by the mongodb adapter" のまま)。
|
|
42
|
+
|
|
43
|
+
実装方針: 畳み込み前にどちらのフラグが使われたかが分かる状態で
|
|
44
|
+
「target-table 必須」「アダプタ整合」を検証してから `@ids_field` に集約する。
|
|
45
|
+
|
|
46
|
+
### 2. `lib/exwiw/query_ast_builder.rb` — WHERE 句に反映
|
|
47
|
+
|
|
48
|
+
**当初の想定(1行変更)は関連テーブルで不正確になることが判明**したため、設計を変更した。
|
|
49
|
+
SQL アダプタは単一クエリで `dump_target.ids` を外部キーに直接伝播する
|
|
50
|
+
(`orders.user_id IN ids`)。これは `--ids` が主キーである前提のため、`--ids-column`
|
|
51
|
+
で別カラムを指定すると関連テーブルが壊れる(mongodb は @state に主キーを溜めて伝播する
|
|
52
|
+
ので正しい)。
|
|
53
|
+
|
|
54
|
+
採用したアプローチ: **ターゲットを介すサブクエリ**。`ids_field` 指定時、外部キー制約を
|
|
55
|
+
`fk IN (SELECT pk FROM target WHERE ids_field IN (ids))` に置き換える。direct /
|
|
56
|
+
indirect / polymorphic を一律に正しく扱える。
|
|
57
|
+
|
|
58
|
+
- `lib/exwiw/query_ast.rb`: `Subquery` 構造体を追加。`WhereClause` に
|
|
59
|
+
operator `:in_subquery`(value が `Subquery`)を導入し `to_h` を対応。
|
|
60
|
+
- `lib/exwiw/query_ast_builder.rb`: `dump_target_fk_clause(foreign_key)` ヘルパーを新設。
|
|
61
|
+
`ids_field` 無しなら従来通り `eq`、有りなら `:in_subquery` を返す。
|
|
62
|
+
- `build_where_clauses`(direct belongs_to)と `build_join_clauses`
|
|
63
|
+
(indirect の `relation_to_dump_target` hop)の両方で利用。
|
|
64
|
+
- ターゲットテーブル自身のフィルタは `ids_field || primary_key` の `eq`(従来どおり)。
|
|
65
|
+
- 各 SQL アダプタ(postgresql / mysql2 / sqlite3)の `compile_where_condition` に
|
|
66
|
+
`:in_subquery` 分岐と `compile_subquery` を追加。`is_a?(WhereClause)` のままなので
|
|
67
|
+
bulk_delete のサブクエリ生成・JoinClause.to_h もそのまま動く。
|
|
68
|
+
|
|
69
|
+
補足: `--ids-column` がマスク対象カラムの場合、`delete-*` の冪等性が崩れる
|
|
70
|
+
(README に注記済み)。
|
|
71
|
+
|
|
72
|
+
## 検証
|
|
73
|
+
|
|
74
|
+
- `bundle exec rspec spec/cli_spec.rb` — 既存の `--ids-field` validation を維持しつつ、
|
|
75
|
+
新規ケースを追加:
|
|
76
|
+
- `--ids-column` が `@ids_field`(畳み込み後)にパースされる
|
|
77
|
+
- `--ids-column` を mongodb で指定すると拒否される
|
|
78
|
+
- `--ids-field` と `--ids-column` の同時指定が拒否される
|
|
79
|
+
- `--ids-column` を target-table 無しで指定すると拒否される
|
|
80
|
+
- `bundle exec rspec spec/query_ast_builder_spec.rb`(存在すれば)に
|
|
81
|
+
`ids_field` 指定時に対象テーブルの WHERE が主キーではなく当該カラムになることを確認する
|
|
82
|
+
ケースを追加。
|
|
83
|
+
- `explain` サブコマンド(SQL のみ対応)で end-to-end 確認:
|
|
84
|
+
既存 scenario(例 `scenario/sqlite3-schema`)に対し
|
|
85
|
+
`--target-table=... --ids=... --ids-column=<col>` を渡し、出力 SQL の WHERE が
|
|
86
|
+
`<table>.<col> IN (...)` になることを目視確認。
|
|
87
|
+
- `bundle exec rspec`(全体)でリグレッションが無いこと。
|
|
88
|
+
|
|
89
|
+
## ドキュメント
|
|
90
|
+
|
|
91
|
+
`README.md:415` の `--ids-field` 説明を更新し、SQL では `--ids-column` を使う旨と例
|
|
92
|
+
(例: `--target-table=users --ids=a@example.com --ids-column=email`)を追記。
|
|
93
|
+
「SQL adapters reject it / TODO」の記述を解消する。
|
|
@@ -201,11 +201,20 @@ module Exwiw
|
|
|
201
201
|
else
|
|
202
202
|
"#{key} IN (#{values.join(', ')})"
|
|
203
203
|
end
|
|
204
|
+
elsif where_clause.operator == :in_subquery
|
|
205
|
+
"#{key} IN (#{compile_subquery(where_clause.value)})"
|
|
204
206
|
else
|
|
205
207
|
raise "Unsupported operator: #{where_clause.operator}"
|
|
206
208
|
end
|
|
207
209
|
end
|
|
208
210
|
|
|
211
|
+
private def compile_subquery(subquery)
|
|
212
|
+
inner_values = subquery.where_values.map { |v| escape_value(v) }
|
|
213
|
+
"SELECT #{subquery.table_name}.#{subquery.select_column} " \
|
|
214
|
+
"FROM #{subquery.table_name} " \
|
|
215
|
+
"WHERE #{subquery.table_name}.#{subquery.where_column} IN (#{inner_values.join(', ')})"
|
|
216
|
+
end
|
|
217
|
+
|
|
209
218
|
private def escape_value(value)
|
|
210
219
|
case value
|
|
211
220
|
when nil
|
|
@@ -243,11 +243,20 @@ module Exwiw
|
|
|
243
243
|
else
|
|
244
244
|
"#{key} IN (#{values.join(', ')})"
|
|
245
245
|
end
|
|
246
|
+
elsif where_clause.operator == :in_subquery
|
|
247
|
+
"#{key} IN (#{compile_subquery(where_clause.value)})"
|
|
246
248
|
else
|
|
247
249
|
raise "Unsupported operator: #{where_clause.operator}"
|
|
248
250
|
end
|
|
249
251
|
end
|
|
250
252
|
|
|
253
|
+
private def compile_subquery(subquery)
|
|
254
|
+
inner_values = subquery.where_values.map { |v| escape_value(v) }
|
|
255
|
+
"SELECT #{subquery.table_name}.#{subquery.select_column} " \
|
|
256
|
+
"FROM #{subquery.table_name} " \
|
|
257
|
+
"WHERE #{subquery.table_name}.#{subquery.where_column} IN (#{inner_values.join(', ')})"
|
|
258
|
+
end
|
|
259
|
+
|
|
251
260
|
private def escape_value(value)
|
|
252
261
|
case value
|
|
253
262
|
when nil
|
|
@@ -188,11 +188,20 @@ module Exwiw
|
|
|
188
188
|
else
|
|
189
189
|
"#{key} IN (#{values.join(', ')})"
|
|
190
190
|
end
|
|
191
|
+
elsif where_clause.operator == :in_subquery
|
|
192
|
+
"#{key} IN (#{compile_subquery(where_clause.value)})"
|
|
191
193
|
else
|
|
192
194
|
raise "Unsupported operator: #{where_clause.operator}"
|
|
193
195
|
end
|
|
194
196
|
end
|
|
195
197
|
|
|
198
|
+
private def compile_subquery(subquery)
|
|
199
|
+
inner_values = subquery.where_values.map { |v| escape_value(v) }
|
|
200
|
+
"SELECT #{subquery.table_name}.#{subquery.select_column} " \
|
|
201
|
+
"FROM #{subquery.table_name} " \
|
|
202
|
+
"WHERE #{subquery.table_name}.#{subquery.where_column} IN (#{inner_values.join(', ')})"
|
|
203
|
+
end
|
|
204
|
+
|
|
196
205
|
private def escape_value(value)
|
|
197
206
|
case value
|
|
198
207
|
when nil
|
data/lib/exwiw/cli.rb
CHANGED
|
@@ -40,6 +40,7 @@ module Exwiw
|
|
|
40
40
|
@target_collection_name = nil
|
|
41
41
|
@ids = []
|
|
42
42
|
@ids_field = nil
|
|
43
|
+
@ids_column = nil
|
|
43
44
|
@output_format = nil
|
|
44
45
|
@insert_only = nil
|
|
45
46
|
@after_insert_hook_path = nil
|
|
@@ -99,6 +100,7 @@ module Exwiw
|
|
|
99
100
|
|
|
100
101
|
private def validate_options!
|
|
101
102
|
resolve_target_collection_alias!
|
|
103
|
+
resolve_ids_column_alias!
|
|
102
104
|
|
|
103
105
|
if @subcommand == "explain"
|
|
104
106
|
validate_explain_only!
|
|
@@ -172,23 +174,6 @@ module Exwiw
|
|
|
172
174
|
exit 1
|
|
173
175
|
end
|
|
174
176
|
|
|
175
|
-
if @ids_field
|
|
176
|
-
# --ids-field overrides the field --ids filters against on the target
|
|
177
|
-
# table; it is meaningless without a target table to constrain.
|
|
178
|
-
if !@target_table_name
|
|
179
|
-
$stderr.puts "--target-table is required when --ids-field is specified"
|
|
180
|
-
exit 1
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# TODO: support --ids-field for the sql adapters (mysql2/postgresql/
|
|
184
|
-
# sqlite3) by threading dump_target.ids_field through QueryAstBuilder's
|
|
185
|
-
# WHERE clause on the target table. For now it is mongodb-only.
|
|
186
|
-
if @database_adapter != "mongodb"
|
|
187
|
-
$stderr.puts "--ids-field is currently only supported by the mongodb adapter"
|
|
188
|
-
exit 1
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
177
|
if @after_insert_hook_path
|
|
193
178
|
unless File.file?(@after_insert_hook_path)
|
|
194
179
|
$stderr.puts "--after-insert-hook file not found: #{@after_insert_hook_path}"
|
|
@@ -223,6 +208,43 @@ module Exwiw
|
|
|
223
208
|
@target_table_name = @target_collection_name
|
|
224
209
|
end
|
|
225
210
|
|
|
211
|
+
# `--ids-column` is the sql-adapter spelling of `--ids-field` (the mongodb
|
|
212
|
+
# spelling). Both override which column/field `--ids` is matched against on
|
|
213
|
+
# the target table; internally they fold into the single @ids_field carried
|
|
214
|
+
# by DumpTarget. Mirror the --target-table/--target-collection split: each
|
|
215
|
+
# flag is restricted to its adapter family and the two are mutually
|
|
216
|
+
# exclusive. Runs after resolve_target_collection_alias! so
|
|
217
|
+
# @target_table_name already reflects the collection alias.
|
|
218
|
+
private def resolve_ids_column_alias!
|
|
219
|
+
if @ids_field && @ids_column
|
|
220
|
+
$stderr.puts "Specify only one of --ids-field and --ids-column"
|
|
221
|
+
exit 1
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if @ids_field && @database_adapter != "mongodb"
|
|
225
|
+
$stderr.puts "--ids-field is only supported by the mongodb adapter (use --ids-column)"
|
|
226
|
+
exit 1
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
if @ids_column
|
|
230
|
+
sql_adapters = ["mysql2", "postgresql", "sqlite3"]
|
|
231
|
+
unless sql_adapters.include?(@database_adapter)
|
|
232
|
+
$stderr.puts "--ids-column is only supported by the sql adapters (use --ids-field)"
|
|
233
|
+
exit 1
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
@ids_field = @ids_column
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# --ids-field/--ids-column override the column --ids filters against on
|
|
240
|
+
# the target table; meaningless without a target table to constrain.
|
|
241
|
+
if @ids_field && !@target_table_name
|
|
242
|
+
flag = @ids_column ? "--ids-column" : "--ids-field"
|
|
243
|
+
$stderr.puts "--target-table is required when #{flag} is specified"
|
|
244
|
+
exit 1
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
226
248
|
private def validate_explain_only!
|
|
227
249
|
if @database_adapter == "mongodb"
|
|
228
250
|
$stderr.puts "mongodb adapter is not yet supported by 'explain' subcommand"
|
|
@@ -306,7 +328,8 @@ module Exwiw
|
|
|
306
328
|
opts.on("--target-table=[TABLE]", "Target table for extraction. If omitted, dump all tables.") { |v| @target_table_name = v }
|
|
307
329
|
opts.on("--target-collection=[COLLECTION]", "Alias of --target-table for the mongodb adapter.") { |v| @target_collection_name = v }
|
|
308
330
|
opts.on("--ids=[IDS]", "Comma-separated list of identifiers. Required when --target-table is given.") { |v| @ids = v.split(',') }
|
|
309
|
-
opts.on("--ids-field=[FIELD]", "Field on the target
|
|
331
|
+
opts.on("--ids-field=[FIELD]", "Field on the target collection that --ids is matched against. Defaults to the primary key. (mongodb adapter only)") { |v| @ids_field = v }
|
|
332
|
+
opts.on("--ids-column=[COLUMN]", "Column on the target table that --ids is matched against. Defaults to the primary key. (sql adapters only)") { |v| @ids_column = v }
|
|
310
333
|
opts.on("--output-format=[FORMAT]", "Output format: insert (default) or copy (PostgreSQL only, dump subcommand only)") { |v| @output_format = v }
|
|
311
334
|
opts.on("--insert-only", "Do not generate DELETE SQL files (dump subcommand only)") { @insert_only = true }
|
|
312
335
|
opts.on("--after-insert-hook=PATH", "Path to a .rb or .sh post-processing hook executed after all insert/delete files are written (dump subcommand only)") do |v|
|
data/lib/exwiw/query_ast.rb
CHANGED
|
@@ -41,7 +41,26 @@ module Exwiw
|
|
|
41
41
|
{
|
|
42
42
|
column_name: column_name,
|
|
43
43
|
operator: operator,
|
|
44
|
-
value: value,
|
|
44
|
+
value: value.is_a?(Subquery) ? value.to_h : value,
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Resolves a set of values on `where_column` to the rows' `select_column`
|
|
50
|
+
# via a nested SELECT. Used as the `value` of a WhereClause whose operator
|
|
51
|
+
# is `:in_subquery`, so `--ids-column`/`--ids-field` can filter related
|
|
52
|
+
# tables through the target table's primary key:
|
|
53
|
+
#
|
|
54
|
+
# <table>.<fk> IN (SELECT <table_name>.<select_column>
|
|
55
|
+
# FROM <table_name>
|
|
56
|
+
# WHERE <table_name>.<where_column> IN (<where_values>))
|
|
57
|
+
Subquery = Struct.new(:table_name, :select_column, :where_column, :where_values, keyword_init: true) do
|
|
58
|
+
def to_h
|
|
59
|
+
{
|
|
60
|
+
table_name: table_name,
|
|
61
|
+
select_column: select_column,
|
|
62
|
+
where_column: where_column,
|
|
63
|
+
where_values: where_values,
|
|
45
64
|
}
|
|
46
65
|
end
|
|
47
66
|
end
|
|
@@ -73,11 +73,7 @@ module Exwiw
|
|
|
73
73
|
end
|
|
74
74
|
relation_to_dump_target = to_table.belongs_to(dump_target.table_name)
|
|
75
75
|
if relation_to_dump_target
|
|
76
|
-
join_clause.where_clauses.push
|
|
77
|
-
column_name: relation_to_dump_target.foreign_key,
|
|
78
|
-
operator: :eq,
|
|
79
|
-
value: dump_target.ids
|
|
80
|
-
)
|
|
76
|
+
join_clause.where_clauses.push dump_target_fk_clause(relation_to_dump_target.foreign_key)
|
|
81
77
|
|
|
82
78
|
# 中間テーブルが dump target へ polymorphic belongs_to している場合は、
|
|
83
79
|
# 型カラム (foreign_type) も join 条件に追加する。型カラムは to_table
|
|
@@ -107,12 +103,12 @@ module Exwiw
|
|
|
107
103
|
clauses = []
|
|
108
104
|
|
|
109
105
|
if table.name == dump_target.table_name
|
|
110
|
-
#
|
|
111
|
-
# primary-key column on the target table
|
|
112
|
-
#
|
|
113
|
-
#
|
|
106
|
+
# `--ids-column` (folded into dump_target.ids_field by the CLI) lets
|
|
107
|
+
# `--ids` match a non primary-key column on the target table; otherwise
|
|
108
|
+
# fall back to the primary key. Only the target table's filter changes —
|
|
109
|
+
# downstream foreign-key propagation still keys off the primary key.
|
|
114
110
|
clauses.push Exwiw::QueryAst::WhereClause.new(
|
|
115
|
-
column_name: table.primary_key,
|
|
111
|
+
column_name: dump_target.ids_field || table.primary_key,
|
|
116
112
|
operator: :eq,
|
|
117
113
|
value: dump_target.ids
|
|
118
114
|
)
|
|
@@ -123,11 +119,7 @@ module Exwiw
|
|
|
123
119
|
belongs_to = table.belongs_to(dump_target.table_name)
|
|
124
120
|
return clauses if belongs_to.nil?
|
|
125
121
|
|
|
126
|
-
clauses.push
|
|
127
|
-
column_name: belongs_to.foreign_key,
|
|
128
|
-
operator: :eq,
|
|
129
|
-
value: dump_target.ids
|
|
130
|
-
)
|
|
122
|
+
clauses.push dump_target_fk_clause(belongs_to.foreign_key)
|
|
131
123
|
|
|
132
124
|
# polymorphic belongs_to の場合は外部キーだけでは型を区別できないため
|
|
133
125
|
# (例: reviewable_id=1 が Product なのか別モデルなのか判別できない)、
|
|
@@ -147,6 +139,36 @@ module Exwiw
|
|
|
147
139
|
clauses
|
|
148
140
|
end
|
|
149
141
|
|
|
142
|
+
# Builds the WHERE clause that constrains a `foreign_key` pointing at the
|
|
143
|
+
# dump target. Normally `--ids` are the target's primary keys, so a plain
|
|
144
|
+
# `foreign_key IN (ids)` suffices. When `--ids-column`/`--ids-field` is set
|
|
145
|
+
# (dump_target.ids_field), `--ids` match a non primary-key column instead,
|
|
146
|
+
# so the foreign key must be resolved through the target table:
|
|
147
|
+
# `foreign_key IN (SELECT pk FROM target WHERE ids_field IN (ids))`.
|
|
148
|
+
# This keeps related-table extraction correct regardless of whether the
|
|
149
|
+
# relation is direct, indirect, or polymorphic.
|
|
150
|
+
private def dump_target_fk_clause(foreign_key)
|
|
151
|
+
unless dump_target.ids_field
|
|
152
|
+
return Exwiw::QueryAst::WhereClause.new(
|
|
153
|
+
column_name: foreign_key,
|
|
154
|
+
operator: :eq,
|
|
155
|
+
value: dump_target.ids
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
target = table_by_name.fetch(dump_target.table_name)
|
|
160
|
+
Exwiw::QueryAst::WhereClause.new(
|
|
161
|
+
column_name: foreign_key,
|
|
162
|
+
operator: :in_subquery,
|
|
163
|
+
value: Exwiw::QueryAst::Subquery.new(
|
|
164
|
+
table_name: target.name,
|
|
165
|
+
select_column: target.primary_key,
|
|
166
|
+
where_column: dump_target.ids_field,
|
|
167
|
+
where_values: dump_target.ids
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
150
172
|
private def find_path_to_dump_target(table, table_by_name, dump_target)
|
|
151
173
|
return [] if table.name == dump_target.table_name
|
|
152
174
|
|
data/lib/exwiw/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shia
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- docs/plans/2026-05-22-after-insert-hook.md
|
|
41
41
|
- docs/plans/2026-05-22-postgres-copy-mode-scenario-test.md
|
|
42
42
|
- docs/plans/2026-05-29-rails-managed-tables.md
|
|
43
|
+
- docs/plans/2026-05-31-ids-column-for-sql-adapters.md
|
|
43
44
|
- exe/exwiw
|
|
44
45
|
- lib/exwiw.rb
|
|
45
46
|
- lib/exwiw/adapter.rb
|