|
| 1 | +-- Google sağlayıcısı ve insan/ajan handle'larının tek havuzda buluşması. |
| 2 | +-- |
| 3 | +-- İki iş bir arada, çünkü ikincisi birincisinin sonucu: Google'da kullanıcı |
| 4 | +-- adı yok. Bugüne kadar hesabın handle'ı GitHub kullanıcı adından türüyordu |
| 5 | +-- ve o ad zaten GitHub'da tekildi. Google'a geçince handle'ı kullanıcı |
| 6 | +-- seçecek — yani insan handle'ları ilk kez serbest bir alandan geliyor ve |
| 7 | +-- ajan handle'larıyla aynı ismi taşıyabiliyor. |
| 8 | + |
| 9 | +PRAGMA foreign_keys = ON; |
| 10 | + |
| 11 | +-- 1. auth_identities: sağlayıcı listesi genişliyor. |
| 12 | +-- |
| 13 | +-- `CHECK (provider = 'github')` SQLite'ta yerinde gevşetilemiyor; kısıtı |
| 14 | +-- değiştirmenin tek yolu tabloyu yeniden kurmak. Bu repoda ilk tablo yeniden |
| 15 | +-- kurulumu, o yüzden sırası önemli. |
| 16 | +-- |
| 17 | +-- Yeniden kurulan tablo bir ÇOCUK: accounts'a referans veriyor ama ona |
| 18 | +-- referans veren başka tablo yok. Bu yüzden DROP + RENAME sırasında başka |
| 19 | +-- hiçbir tablonun yabancı anahtarı yeniden yazılmıyor ve `foreign_keys` |
| 20 | +-- açıkken yapmak güvenli. Referans veren bir tablo olsaydı bu göç böyle |
| 21 | +-- yazılamazdı. |
| 22 | +-- |
| 23 | +-- 'github' listede kalıyor, çünkü göç henüz bitmedi: mevcut hesaplar |
| 24 | +-- GitHub'la girip Google kimliğini kendi oturumlarında bağlayacak. İki |
| 25 | +-- sağlayıcı bir arada durmak kalıcı bir karar değil, geçişin mekanizması. |
| 26 | +-- Bağlama tamamlanınca kısıt 'google'a daraltılacak ve bu satırlar silinecek. |
| 27 | +CREATE TABLE auth_identities_rebuilt ( |
| 28 | + id TEXT PRIMARY KEY, |
| 29 | + account_id TEXT NOT NULL REFERENCES accounts(id), |
| 30 | + provider TEXT NOT NULL CHECK (provider IN ('github', 'google')), |
| 31 | + provider_user_id TEXT NOT NULL, |
| 32 | + provider_login_snapshot TEXT NOT NULL, |
| 33 | + created_at INTEGER NOT NULL, |
| 34 | + last_seen_at INTEGER NOT NULL, |
| 35 | + provider_email_snapshot TEXT, |
| 36 | + UNIQUE (provider, provider_user_id), |
| 37 | + UNIQUE (account_id, provider) |
| 38 | +); |
| 39 | + |
| 40 | +INSERT INTO auth_identities_rebuilt ( |
| 41 | + id, account_id, provider, provider_user_id, provider_login_snapshot, |
| 42 | + created_at, last_seen_at, provider_email_snapshot |
| 43 | +) |
| 44 | +SELECT |
| 45 | + id, account_id, provider, provider_user_id, provider_login_snapshot, |
| 46 | + created_at, last_seen_at, provider_email_snapshot |
| 47 | +FROM auth_identities; |
| 48 | + |
| 49 | +DROP TABLE auth_identities; |
| 50 | + |
| 51 | +ALTER TABLE auth_identities_rebuilt RENAME TO auth_identities; |
| 52 | + |
| 53 | +-- 2. accounts.handle_skeleton. |
| 54 | +-- |
| 55 | +-- 0037 bunu `agents` için yazdı; gerekçesi orada duruyor ve aynen geçerli: |
| 56 | +-- `handle_normalized` bir ARAMA ANAHTARI, iskelet ise `nyxx` ile `nyx`i |
| 57 | +-- kasten aynı değere indiren kayıplı bir form. İkisi ayrı sütun olmalı. |
| 58 | +-- |
| 59 | +-- Bugüne kadar insan tarafında gerekmiyordu çünkü handle'ı insan seçmiyordu. |
| 60 | +ALTER TABLE accounts ADD COLUMN handle_skeleton TEXT; |
| 61 | + |
| 62 | +-- Geri doldurma 0037'deki dönüşümün aynısı: tireleri at, rakamları harfe |
| 63 | +-- eşle, ardışık tekrarları daralt. İlk ikisi `replace()`, üçüncüsü SQL'de düz |
| 64 | +-- bir ifade olmadığı için özyinelemeli CTE dizeyi karakter karakter yürüyor. |
| 65 | +-- |
| 66 | +-- Harici betiğe bırakılmamasının sebebi de 0037'deki ile aynı: sütun bir süre |
| 67 | +-- NULL kalsaydı, SQLite tekil indekste NULL'ları çakıştırmadığı için koruma o |
| 68 | +-- aralıkta hiç çalışmazdı ve betiği unutmak kapıyı sessizce açık bırakırdı. |
| 69 | +WITH RECURSIVE |
| 70 | +mapped(id, source) AS ( |
| 71 | + SELECT id, replace(replace(replace(replace(replace(replace(replace( |
| 72 | + handle_normalized, '-', ''), '0', 'o'), '1', 'i'), '3', 'e'), '4', 'a'), '5', 's'), '7', 't') |
| 73 | + FROM accounts |
| 74 | +), |
| 75 | +walk(id, rest, acc) AS ( |
| 76 | + SELECT id, source, '' FROM mapped |
| 77 | + UNION ALL |
| 78 | + SELECT id, substr(rest, 2), |
| 79 | + CASE WHEN acc <> '' AND substr(acc, -1, 1) = substr(rest, 1, 1) |
| 80 | + THEN acc |
| 81 | + ELSE acc || substr(rest, 1, 1) |
| 82 | + END |
| 83 | + FROM walk WHERE rest <> '' |
| 84 | +) |
| 85 | +UPDATE accounts SET handle_skeleton = ( |
| 86 | + SELECT acc FROM walk WHERE walk.id = accounts.id AND walk.rest = '' |
| 87 | +); |
| 88 | + |
| 89 | +CREATE UNIQUE INDEX accounts_handle_skeleton_unique ON accounts (handle_skeleton); |
| 90 | + |
| 91 | +CREATE TRIGGER accounts_handle_skeleton_is_required_insert |
| 92 | +BEFORE INSERT ON accounts |
| 93 | +WHEN NEW.handle_skeleton IS NULL OR NEW.handle_skeleton = '' |
| 94 | +BEGIN |
| 95 | + SELECT RAISE(ABORT, 'account_handle_skeleton_required'); |
| 96 | +END; |
| 97 | + |
| 98 | +CREATE TRIGGER accounts_handle_skeleton_is_required_update |
| 99 | +BEFORE UPDATE OF handle_skeleton ON accounts |
| 100 | +WHEN NEW.handle_skeleton IS NULL OR NEW.handle_skeleton = '' |
| 101 | +BEGIN |
| 102 | + SELECT RAISE(ABORT, 'account_handle_skeleton_required'); |
| 103 | +END; |
| 104 | + |
| 105 | +-- 3. Tek havuz. |
| 106 | +-- |
| 107 | +-- İki tabloda iki ayrı tekil indeks, iki ayrı havuz demekti: `nyx` adlı bir |
| 108 | +-- ajan varken `nyx` adlı bir insan da olabilirdi. İkisi aynı sayfada yan yana |
| 109 | +-- görünüyor — ajan profilindeki "İnsanı" kartı — yani bu, ürünün en çok |
| 110 | +-- karışıklık üretebileceği yerde iki farklı varlığa aynı adı vermek olurdu. |
| 111 | +-- |
| 112 | +-- Önek ayrımı (`a-nyx` / `h-nyx`) reddedildi. Handle burada sıradan bir |
| 113 | +-- kullanıcı adı değil: `display_name` zorla handle'a eşitleniyor ve handle |
| 114 | +-- değiştirilemiyor, yani önek ajanın tüm görünen kimliğine kalıcı olarak |
| 115 | +-- yapışırdı. Üstelik ayrımı gerçekten kurmuyor — `a-nyx` de `h-nyx` de |
| 116 | +-- konuşulduğunda "nyx". |
| 117 | +-- |
| 118 | +-- Tekil indeks tek tabloya bakabildiği için havuz trigger'la birleşiyor. |
| 119 | +-- Uygulama katmanında da kontrol var; buradaki onun yerine geçmiyor, altına |
| 120 | +-- konuyor. Aynı anda gelen iki istek "önce oku sonra yaz" kontrolünü ikisi de |
| 121 | +-- geçebilir; yarışı veritabanı kesiyor. |
| 122 | +-- |
| 123 | +-- Karantina (`handle_quarantine`) bilerek buraya alınmadı. Ajan tarafında |
| 124 | +-- karantina kontrolü uygulamada yapılıyor ve 0038'in zorla yeniden adlandırma |
| 125 | +-- akışı geçici handle'larla çalışıyor; o akışın altına bir trigger koymak |
| 126 | +-- moderasyon kolunu kendi kurduğu duruma çarptırabilirdi. Karantina her iki |
| 127 | +-- tarafta da uygulama katmanında kontrol ediliyor. |
| 128 | +-- Trigger'lar yalnız BUNDAN SONRAKİ yazmaları görüyor; göç anında zaten yan |
| 129 | +-- yana duran bir çakışma onlara hiç uğramaz ve havuz daha ilk günden bozuk |
| 130 | +-- başlardı. Aşağıdaki geçici tablo o durumu göçün kendisinde yakalıyor: |
| 131 | +-- çakışma varsa CHECK düşer, migration yarıda durur ve hangi ismin çakıştığı |
| 132 | +-- elle bakılacak bir soru olarak kalır. Sessizce devam etmekten iyi. |
| 133 | +-- |
| 134 | +-- SQLite'ta trigger dışında RAISE yok; kısıtı bir CHECK'e taşımak, "koşul |
| 135 | +-- tutmuyorsa göç ilerlemesin" demenin buradaki tek düz yolu. |
| 136 | +CREATE TABLE handle_pool_precondition (ok INTEGER NOT NULL CHECK (ok = 1)); |
| 137 | + |
| 138 | +INSERT INTO handle_pool_precondition (ok) |
| 139 | +SELECT CASE WHEN EXISTS ( |
| 140 | + SELECT 1 FROM accounts a JOIN agents g ON g.handle_skeleton = a.handle_skeleton |
| 141 | +) THEN 0 ELSE 1 END; |
| 142 | + |
| 143 | +DROP TABLE handle_pool_precondition; |
| 144 | + |
| 145 | +CREATE TRIGGER accounts_handle_skeleton_is_shared_with_agents_insert |
| 146 | +BEFORE INSERT ON accounts |
| 147 | +WHEN EXISTS (SELECT 1 FROM agents WHERE handle_skeleton = NEW.handle_skeleton) |
| 148 | +BEGIN |
| 149 | + SELECT RAISE(ABORT, 'handle_taken'); |
| 150 | +END; |
| 151 | + |
| 152 | +CREATE TRIGGER accounts_handle_skeleton_is_shared_with_agents_update |
| 153 | +BEFORE UPDATE OF handle_skeleton ON accounts |
| 154 | +WHEN EXISTS (SELECT 1 FROM agents WHERE handle_skeleton = NEW.handle_skeleton) |
| 155 | +BEGIN |
| 156 | + SELECT RAISE(ABORT, 'handle_taken'); |
| 157 | +END; |
| 158 | + |
| 159 | +CREATE TRIGGER agents_handle_skeleton_is_shared_with_accounts_insert |
| 160 | +BEFORE INSERT ON agents |
| 161 | +WHEN EXISTS (SELECT 1 FROM accounts WHERE handle_skeleton = NEW.handle_skeleton) |
| 162 | +BEGIN |
| 163 | + SELECT RAISE(ABORT, 'handle_taken'); |
| 164 | +END; |
| 165 | + |
| 166 | +CREATE TRIGGER agents_handle_skeleton_is_shared_with_accounts_update |
| 167 | +BEFORE UPDATE OF handle_skeleton ON agents |
| 168 | +WHEN EXISTS (SELECT 1 FROM accounts WHERE handle_skeleton = NEW.handle_skeleton) |
| 169 | +BEGIN |
| 170 | + SELECT RAISE(ABORT, 'handle_taken'); |
| 171 | +END; |
0 commit comments