Mache Charakterentwicklungsmigration wiederholbar
All checks were successful
Deploy to production / deploy (push) Successful in 2m26s

This commit is contained in:
Torsten Schulz (local)
2026-11-04 14:37:11 +01:00
parent 343bf64115
commit 8e8e92aa62
3 changed files with 51 additions and 10 deletions

View File

@@ -11,6 +11,8 @@ Werte für bestehende Charaktere. Ein Datenbanktrigger initialisiert sie auch f
neue Charaktere, einschließlich der direkten SQL-Inserts des Daemons für NPCs neue Charaktere, einschließlich der direkten SQL-Inserts des Daemons für NPCs
und Neugeborene. Normale Updates, auch das Übergeben von NULL, würfeln bereits und Neugeborene. Normale Updates, auch das Übergeben von NULL, würfeln bereits
gespeicherte Werte nicht neu aus. Explizite gültige Änderungen bleiben möglich. gespeicherte Werte nicht neu aus. Explizite gültige Änderungen bleiben möglich.
Die Migration kann auch nach manueller Vorbereitung der Spalten erneut laufen:
Vorhandene Werte bleiben erhalten, fehlende Spalten und Werte werden ergänzt.
Das vereinfachte Spielmodell verwendet folgende Verteilung: Das vereinfachte Spielmodell verwendet folgende Verteilung:

View File

@@ -5,13 +5,13 @@ module.exports = {
await queryInterface.sequelize.transaction(async (transaction) => { await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.sequelize.query(` await queryInterface.sequelize.query(`
ALTER TABLE falukant_data.character ALTER TABLE falukant_data.character
ADD COLUMN puberty_start_age SMALLINT, ADD COLUMN IF NOT EXISTS puberty_start_age SMALLINT,
ADD COLUMN fertile_from_age SMALLINT; ADD COLUMN IF NOT EXISTS fertile_from_age SMALLINT;
-- Shared by Sequelize and raw daemon INSERTs, including newborns and NPCs. -- Shared by Sequelize and raw daemon INSERTs, including newborns and NPCs.
-- One real calendar day is one game year. These are game distributions, -- One real calendar day is one game year. These are game distributions,
-- not medical predictions or reconstructed medieval population data. -- not medical predictions or reconstructed medieval population data.
CREATE FUNCTION falukant_data.initialize_character_development() CREATE OR REPLACE FUNCTION falukant_data.initialize_character_development()
RETURNS trigger LANGUAGE plpgsql AS $$ RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN BEGIN
IF TG_OP = 'UPDATE' THEN IF TG_OP = 'UPDATE' THEN
@@ -30,21 +30,35 @@ module.exports = {
END; END;
$$; $$;
DROP TRIGGER IF EXISTS initialize_character_development ON falukant_data.character;
CREATE TRIGGER initialize_character_development CREATE TRIGGER initialize_character_development
BEFORE INSERT OR UPDATE ON falukant_data.character BEFORE INSERT OR UPDATE ON falukant_data.character
FOR EACH ROW EXECUTE FUNCTION falukant_data.initialize_character_development(); FOR EACH ROW EXECUTE FUNCTION falukant_data.initialize_character_development();
-- Existing pregnancies and relationships are deliberately untouched. -- Existing pregnancies and relationships are deliberately untouched.
UPDATE falukant_data.character SET puberty_start_age = puberty_start_age; UPDATE falukant_data.character SET puberty_start_age = puberty_start_age
WHERE puberty_start_age IS NULL OR fertile_from_age IS NULL;
ALTER TABLE falukant_data.character ALTER TABLE falukant_data.character
ALTER COLUMN puberty_start_age SET NOT NULL, ALTER COLUMN puberty_start_age SET NOT NULL,
ALTER COLUMN fertile_from_age SET NOT NULL, ALTER COLUMN fertile_from_age SET NOT NULL;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conrelid = 'falukant_data.character'::regclass
AND conname = 'character_development_ages_valid'
) THEN
ALTER TABLE falukant_data.character
ADD CONSTRAINT character_development_ages_valid CHECK ( ADD CONSTRAINT character_development_ages_valid CHECK (
puberty_start_age BETWEEN 8 AND 18 puberty_start_age BETWEEN 8 AND 18
AND fertile_from_age > puberty_start_age AND fertile_from_age > puberty_start_age
AND fertile_from_age <= 21 AND fertile_from_age <= 21
); );
END IF;
END;
$$;
`, { transaction }); `, { transaction });
}); });
}, },

View File

@@ -71,6 +71,31 @@ test('database migration, persistence and daemon conception paths', {
await assert.rejects(db.query(`INSERT INTO falukant_data.character await assert.rejects(db.query(`INSERT INTO falukant_data.character
(puberty_start_age, fertile_from_age) VALUES (12, 11)`)); (puberty_start_age, fertile_from_age) VALUES (12, 11)`));
await t.test('repeating the migration preserves all existing character data', async () => {
const [saved] = await db.query('SELECT * FROM falukant_data.character ORDER BY id');
await migration.up(qi);
const [repeated] = await db.query('SELECT * FROM falukant_data.character ORDER BY id');
assert.deepEqual(repeated, saved);
});
for (const bothColumns of [true, false]) {
await t.test(`manually prepared schema with ${bothColumns ? 'both columns' : 'one column'}`, async () => {
await migration.down(qi);
await db.query(`ALTER TABLE falukant_data.character ADD COLUMN puberty_start_age SMALLINT`);
if (bothColumns) {
await db.query(`ALTER TABLE falukant_data.character ADD COLUMN fertile_from_age SMALLINT;
UPDATE falukant_data.character SET fertile_from_age = 13 WHERE id = 1`);
}
await db.query(`UPDATE falukant_data.character SET puberty_start_age = 10 WHERE id = 1`);
await migration.up(qi);
const [characters] = await db.query('SELECT * FROM falukant_data.character ORDER BY id');
assert.equal(characters[0].puberty_start_age, 10);
if (bothColumns) assert.equal(characters[0].fertile_from_age, 13);
assert.deepEqual(characters[0].pregnancy_due_at, before[0].pregnancy_due_at);
assert.ok(characters.every(c => c.fertile_from_age > c.puberty_start_age));
});
}
await t.test('daemon checks both parents and preserves already scheduled births', { await t.test('daemon checks both parents and preserves already scheduled births', {
skip: !process.env.DAEMON_SQL_PATH, skip: !process.env.DAEMON_SQL_PATH,
}, async () => { }, async () => {