exwiw 0.9.8 → 0.9.9
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 +6 -0
- data/README.md +42 -19
- data/lib/exwiw/japanese_names.rb +315 -0
- data/lib/exwiw/row_transformer.rb +161 -19
- data/lib/exwiw/table_config.rb +3 -2
- 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: 8b734fe407ce3c430c3197b04db275df36640849f6795d7636614d8fab0c6574
|
|
4
|
+
data.tar.gz: 4be4a94e18f929ef13c97072248e4c7b7e867ad80160841a81c0146a929b9e21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdb946b9f0b3ae2bfca4aa74aca93454cbc0ae6f332f0900216c3a35be9d8bdcef2b2082744bcc6262b6bc9cf72afae9a477c85f7e00d60da8d6eaa4ed55b825
|
|
7
|
+
data.tar.gz: 12f63468a2d834e558b98e73937e739aa2ab4581cf83699e0a675e280cfd6e6f158d136156400e3ec7c7672beaa63189a6e90c727cc1c87281412e061c72b417
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.9] - 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **`replace_with_fake_data` name types now produce a coherent identity, and gained kana types.** The person-family types (`human_name`, `first_name`, `last_name`) previously drew from three independent faker pools, so a single seed's first name, last name, and full name were unrelated people. They now share one per-locale pool of whole people keyed by the seed, so for a given seed `human_name` always equals `last_name` + `first_name` (ordered per locale: `姓 名` for `ja`, `First Last` otherwise). Three kana types are added — `human_name_kana`, `first_name_kana`, `last_name_kana` — for the reading of the same person. Because faker's `ja` locale has kanji names with no readings, exwiw bundles its own paired (kanji, katakana) dataset (`Exwiw::JapaneseNames`) and builds the `ja` name pool from it, so kana matches the kanji and a `ja`-name-only config needs no faker at all. Kana types require `locale: ja` (they raise a clear error otherwise). The person pool is sized independently of the other types at 20,000 (`PERSON_POOL_SIZE`), and for `ja` is filled with 20,000 *distinct* people enumerated from a 142×142 name dataset (rather than sampled with replacement, which left ~37% of slots duplicated), so the name space is large and evenly used. Determinism for the `ja` name family is now pinned to exwiw's bundled dataset version rather than the faker version; other types and locales are unchanged.
|
|
10
|
+
|
|
5
11
|
## [0.9.8] - 2026-07-09
|
|
6
12
|
|
|
7
13
|
### Added
|
data/README.md
CHANGED
|
@@ -850,34 +850,57 @@ fake value, across tables, runs, and adapters:
|
|
|
850
850
|
because their text forms differ per adapter. A `NULL` seed value hashes `""`
|
|
851
851
|
(still deterministic).
|
|
852
852
|
- Like `replace_with`, it **preserves NULL** in the target column.
|
|
853
|
-
- `locale` (optional) sets the
|
|
854
|
-
|
|
855
|
-
|
|
853
|
+
- `locale` (optional) sets the locale used to build the candidate values, e.g.
|
|
854
|
+
`{ "seed": "id", "type": "human_name", "locale": "ja" }` produces Japanese
|
|
855
|
+
names.
|
|
856
856
|
- Supported `type`s:
|
|
857
857
|
|
|
858
|
-
| type | example output |
|
|
859
|
-
|
|
860
|
-
| `human_name` | `Adrianna Kilback` |
|
|
861
|
-
| `first_name` | `Adrianna` |
|
|
862
|
-
| `last_name` | `Kilback` |
|
|
863
|
-
| `
|
|
864
|
-
| `
|
|
865
|
-
| `
|
|
866
|
-
| `
|
|
867
|
-
| `
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
858
|
+
| type | example output (en) | example output (`locale: ja`) |
|
|
859
|
+
|------|----------------|----------------|
|
|
860
|
+
| `human_name` | `Adrianna Kilback` | `山田 太郎` |
|
|
861
|
+
| `first_name` | `Adrianna` | `太郎` |
|
|
862
|
+
| `last_name` | `Kilback` | `山田` |
|
|
863
|
+
| `human_name_kana` | — (ja only) | `ヤマダ タロウ` |
|
|
864
|
+
| `first_name_kana` | — (ja only) | `タロウ` |
|
|
865
|
+
| `last_name_kana` | — (ja only) | `ヤマダ` |
|
|
866
|
+
| `phone_number` | `(555) 123-4567` | |
|
|
867
|
+
| `address` | `282 Kevin Brook, Imogeneborough, CA 58517` | |
|
|
868
|
+
| `company_name` | `Hirthe-Ritchie` | |
|
|
869
|
+
| `email` | `cliff.fay.9d6b804eff5a3f57@example.com` | |
|
|
870
|
+
| `username` | `cliff.fay_9d6b804eff5a3f57` | |
|
|
871
|
+
|
|
872
|
+
- **Coherent identity across the name family.** The person-family types
|
|
873
|
+
(`human_name`, `first_name`, `last_name` and their `*_kana` counterparts) all
|
|
874
|
+
draw from a single shared pool of people per locale, keyed by the same seed —
|
|
875
|
+
so for one seed value the last name, first name, full name, and every kana
|
|
876
|
+
reading belong to the **same person**: `human_name` always equals
|
|
877
|
+
`last_name` + `first_name`, and `human_name_kana` matches `human_name`. Full
|
|
878
|
+
names are ordered per locale (`姓 名` for `ja`, `First Last` otherwise).
|
|
879
|
+
- **Kana (`*_kana`) types require `locale: ja`.** faker's `ja` locale ships
|
|
880
|
+
kanji names with no reading, so exwiw bundles its own paired (kanji, katakana)
|
|
881
|
+
dataset for `ja`; this is what lets a fake person carry a kana reading that
|
|
882
|
+
actually matches its kanji. Requesting a `*_kana` type with any other locale
|
|
883
|
+
raises a clear error at build time.
|
|
884
|
+
- Values are drawn from a pre-generated pool per (type, locale), so distinct
|
|
885
|
+
seeds can share a fake value. The name family uses one shared **person** pool
|
|
886
|
+
of 20,000 identities — for `ja` these are 20,000 *distinct* people enumerated
|
|
887
|
+
from the bundled (kanji, kana) dataset (142 surnames × 142 given names); the
|
|
888
|
+
other types use an independent 10,000-candidate pool. The
|
|
871
889
|
uniqueness-sensitive types (`email`, `username`) additionally embed a 64-bit
|
|
872
890
|
hex token derived from the seed hash, so they stay collision-free under a
|
|
873
891
|
unique index even at millions of rows (collision probability at 5M distinct
|
|
874
892
|
seeds ≈ 7e-7) and always use the `example.com` domain.
|
|
875
|
-
- **Determinism caveat**: values are stable for a given
|
|
876
|
-
|
|
893
|
+
- **Determinism caveat**: values are stable for a given locale plus the version
|
|
894
|
+
of the value source — the faker gem for the non-`ja` name family and the
|
|
895
|
+
independent types, and exwiw's bundled dataset for the `ja` name family.
|
|
896
|
+
Upgrading that source (or changing `locale`) regenerates the pool and maps
|
|
877
897
|
seeds to different values. The seed→value mapping itself never changes within
|
|
878
898
|
one version.
|
|
879
899
|
- The faker gem is **not** a runtime dependency of exwiw — add `gem "faker"` to
|
|
880
|
-
your Gemfile to use this mode (exwiw raises a clear error otherwise).
|
|
900
|
+
your Gemfile to use this mode (exwiw raises a clear error otherwise). A config
|
|
901
|
+
that uses **only** `ja` person types needs no faker (that pool is built
|
|
902
|
+
entirely from the bundled dataset); faker is required for every other type
|
|
903
|
+
and locale.
|
|
881
904
|
- Exclusive with the other masking keys on the same column. SQL adapters only
|
|
882
905
|
(the MongoDB adapter rejects the key on load, see
|
|
883
906
|
[Unknown keys are rejected](#unknown-keys-are-rejected)), and invisible to
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Exwiw
|
|
4
|
+
# Paired (kanji, katakana) Japanese name data for the `ja` locale of the
|
|
5
|
+
# person-family `replace_with_fake_data` types (see {RowTransformer}).
|
|
6
|
+
#
|
|
7
|
+
# faker's `ja` locale ships kanji names only, with no reading, so it cannot
|
|
8
|
+
# produce kana (フリガナ) at all, and its last_name / first_name pools are
|
|
9
|
+
# unrelated to each other. exwiw bundles this paired dataset instead so that:
|
|
10
|
+
#
|
|
11
|
+
# - a fake person carries a kana reading that actually matches its kanji, and
|
|
12
|
+
# - last_name / first_name / full name (and their kana) for a single seed all
|
|
13
|
+
# come from the same person (a coherent identity).
|
|
14
|
+
#
|
|
15
|
+
# The kanji sets extend faker's `ja` name list (its top 100 surnames / 100
|
|
16
|
+
# given names) with common additions; exwiw supplies the readings. 142 x 142
|
|
17
|
+
# = 20,164 combinations exceeds the person pool size
|
|
18
|
+
# (RowTransformer::PERSON_POOL_SIZE = 20,000), so the ja pool is filled with
|
|
19
|
+
# that many DISTINCT people rather than repeating a small subset.
|
|
20
|
+
#
|
|
21
|
+
# Readings are katakana (matching the katakana convention of typical `*_kana`
|
|
22
|
+
# columns). They are plausible common readings, not the only possible reading
|
|
23
|
+
# of each kanji — fake data only needs to look real, not be canonical.
|
|
24
|
+
module JapaneseNames
|
|
25
|
+
SURNAMES = [
|
|
26
|
+
%w[佐藤 サトウ],
|
|
27
|
+
%w[鈴木 スズキ],
|
|
28
|
+
%w[高橋 タカハシ],
|
|
29
|
+
%w[田中 タナカ],
|
|
30
|
+
%w[伊藤 イトウ],
|
|
31
|
+
%w[山本 ヤマモト],
|
|
32
|
+
%w[渡辺 ワタナベ],
|
|
33
|
+
%w[中村 ナカムラ],
|
|
34
|
+
%w[小林 コバヤシ],
|
|
35
|
+
%w[加藤 カトウ],
|
|
36
|
+
%w[吉田 ヨシダ],
|
|
37
|
+
%w[山田 ヤマダ],
|
|
38
|
+
%w[佐々木 ササキ],
|
|
39
|
+
%w[山口 ヤマグチ],
|
|
40
|
+
%w[松本 マツモト],
|
|
41
|
+
%w[井上 イノウエ],
|
|
42
|
+
%w[木村 キムラ],
|
|
43
|
+
%w[林 ハヤシ],
|
|
44
|
+
%w[斎藤 サイトウ],
|
|
45
|
+
%w[清水 シミズ],
|
|
46
|
+
%w[山崎 ヤマザキ],
|
|
47
|
+
%w[阿部 アベ],
|
|
48
|
+
%w[森 モリ],
|
|
49
|
+
%w[池田 イケダ],
|
|
50
|
+
%w[橋本 ハシモト],
|
|
51
|
+
%w[山下 ヤマシタ],
|
|
52
|
+
%w[石川 イシカワ],
|
|
53
|
+
%w[中島 ナカジマ],
|
|
54
|
+
%w[前田 マエダ],
|
|
55
|
+
%w[藤田 フジタ],
|
|
56
|
+
%w[後藤 ゴトウ],
|
|
57
|
+
%w[小川 オガワ],
|
|
58
|
+
%w[岡田 オカダ],
|
|
59
|
+
%w[村上 ムラカミ],
|
|
60
|
+
%w[長谷川 ハセガワ],
|
|
61
|
+
%w[近藤 コンドウ],
|
|
62
|
+
%w[石井 イシイ],
|
|
63
|
+
%w[斉藤 サイトウ],
|
|
64
|
+
%w[坂本 サカモト],
|
|
65
|
+
%w[遠藤 エンドウ],
|
|
66
|
+
%w[藤井 フジイ],
|
|
67
|
+
%w[青木 アオキ],
|
|
68
|
+
%w[福田 フクダ],
|
|
69
|
+
%w[三浦 ミウラ],
|
|
70
|
+
%w[西村 ニシムラ],
|
|
71
|
+
%w[藤原 フジワラ],
|
|
72
|
+
%w[太田 オオタ],
|
|
73
|
+
%w[松田 マツダ],
|
|
74
|
+
%w[原田 ハラダ],
|
|
75
|
+
%w[岡本 オカモト],
|
|
76
|
+
%w[中野 ナカノ],
|
|
77
|
+
%w[中川 ナカガワ],
|
|
78
|
+
%w[小野 オノ],
|
|
79
|
+
%w[田村 タムラ],
|
|
80
|
+
%w[竹内 タケウチ],
|
|
81
|
+
%w[金子 カネコ],
|
|
82
|
+
%w[中山 ナカヤマ],
|
|
83
|
+
%w[和田 ワダ],
|
|
84
|
+
%w[石田 イシダ],
|
|
85
|
+
%w[工藤 クドウ],
|
|
86
|
+
%w[上田 ウエダ],
|
|
87
|
+
%w[原 ハラ],
|
|
88
|
+
%w[森田 モリタ],
|
|
89
|
+
%w[酒井 サカイ],
|
|
90
|
+
%w[横山 ヨコヤマ],
|
|
91
|
+
%w[柴田 シバタ],
|
|
92
|
+
%w[宮崎 ミヤザキ],
|
|
93
|
+
%w[宮本 ミヤモト],
|
|
94
|
+
%w[内田 ウチダ],
|
|
95
|
+
%w[高木 タカギ],
|
|
96
|
+
%w[谷口 タニグチ],
|
|
97
|
+
%w[安藤 アンドウ],
|
|
98
|
+
%w[丸山 マルヤマ],
|
|
99
|
+
%w[今井 イマイ],
|
|
100
|
+
%w[大野 オオノ],
|
|
101
|
+
%w[高田 タカダ],
|
|
102
|
+
%w[菅原 スガワラ],
|
|
103
|
+
%w[河野 コウノ],
|
|
104
|
+
%w[武田 タケダ],
|
|
105
|
+
%w[藤本 フジモト],
|
|
106
|
+
%w[上野 ウエノ],
|
|
107
|
+
%w[杉山 スギヤマ],
|
|
108
|
+
%w[千葉 チバ],
|
|
109
|
+
%w[村田 ムラタ],
|
|
110
|
+
%w[増田 マスダ],
|
|
111
|
+
%w[小島 コジマ],
|
|
112
|
+
%w[小山 コヤマ],
|
|
113
|
+
%w[大塚 オオツカ],
|
|
114
|
+
%w[平野 ヒラノ],
|
|
115
|
+
%w[久保 クボ],
|
|
116
|
+
%w[渡部 ワタナベ],
|
|
117
|
+
%w[松井 マツイ],
|
|
118
|
+
%w[菊地 キクチ],
|
|
119
|
+
%w[岩崎 イワサキ],
|
|
120
|
+
%w[松尾 マツオ],
|
|
121
|
+
%w[佐野 サノ],
|
|
122
|
+
%w[木下 キノシタ],
|
|
123
|
+
%w[野口 ノグチ],
|
|
124
|
+
%w[野村 ノムラ],
|
|
125
|
+
%w[新井 アライ],
|
|
126
|
+
%w[北村 キタムラ],
|
|
127
|
+
%w[平田 ヒラタ],
|
|
128
|
+
%w[森本 モリモト],
|
|
129
|
+
%w[荒木 アラキ],
|
|
130
|
+
%w[大西 オオニシ],
|
|
131
|
+
%w[菅野 カンノ],
|
|
132
|
+
%w[小池 コイケ],
|
|
133
|
+
%w[桜井 サクライ],
|
|
134
|
+
%w[高野 タカノ],
|
|
135
|
+
%w[川口 カワグチ],
|
|
136
|
+
%w[大久保 オオクボ],
|
|
137
|
+
%w[中田 ナカタ],
|
|
138
|
+
%w[川崎 カワサキ],
|
|
139
|
+
%w[飯田 イイダ],
|
|
140
|
+
%w[星野 ホシノ],
|
|
141
|
+
%w[岩田 イワタ],
|
|
142
|
+
%w[大森 オオモリ],
|
|
143
|
+
%w[松岡 マツオカ],
|
|
144
|
+
%w[沢田 サワダ],
|
|
145
|
+
%w[内藤 ナイトウ],
|
|
146
|
+
%w[松浦 マツウラ],
|
|
147
|
+
%w[西田 ニシダ],
|
|
148
|
+
%w[白石 シライシ],
|
|
149
|
+
%w[浜田 ハマダ],
|
|
150
|
+
%w[早川 ハヤカワ],
|
|
151
|
+
%w[永井 ナガイ],
|
|
152
|
+
%w[松村 マツムラ],
|
|
153
|
+
%w[三宅 ミヤケ],
|
|
154
|
+
%w[福島 フクシマ],
|
|
155
|
+
%w[大橋 オオハシ],
|
|
156
|
+
%w[中西 ナカニシ],
|
|
157
|
+
%w[吉川 ヨシカワ],
|
|
158
|
+
%w[服部 ハットリ],
|
|
159
|
+
%w[尾崎 オザキ],
|
|
160
|
+
%w[水野 ミズノ],
|
|
161
|
+
%w[香川 カガワ],
|
|
162
|
+
%w[荒井 アライ],
|
|
163
|
+
%w[田口 タグチ],
|
|
164
|
+
%w[江口 エグチ],
|
|
165
|
+
%w[秋山 アキヤマ],
|
|
166
|
+
%w[黒田 クロダ],
|
|
167
|
+
%w[今村 イマムラ],
|
|
168
|
+
].freeze
|
|
169
|
+
|
|
170
|
+
GIVEN_NAMES = [
|
|
171
|
+
%w[翔太 ショウタ],
|
|
172
|
+
%w[蓮 レン],
|
|
173
|
+
%w[翔 ショウ],
|
|
174
|
+
%w[陸 リク],
|
|
175
|
+
%w[颯太 ソウタ],
|
|
176
|
+
%w[悠斗 ユウト],
|
|
177
|
+
%w[大翔 ヒロト],
|
|
178
|
+
%w[翼 ツバサ],
|
|
179
|
+
%w[樹 イツキ],
|
|
180
|
+
%w[奏太 ソウタ],
|
|
181
|
+
%w[大和 ヤマト],
|
|
182
|
+
%w[大輝 ダイキ],
|
|
183
|
+
%w[悠 ユウ],
|
|
184
|
+
%w[隼人 ハヤト],
|
|
185
|
+
%w[健太 ケンタ],
|
|
186
|
+
%w[大輔 ダイスケ],
|
|
187
|
+
%w[駿 シュン],
|
|
188
|
+
%w[陽斗 ハルト],
|
|
189
|
+
%w[優 ユウ],
|
|
190
|
+
%w[陽 ヨウ],
|
|
191
|
+
%w[悠人 ユウト],
|
|
192
|
+
%w[誠 マコト],
|
|
193
|
+
%w[拓海 タクミ],
|
|
194
|
+
%w[仁 ジン],
|
|
195
|
+
%w[悠太 ユウタ],
|
|
196
|
+
%w[悠真 ユウマ],
|
|
197
|
+
%w[大地 ダイチ],
|
|
198
|
+
%w[健 ケン],
|
|
199
|
+
%w[遼 リョウ],
|
|
200
|
+
%w[大樹 ダイキ],
|
|
201
|
+
%w[諒 リョウ],
|
|
202
|
+
%w[響 ヒビキ],
|
|
203
|
+
%w[太一 タイチ],
|
|
204
|
+
%w[一郎 イチロウ],
|
|
205
|
+
%w[優斗 ユウト],
|
|
206
|
+
%w[亮 リョウ],
|
|
207
|
+
%w[海斗 カイト],
|
|
208
|
+
%w[颯 ソウ],
|
|
209
|
+
%w[亮太 リョウタ],
|
|
210
|
+
%w[匠 タクミ],
|
|
211
|
+
%w[陽太 ヨウタ],
|
|
212
|
+
%w[航 ワタル],
|
|
213
|
+
%w[瑛太 エイタ],
|
|
214
|
+
%w[直樹 ナオキ],
|
|
215
|
+
%w[空 ソラ],
|
|
216
|
+
%w[光 ヒカル],
|
|
217
|
+
%w[太郎 タロウ],
|
|
218
|
+
%w[輝 ヒカル],
|
|
219
|
+
%w[一輝 カズキ],
|
|
220
|
+
%w[蒼 アオイ],
|
|
221
|
+
%w[葵 アオイ],
|
|
222
|
+
%w[優那 ユウナ],
|
|
223
|
+
%w[優奈 ユウナ],
|
|
224
|
+
%w[凛 リン],
|
|
225
|
+
%w[陽菜 ヒナ],
|
|
226
|
+
%w[愛 アイ],
|
|
227
|
+
%w[結衣 ユイ],
|
|
228
|
+
%w[美咲 ミサキ],
|
|
229
|
+
%w[楓 カエデ],
|
|
230
|
+
%w[さくら サクラ],
|
|
231
|
+
%w[遥 ハルカ],
|
|
232
|
+
%w[美優 ミユ],
|
|
233
|
+
%w[莉子 リコ],
|
|
234
|
+
%w[七海 ナナミ],
|
|
235
|
+
%w[美月 ミヅキ],
|
|
236
|
+
%w[結菜 ユナ],
|
|
237
|
+
%w[真央 マオ],
|
|
238
|
+
%w[花音 カノン],
|
|
239
|
+
%w[陽子 ヨウコ],
|
|
240
|
+
%w[舞 マイ],
|
|
241
|
+
%w[美羽 ミウ],
|
|
242
|
+
%w[優衣 ユイ],
|
|
243
|
+
%w[未来 ミク],
|
|
244
|
+
%w[彩 アヤ],
|
|
245
|
+
%w[彩乃 アヤノ],
|
|
246
|
+
%w[彩花 アヤカ],
|
|
247
|
+
%w[智子 トモコ],
|
|
248
|
+
%w[奈々 ナナ],
|
|
249
|
+
%w[千尋 チヒロ],
|
|
250
|
+
%w[愛美 マナミ],
|
|
251
|
+
%w[優菜 ユウナ],
|
|
252
|
+
%w[杏 アン],
|
|
253
|
+
%w[裕子 ユウコ],
|
|
254
|
+
%w[芽衣 メイ],
|
|
255
|
+
%w[綾乃 アヤノ],
|
|
256
|
+
%w[琴音 コトネ],
|
|
257
|
+
%w[桜 サクラ],
|
|
258
|
+
%w[恵 メグミ],
|
|
259
|
+
%w[杏奈 アンナ],
|
|
260
|
+
%w[美桜 ミオ],
|
|
261
|
+
%w[優花 ユウカ],
|
|
262
|
+
%w[玲奈 レナ],
|
|
263
|
+
%w[結 ユイ],
|
|
264
|
+
%w[茜 アカネ],
|
|
265
|
+
%w[美穂 ミホ],
|
|
266
|
+
%w[明日香 アスカ],
|
|
267
|
+
%w[愛子 アイコ],
|
|
268
|
+
%w[美緒 ミオ],
|
|
269
|
+
%w[千夏 チナツ],
|
|
270
|
+
%w[真希 マキ],
|
|
271
|
+
%w[陽向 ヒナタ],
|
|
272
|
+
%w[大介 ダイスケ],
|
|
273
|
+
%w[涼介 リョウスケ],
|
|
274
|
+
%w[悠介 ユウスケ],
|
|
275
|
+
%w[拓也 タクヤ],
|
|
276
|
+
%w[和也 カズヤ],
|
|
277
|
+
%w[直人 ナオト],
|
|
278
|
+
%w[雄太 ユウタ],
|
|
279
|
+
%w[翔平 ショウヘイ],
|
|
280
|
+
%w[健太郎 ケンタロウ],
|
|
281
|
+
%w[慎太郎 シンタロウ],
|
|
282
|
+
%w[竜也 タツヤ],
|
|
283
|
+
%w[圭 ケイ],
|
|
284
|
+
%w[樹里 ジュリ],
|
|
285
|
+
%w[花子 ハナコ],
|
|
286
|
+
%w[由美 ユミ],
|
|
287
|
+
%w[由美子 ユミコ],
|
|
288
|
+
%w[美穂子 ミホコ],
|
|
289
|
+
%w[直子 ナオコ],
|
|
290
|
+
%w[京子 キョウコ],
|
|
291
|
+
%w[順子 ジュンコ],
|
|
292
|
+
%w[洋子 ヨウコ],
|
|
293
|
+
%w[幸子 サチコ],
|
|
294
|
+
%w[美和 ミワ],
|
|
295
|
+
%w[理沙 リサ],
|
|
296
|
+
%w[麻衣 マイ],
|
|
297
|
+
%w[早紀 サキ],
|
|
298
|
+
%w[春香 ハルカ],
|
|
299
|
+
%w[沙織 サオリ],
|
|
300
|
+
%w[香織 カオリ],
|
|
301
|
+
%w[友美 トモミ],
|
|
302
|
+
%w[恵子 ケイコ],
|
|
303
|
+
%w[真由美 マユミ],
|
|
304
|
+
%w[里奈 リナ],
|
|
305
|
+
%w[彩香 アヤカ],
|
|
306
|
+
%w[亜美 アミ],
|
|
307
|
+
%w[春菜 ハルナ],
|
|
308
|
+
%w[千秋 チアキ],
|
|
309
|
+
%w[桃子 モモコ],
|
|
310
|
+
%w[美里 ミサト],
|
|
311
|
+
%w[結子 ユウコ],
|
|
312
|
+
%w[陽介 ヨウスケ],
|
|
313
|
+
].freeze
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
|
+
require_relative "japanese_names"
|
|
6
|
+
|
|
5
7
|
module Exwiw
|
|
6
8
|
# Applies the Ruby-process-side masking modes — `map` and
|
|
7
9
|
# `replace_with_fake_data` — to the rows streamed out of a SQL adapter's
|
|
@@ -31,16 +33,46 @@ module Exwiw
|
|
|
31
33
|
# given faker gem version + locale.
|
|
32
34
|
POOL_RANDOM_SEED = 715_517
|
|
33
35
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
36
|
+
# The person pool (PERSON_TYPES) is sized independently of POOL_SIZE — at
|
|
37
|
+
# twice the size — so the coherent-identity name space is larger without
|
|
38
|
+
# disturbing the independent types' seed->value mappings. The ja pool is
|
|
39
|
+
# filled with distinct people (see .build_japanese_person_pool), so
|
|
40
|
+
# JapaneseNames must supply at least this many (surname, given) combinations.
|
|
41
|
+
PERSON_POOL_SIZE = POOL_SIZE * 2
|
|
42
|
+
|
|
43
|
+
# A coherent fake identity. All person-family types (PERSON_TYPES) draw
|
|
44
|
+
# from one shared pool of these per locale, and a seed picks the same pool
|
|
45
|
+
# index for every such column — so last_name / first_name / full name (and
|
|
46
|
+
# their kana) for one seed all belong to the same person. kana fields are
|
|
47
|
+
# only populated for the `ja` locale (faker has no readings; exwiw bundles
|
|
48
|
+
# a paired dataset — see JapaneseNames); they are nil otherwise.
|
|
49
|
+
Person = Struct.new(:last_name, :first_name, :last_name_kana, :first_name_kana)
|
|
50
|
+
|
|
51
|
+
# Person-family types: they share the per-locale person pool (built by
|
|
52
|
+
# .person_pool), and each extractor derives its value from the picked
|
|
53
|
+
# Person. Full-name ordering is locale-aware ("姓 名" for ja, "First Last"
|
|
54
|
+
# elsewhere). This is what keeps a single seed's name columns consistent.
|
|
55
|
+
PERSON_TYPES = {
|
|
56
|
+
"human_name" => ->(p, locale) { join_full_name(p.last_name, p.first_name, locale) },
|
|
57
|
+
"last_name" => ->(p, _locale) { p.last_name },
|
|
58
|
+
"first_name" => ->(p, _locale) { p.first_name },
|
|
59
|
+
"human_name_kana" => ->(p, locale) { join_full_name(p.last_name_kana, p.first_name_kana, locale) },
|
|
60
|
+
"last_name_kana" => ->(p, _locale) { p.last_name_kana },
|
|
61
|
+
"first_name_kana" => ->(p, _locale) { p.first_name_kana },
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# Person-family types whose value is a kana reading, only available for the
|
|
65
|
+
# `ja` locale (the person pool carries nil kana for other locales, so these
|
|
66
|
+
# are rejected at build time with a clear error).
|
|
67
|
+
KANA_TYPES = %w[human_name_kana last_name_kana first_name_kana].freeze
|
|
68
|
+
|
|
69
|
+
# Independent (non-person) types. `pool` builds one candidate value (called
|
|
70
|
+
# POOL_SIZE times under a seeded Faker random). Types with `compose` are
|
|
71
|
+
# uniqueness-sensitive: the pooled base alone would collide under a unique
|
|
72
|
+
# index at scale, so the final value composes the base with a 64-bit hex
|
|
73
|
+
# token derived from the same seed digest (collision probability at 5M
|
|
74
|
+
# distinct seeds ≈ 7e-7).
|
|
40
75
|
FAKE_TYPES = {
|
|
41
|
-
"human_name" => { pool: -> { Faker::Name.name } },
|
|
42
|
-
"first_name" => { pool: -> { Faker::Name.first_name } },
|
|
43
|
-
"last_name" => { pool: -> { Faker::Name.last_name } },
|
|
44
76
|
"phone_number" => { pool: -> { Faker::PhoneNumber.phone_number } },
|
|
45
77
|
"address" => { pool: -> { Faker::Address.full_address } },
|
|
46
78
|
"company_name" => { pool: -> { Faker::Company.name } },
|
|
@@ -50,6 +82,13 @@ module Exwiw
|
|
|
50
82
|
compose: ->(base, token) { "#{base}_#{token}" } },
|
|
51
83
|
}.freeze
|
|
52
84
|
|
|
85
|
+
# "姓 名" for Japanese, "First Last" otherwise. A nil part (e.g. missing
|
|
86
|
+
# kana) collapses so the result never has a dangling separator.
|
|
87
|
+
def self.join_full_name(last, first, locale)
|
|
88
|
+
parts = locale.to_s == "ja" ? [last, first] : [first, last]
|
|
89
|
+
parts.compact.join(" ")
|
|
90
|
+
end
|
|
91
|
+
|
|
53
92
|
# The `r` a map proc receives: read-only access to the current row's
|
|
54
93
|
# values by column name (`r['id']`). One instance is reused across all
|
|
55
94
|
# rows (zero per-row allocation) — do not retain it outside the proc.
|
|
@@ -118,6 +157,15 @@ module Exwiw
|
|
|
118
157
|
"Add `gem \"faker\"` to your Gemfile (it is not a runtime dependency of exwiw)."
|
|
119
158
|
end
|
|
120
159
|
|
|
160
|
+
# Whether a (type, locale) draws any value from faker. The `ja` person pool
|
|
161
|
+
# is built entirely from exwiw's bundled dataset, so a config that only
|
|
162
|
+
# uses `ja` person types needs no faker at all.
|
|
163
|
+
def self.type_needs_faker?(type, locale)
|
|
164
|
+
return true if FAKE_TYPES.key?(type)
|
|
165
|
+
|
|
166
|
+
PERSON_TYPES.key?(type) && locale.to_s != "ja"
|
|
167
|
+
end
|
|
168
|
+
|
|
121
169
|
# Pools are memoized per (type, locale): every column sharing a type+locale
|
|
122
170
|
# sees the same pool, which is what makes equal seed values map to equal
|
|
123
171
|
# fake values across tables and runs.
|
|
@@ -138,6 +186,51 @@ module Exwiw
|
|
|
138
186
|
Faker::Config.random = previous_random
|
|
139
187
|
end
|
|
140
188
|
|
|
189
|
+
# One pool of coherent Person records per locale, shared by every
|
|
190
|
+
# person-family type. Memoized so equal seeds map to equal people across
|
|
191
|
+
# tables and runs. `ja` is built from exwiw's bundled (kanji, kana) dataset
|
|
192
|
+
# so kana matches kanji; other locales use faker (no kana).
|
|
193
|
+
def self.person_pool(locale)
|
|
194
|
+
@person_pools ||= {}
|
|
195
|
+
@person_pools[locale] ||= build_person_pool(locale)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def self.build_person_pool(locale)
|
|
199
|
+
random = Random.new(POOL_RANDOM_SEED)
|
|
200
|
+
return build_japanese_person_pool(random) if locale.to_s == "ja"
|
|
201
|
+
|
|
202
|
+
previous_locale = Faker::Config.locale
|
|
203
|
+
previous_random = Faker::Config.random
|
|
204
|
+
Faker::Config.locale = locale if locale
|
|
205
|
+
Faker::Config.random = random
|
|
206
|
+
Array.new(PERSON_POOL_SIZE) { Person.new(Faker::Name.last_name, Faker::Name.first_name, nil, nil).freeze }.freeze
|
|
207
|
+
ensure
|
|
208
|
+
unless locale.to_s == "ja"
|
|
209
|
+
Faker::Config.locale = previous_locale
|
|
210
|
+
Faker::Config.random = previous_random
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Fill the ja pool with DISTINCT people: enumerate every (surname, given)
|
|
215
|
+
# combination, shuffle deterministically, and take PERSON_POOL_SIZE. Unlike
|
|
216
|
+
# sampling with replacement (which wastes ~37% of slots to duplicates), this
|
|
217
|
+
# gives PERSON_POOL_SIZE distinct identities — so JapaneseNames must supply
|
|
218
|
+
# at least that many combinations.
|
|
219
|
+
def self.build_japanese_person_pool(random)
|
|
220
|
+
surnames = JapaneseNames::SURNAMES
|
|
221
|
+
given_names = JapaneseNames::GIVEN_NAMES
|
|
222
|
+
combinations = surnames.size * given_names.size
|
|
223
|
+
if combinations < PERSON_POOL_SIZE
|
|
224
|
+
raise "JapaneseNames has #{combinations} (surname, given) combinations, " \
|
|
225
|
+
"need at least PERSON_POOL_SIZE=#{PERSON_POOL_SIZE}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
people = surnames.flat_map do |last|
|
|
229
|
+
given_names.map { |first| Person.new(last[0], first[0], last[1], first[1]).freeze }
|
|
230
|
+
end
|
|
231
|
+
people.shuffle(random: random).first(PERSON_POOL_SIZE).freeze
|
|
232
|
+
end
|
|
233
|
+
|
|
141
234
|
def initialize(table)
|
|
142
235
|
@table_name = table.name
|
|
143
236
|
@name_to_index = {}
|
|
@@ -145,7 +238,11 @@ module Exwiw
|
|
|
145
238
|
@name_to_index.freeze
|
|
146
239
|
@row_accessor = Row.new(@table_name, @name_to_index)
|
|
147
240
|
|
|
148
|
-
|
|
241
|
+
needs_faker = table.columns.any? do |column|
|
|
242
|
+
fd = column.replace_with_fake_data
|
|
243
|
+
fd && self.class.type_needs_faker?(fd.type, fd.locale)
|
|
244
|
+
end
|
|
245
|
+
self.class.require_faker! if needs_faker
|
|
149
246
|
|
|
150
247
|
@transforms = table.columns.each_with_index.filter_map do |column, index|
|
|
151
248
|
if column.map
|
|
@@ -210,11 +307,26 @@ module Exwiw
|
|
|
210
307
|
|
|
211
308
|
private def compile_fake(column, column_index)
|
|
212
309
|
fake_data = column.replace_with_fake_data
|
|
213
|
-
|
|
310
|
+
type = fake_data.type
|
|
311
|
+
unless PERSON_TYPES.key?(type) || FAKE_TYPES.key?(type)
|
|
312
|
+
raise ArgumentError,
|
|
313
|
+
"replace_with_fake_data for column '#{@table_name}.#{column.name}': " \
|
|
314
|
+
"unknown type '#{type}'. Supported: #{(PERSON_TYPES.keys + FAKE_TYPES.keys).join(', ')}"
|
|
315
|
+
end
|
|
214
316
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
317
|
+
seed_index = resolve_seed_index(fake_data, column)
|
|
318
|
+
|
|
319
|
+
if PERSON_TYPES.key?(type)
|
|
320
|
+
compile_person_fake(fake_data, column, column_index, seed_index)
|
|
321
|
+
else
|
|
322
|
+
compile_independent_fake(fake_data, column_index, seed_index)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Re-resolve the seed here, against the effective (post-ignore) columns:
|
|
327
|
+
# load-time validation sees the full column list, so a seed column that was
|
|
328
|
+
# ignore:true is only caught at dump time.
|
|
329
|
+
private def resolve_seed_index(fake_data, column)
|
|
218
330
|
seed_column = fake_data.seed.delete_prefix("#{@table_name}.")
|
|
219
331
|
seed_index = @name_to_index[seed_column]
|
|
220
332
|
unless seed_index
|
|
@@ -223,14 +335,44 @@ module Exwiw
|
|
|
223
335
|
"seed '#{fake_data.seed}' does not resolve to an extracted column " \
|
|
224
336
|
"(is it ignore:true?)"
|
|
225
337
|
end
|
|
338
|
+
seed_index
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Person-family type: pick a coherent Person from the per-locale pool. The
|
|
342
|
+
# digest[0,8] index is shared with every other person column, so one seed's
|
|
343
|
+
# name columns all belong to the same person.
|
|
344
|
+
private def compile_person_fake(fake_data, column, column_index, seed_index)
|
|
345
|
+
type = fake_data.type
|
|
346
|
+
locale = fake_data.locale
|
|
347
|
+
pool = self.class.person_pool(locale)
|
|
348
|
+
extractor = PERSON_TYPES.fetch(type)
|
|
349
|
+
|
|
350
|
+
if KANA_TYPES.include?(type) && pool.first.last_name_kana.nil?
|
|
351
|
+
raise ArgumentError,
|
|
352
|
+
"replace_with_fake_data for column '#{@table_name}.#{column.name}': " \
|
|
353
|
+
"type '#{type}' needs kana readings, which are only available with locale: ja " \
|
|
354
|
+
"(got locale: #{locale.inspect})"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# NULL-preserving like replace_with: a NULL target stays NULL.
|
|
358
|
+
lambda do |row|
|
|
359
|
+
next nil if row[column_index].nil?
|
|
360
|
+
|
|
361
|
+
digest = Digest::SHA256.digest(row[seed_index].to_s)
|
|
362
|
+
person = pool[digest[0, 8].unpack1("Q>") % PERSON_POOL_SIZE]
|
|
363
|
+
extractor.call(person, locale)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
226
366
|
|
|
367
|
+
# Independent type: pick a value from its own (type, locale) pool.
|
|
368
|
+
private def compile_independent_fake(fake_data, column_index, seed_index)
|
|
227
369
|
pool = self.class.fake_pool(fake_data.type, fake_data.locale)
|
|
228
|
-
compose =
|
|
370
|
+
compose = FAKE_TYPES.fetch(fake_data.type)[:compose]
|
|
229
371
|
|
|
230
|
-
# NULL-preserving like replace_with: a NULL target stays NULL. A nil
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
372
|
+
# NULL-preserving like replace_with: a NULL target stays NULL. A nil seed
|
|
373
|
+
# value hashes "" (deterministic). Seed values are normalized with to_s so
|
|
374
|
+
# sqlite's native Integer 123 and pg/mysql's string "123" pick the same
|
|
375
|
+
# fake value.
|
|
234
376
|
lambda do |row|
|
|
235
377
|
next nil if row[column_index].nil?
|
|
236
378
|
|
data/lib/exwiw/table_config.rb
CHANGED
|
@@ -253,10 +253,11 @@ module Exwiw
|
|
|
253
253
|
fake_data = column.replace_with_fake_data
|
|
254
254
|
return unless fake_data
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
supported_types = RowTransformer::PERSON_TYPES.keys + RowTransformer::FAKE_TYPES.keys
|
|
257
|
+
unless supported_types.include?(fake_data.type)
|
|
257
258
|
raise ArgumentError,
|
|
258
259
|
"Table '#{name}' column '#{column.name}': unknown replace_with_fake_data type '#{fake_data.type}' " \
|
|
259
|
-
"(supported: #{
|
|
260
|
+
"(supported: #{supported_types.join(', ')})."
|
|
260
261
|
end
|
|
261
262
|
|
|
262
263
|
seed_column = fake_data.seed.delete_prefix("#{name}.")
|
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.9.
|
|
4
|
+
version: 0.9.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shia
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- lib/exwiw/explain_runner.rb
|
|
72
72
|
- lib/exwiw/ext_json.rb
|
|
73
73
|
- lib/exwiw/fake_data.rb
|
|
74
|
+
- lib/exwiw/japanese_names.rb
|
|
74
75
|
- lib/exwiw/mongo_query.rb
|
|
75
76
|
- lib/exwiw/mongodb_collection_config.rb
|
|
76
77
|
- lib/exwiw/mongodb_field.rb
|