Refactor SQL queries into a dedicated module
- Moved SQL queries from multiple worker files into `src/worker/sql.rs` for better organization and maintainability. - Updated references in `stockage_manager.rs`, `transport.rs`, `underground.rs`, `user_character.rs`, and `value_recalculation.rs` to use the new centralized SQL queries. - Improved code readability by replacing `.get(0)` with `.first()` for better clarity when retrieving the first row from query results. - Cleaned up unnecessary comments and consolidated related SQL queries.
This commit is contained in:
@@ -6,6 +6,23 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::base::{BaseWorker, Worker, WorkerState};
|
||||
use crate::worker::sql::{
|
||||
QUERY_COUNT_OFFICES_PER_REGION,
|
||||
QUERY_FIND_OFFICE_GAPS,
|
||||
QUERY_SELECT_NEEDED_ELECTIONS,
|
||||
QUERY_INSERT_CANDIDATES,
|
||||
QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES,
|
||||
QUERY_PROCESS_EXPIRED_AND_FILL,
|
||||
QUERY_USERS_IN_CITIES_OF_REGIONS,
|
||||
QUERY_NOTIFY_OFFICE_EXPIRATION,
|
||||
QUERY_NOTIFY_ELECTION_CREATED,
|
||||
QUERY_NOTIFY_OFFICE_FILLED,
|
||||
QUERY_GET_USERS_WITH_EXPIRING_OFFICES,
|
||||
QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS,
|
||||
QUERY_GET_USERS_WITH_FILLED_OFFICES,
|
||||
QUERY_PROCESS_ELECTIONS,
|
||||
QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
|
||||
};
|
||||
|
||||
pub struct PoliticsWorker {
|
||||
base: BaseWorker,
|
||||
@@ -42,466 +59,6 @@ struct Office {
|
||||
|
||||
// --- SQL-Konstanten (1:1 aus politics_worker.h übernommen) ------------------
|
||||
|
||||
const QUERY_COUNT_OFFICES_PER_REGION: &str = r#"
|
||||
WITH
|
||||
seats_per_region AS (
|
||||
SELECT
|
||||
pot.id AS office_type_id,
|
||||
rt.id AS region_id,
|
||||
pot.seats_per_region AS seats_total
|
||||
FROM falukant_type.political_office_type AS pot
|
||||
JOIN falukant_type.region AS rt
|
||||
ON pot.region_type = rt.label_tr
|
||||
),
|
||||
occupied AS (
|
||||
SELECT
|
||||
po.office_type_id,
|
||||
po.region_id,
|
||||
COUNT(*) AS occupied_count
|
||||
FROM falukant_data.political_office AS po
|
||||
GROUP BY po.office_type_id, po.region_id
|
||||
),
|
||||
combined AS (
|
||||
SELECT
|
||||
spr.region_id,
|
||||
spr.seats_total AS required_count,
|
||||
COALESCE(o.occupied_count, 0) AS occupied_count
|
||||
FROM seats_per_region AS spr
|
||||
LEFT JOIN occupied AS o
|
||||
ON spr.office_type_id = o.office_type_id
|
||||
AND spr.region_id = o.region_id
|
||||
)
|
||||
SELECT
|
||||
region_id,
|
||||
SUM(required_count) AS required_count,
|
||||
SUM(occupied_count) AS occupied_count
|
||||
FROM combined
|
||||
GROUP BY region_id;
|
||||
"#;
|
||||
|
||||
/// Findet alle Kombinationen aus Amtstyp und Region, für die laut
|
||||
/// `seats_per_region` mehr Sitze existieren sollten, als aktuell in
|
||||
/// `falukant_data.political_office` belegt sind. Es werden ausschließlich
|
||||
/// positive Differenzen (Gaps) zurückgegeben – wenn `occupied > required`
|
||||
/// (z. B. nach Reduktion der Sitzzahl), wird **nichts** gelöscht und die
|
||||
/// Kombination erscheint hier nicht.
|
||||
const QUERY_FIND_OFFICE_GAPS: &str = r#"
|
||||
WITH
|
||||
seats AS (
|
||||
SELECT
|
||||
pot.id AS office_type_id,
|
||||
rt.id AS region_id,
|
||||
pot.seats_per_region AS seats_total
|
||||
FROM falukant_type.political_office_type AS pot
|
||||
JOIN falukant_type.region AS rt
|
||||
ON pot.region_type = rt.label_tr
|
||||
),
|
||||
occupied AS (
|
||||
SELECT
|
||||
po.office_type_id,
|
||||
po.region_id,
|
||||
COUNT(*) AS occupied_count
|
||||
FROM falukant_data.political_office AS po
|
||||
GROUP BY po.office_type_id, po.region_id
|
||||
)
|
||||
SELECT
|
||||
s.office_type_id,
|
||||
s.region_id,
|
||||
(s.seats_total - COALESCE(o.occupied_count, 0)) AS gaps
|
||||
FROM seats AS s
|
||||
LEFT JOIN occupied AS o
|
||||
ON s.office_type_id = o.office_type_id
|
||||
AND s.region_id = o.region_id
|
||||
WHERE (s.seats_total - COALESCE(o.occupied_count, 0)) > 0;
|
||||
"#;
|
||||
|
||||
const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#"
|
||||
WITH
|
||||
target_date AS (
|
||||
SELECT NOW()::date AS election_date
|
||||
),
|
||||
expired_today AS (
|
||||
DELETE FROM falukant_data.political_office AS po
|
||||
USING falukant_type.political_office_type AS pot
|
||||
WHERE po.office_type_id = pot.id
|
||||
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))::date
|
||||
= (SELECT election_date FROM target_date)
|
||||
RETURNING
|
||||
pot.id AS office_type_id,
|
||||
po.region_id AS region_id
|
||||
),
|
||||
gaps_per_region AS (
|
||||
SELECT
|
||||
office_type_id,
|
||||
region_id,
|
||||
COUNT(*) AS gaps
|
||||
FROM expired_today
|
||||
GROUP BY office_type_id, region_id
|
||||
),
|
||||
to_schedule AS (
|
||||
SELECT
|
||||
g.office_type_id,
|
||||
g.region_id,
|
||||
g.gaps,
|
||||
td.election_date
|
||||
FROM gaps_per_region AS g
|
||||
CROSS JOIN target_date AS td
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM falukant_data.election AS e
|
||||
WHERE e.office_type_id = g.office_type_id
|
||||
AND e.region_id = g.region_id
|
||||
AND e.date::date = td.election_date
|
||||
)
|
||||
),
|
||||
new_elections AS (
|
||||
INSERT INTO falukant_data.election
|
||||
(office_type_id, date, posts_to_fill, created_at, updated_at, region_id)
|
||||
SELECT
|
||||
ts.office_type_id,
|
||||
ts.election_date,
|
||||
ts.gaps,
|
||||
NOW(),
|
||||
NOW(),
|
||||
ts.region_id
|
||||
FROM to_schedule AS ts
|
||||
RETURNING
|
||||
id AS election_id,
|
||||
region_id,
|
||||
posts_to_fill
|
||||
)
|
||||
SELECT
|
||||
ne.election_id,
|
||||
ne.region_id,
|
||||
ne.posts_to_fill
|
||||
FROM new_elections AS ne
|
||||
ORDER BY ne.region_id, ne.election_id;
|
||||
"#;
|
||||
|
||||
const QUERY_INSERT_CANDIDATES: &str = r#"
|
||||
INSERT INTO falukant_data.candidate
|
||||
(election_id, character_id, created_at, updated_at)
|
||||
SELECT
|
||||
$1 AS election_id,
|
||||
sub.id AS character_id,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at
|
||||
FROM (
|
||||
WITH RECURSIVE region_tree AS (
|
||||
SELECT r.id
|
||||
FROM falukant_data.region AS r
|
||||
WHERE r.id = $2
|
||||
UNION ALL
|
||||
SELECT r2.id
|
||||
FROM falukant_data.region AS r2
|
||||
JOIN region_tree AS rt
|
||||
ON r2.parent_id = rt.id
|
||||
)
|
||||
SELECT ch.id
|
||||
FROM falukant_data.character AS ch
|
||||
JOIN region_tree AS rt2
|
||||
ON ch.region_id = rt2.id
|
||||
WHERE ch.user_id IS NULL
|
||||
AND ch.birthdate <= NOW() - INTERVAL '21 days'
|
||||
AND ch.title_of_nobility IN (
|
||||
SELECT id
|
||||
FROM falukant_type.title
|
||||
WHERE label_tr != 'noncivil'
|
||||
)
|
||||
ORDER BY RANDOM()
|
||||
LIMIT ($3 * 2)
|
||||
) AS sub(id);
|
||||
"#;
|
||||
|
||||
/// Wahlen finden, für die noch keine Kandidaten existieren.
|
||||
/// Das umfasst sowohl frisch eingetragene Wahlen als auch manuell
|
||||
/// angelegte Wahlen, solange:
|
||||
/// - das Wahl-Datum heute oder in der Zukunft liegt und
|
||||
/// - noch kein Eintrag in `falukant_data.candidate` existiert.
|
||||
const QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES: &str = r#"
|
||||
SELECT
|
||||
e.id AS election_id,
|
||||
e.region_id AS region_id,
|
||||
e.posts_to_fill
|
||||
FROM falukant_data.election AS e
|
||||
WHERE e.region_id IS NOT NULL
|
||||
AND e.posts_to_fill > 0
|
||||
AND e.date::date >= CURRENT_DATE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM falukant_data.candidate AS c
|
||||
WHERE c.election_id = e.id
|
||||
);
|
||||
"#;
|
||||
|
||||
const QUERY_PROCESS_EXPIRED_AND_FILL: &str = r#"
|
||||
WITH
|
||||
expired_offices AS (
|
||||
DELETE FROM falukant_data.political_office AS po
|
||||
USING falukant_type.political_office_type AS pot
|
||||
WHERE po.office_type_id = pot.id
|
||||
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) <= NOW()
|
||||
RETURNING
|
||||
pot.id AS office_type_id,
|
||||
po.region_id AS region_id
|
||||
),
|
||||
distinct_types AS (
|
||||
SELECT DISTINCT office_type_id, region_id FROM expired_offices
|
||||
),
|
||||
votes_per_candidate AS (
|
||||
SELECT
|
||||
dt.office_type_id,
|
||||
dt.region_id,
|
||||
c.character_id,
|
||||
COUNT(v.id) AS vote_count
|
||||
FROM distinct_types AS dt
|
||||
JOIN falukant_data.election AS e
|
||||
ON e.office_type_id = dt.office_type_id
|
||||
JOIN falukant_data.vote AS v
|
||||
ON v.election_id = e.id
|
||||
JOIN falukant_data.candidate AS c
|
||||
ON c.election_id = e.id
|
||||
AND c.id = v.candidate_id
|
||||
WHERE e.date >= (NOW() - INTERVAL '30 days')
|
||||
GROUP BY dt.office_type_id, dt.region_id, c.character_id
|
||||
),
|
||||
ranked_winners AS (
|
||||
SELECT
|
||||
vpc.office_type_id,
|
||||
vpc.region_id,
|
||||
vpc.character_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY vpc.office_type_id, vpc.region_id
|
||||
ORDER BY vpc.vote_count DESC
|
||||
) AS rn
|
||||
FROM votes_per_candidate AS vpc
|
||||
),
|
||||
selected_winners AS (
|
||||
SELECT
|
||||
rw.office_type_id,
|
||||
rw.region_id,
|
||||
rw.character_id
|
||||
FROM ranked_winners AS rw
|
||||
JOIN falukant_type.political_office_type AS pot
|
||||
ON pot.id = rw.office_type_id
|
||||
WHERE rw.rn <= pot.seats_per_region
|
||||
),
|
||||
insert_winners AS (
|
||||
INSERT INTO falukant_data.political_office
|
||||
(office_type_id, character_id, created_at, updated_at, region_id)
|
||||
SELECT
|
||||
sw.office_type_id,
|
||||
sw.character_id,
|
||||
NOW(),
|
||||
NOW(),
|
||||
sw.region_id
|
||||
FROM selected_winners AS sw
|
||||
RETURNING id AS new_office_id, office_type_id, character_id, region_id
|
||||
),
|
||||
count_inserted AS (
|
||||
SELECT
|
||||
office_type_id,
|
||||
region_id,
|
||||
COUNT(*) AS inserted_count
|
||||
FROM insert_winners
|
||||
GROUP BY office_type_id, region_id
|
||||
),
|
||||
needed_to_fill AS (
|
||||
SELECT
|
||||
dt.office_type_id,
|
||||
dt.region_id,
|
||||
(pot.seats_per_region - COALESCE(ci.inserted_count, 0)) AS gaps
|
||||
FROM distinct_types AS dt
|
||||
JOIN falukant_type.political_office_type AS pot
|
||||
ON pot.id = dt.office_type_id
|
||||
LEFT JOIN count_inserted AS ci
|
||||
ON ci.office_type_id = dt.office_type_id
|
||||
AND ci.region_id = dt.region_id
|
||||
WHERE (pot.seats_per_region - COALESCE(ci.inserted_count, 0)) > 0
|
||||
),
|
||||
random_candidates AS (
|
||||
SELECT
|
||||
rtf.office_type_id,
|
||||
rtf.region_id,
|
||||
ch.id AS character_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY rtf.office_type_id, rtf.region_id
|
||||
ORDER BY RANDOM()
|
||||
) AS rn
|
||||
FROM needed_to_fill AS rtf
|
||||
JOIN falukant_data.character AS ch
|
||||
ON ch.region_id = rtf.region_id
|
||||
AND ch.user_id IS NULL
|
||||
AND ch.birthdate <= NOW() - INTERVAL '21 days'
|
||||
AND ch.title_of_nobility IN (
|
||||
SELECT id FROM falukant_type.title WHERE label_tr != 'noncivil'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM falukant_data.political_office AS po2
|
||||
JOIN falukant_type.political_office_type AS pot2
|
||||
ON pot2.id = po2.office_type_id
|
||||
WHERE po2.character_id = ch.id
|
||||
AND (po2.created_at + (pot2.term_length * INTERVAL '1 day')) >
|
||||
NOW() + INTERVAL '2 days'
|
||||
)
|
||||
),
|
||||
insert_random AS (
|
||||
INSERT INTO falukant_data.political_office
|
||||
(office_type_id, character_id, created_at, updated_at, region_id)
|
||||
SELECT
|
||||
rc.office_type_id,
|
||||
rc.character_id,
|
||||
NOW(),
|
||||
NOW(),
|
||||
rc.region_id
|
||||
FROM random_candidates AS rc
|
||||
JOIN needed_to_fill AS rtf
|
||||
ON rtf.office_type_id = rc.office_type_id
|
||||
AND rtf.region_id = rc.region_id
|
||||
WHERE rc.rn <= rtf.gaps
|
||||
RETURNING id AS new_office_id, office_type_id, character_id, region_id
|
||||
)
|
||||
SELECT
|
||||
new_office_id AS office_id,
|
||||
office_type_id,
|
||||
character_id,
|
||||
region_id
|
||||
FROM insert_winners
|
||||
UNION ALL
|
||||
SELECT
|
||||
new_office_id AS office_id,
|
||||
office_type_id,
|
||||
character_id,
|
||||
region_id
|
||||
FROM insert_random;
|
||||
"#;
|
||||
|
||||
const QUERY_USERS_IN_CITIES_OF_REGIONS: &str = r#"
|
||||
WITH RECURSIVE region_tree AS (
|
||||
SELECT id
|
||||
FROM falukant_data.region
|
||||
WHERE id = $1
|
||||
UNION ALL
|
||||
SELECT r2.id
|
||||
FROM falukant_data.region AS r2
|
||||
JOIN region_tree AS rt
|
||||
ON r2.parent_id = rt.id
|
||||
)
|
||||
SELECT DISTINCT ch.user_id
|
||||
FROM falukant_data.character AS ch
|
||||
JOIN region_tree AS rt2
|
||||
ON ch.region_id = rt2.id
|
||||
WHERE ch.user_id IS NOT NULL;
|
||||
"#;
|
||||
|
||||
const QUERY_NOTIFY_OFFICE_EXPIRATION: &str = r#"
|
||||
INSERT INTO falukant_log.notification
|
||||
(user_id, tr, created_at, updated_at)
|
||||
SELECT
|
||||
po.character_id,
|
||||
'notify_office_expiring',
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM falukant_data.political_office AS po
|
||||
JOIN falukant_type.political_office_type AS pot
|
||||
ON po.office_type_id = pot.id
|
||||
WHERE (po.created_at + (pot.term_length * INTERVAL '1 day'))
|
||||
BETWEEN (NOW() + INTERVAL '2 days')
|
||||
AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
|
||||
"#;
|
||||
|
||||
const QUERY_NOTIFY_ELECTION_CREATED: &str = r#"
|
||||
INSERT INTO falukant_log.notification
|
||||
(user_id, tr, created_at, updated_at)
|
||||
VALUES
|
||||
($1, 'notify_election_created', NOW(), NOW());
|
||||
"#;
|
||||
|
||||
const QUERY_NOTIFY_OFFICE_FILLED: &str = r#"
|
||||
INSERT INTO falukant_log.notification
|
||||
(user_id, tr, created_at, updated_at)
|
||||
VALUES
|
||||
($1, 'notify_office_filled', NOW(), NOW());
|
||||
"#;
|
||||
|
||||
const QUERY_GET_USERS_WITH_EXPIRING_OFFICES: &str = r#"
|
||||
SELECT DISTINCT ch.user_id
|
||||
FROM falukant_data.political_office AS po
|
||||
JOIN falukant_type.political_office_type AS pot
|
||||
ON po.office_type_id = pot.id
|
||||
JOIN falukant_data.character AS ch
|
||||
ON po.character_id = ch.id
|
||||
WHERE ch.user_id IS NOT NULL
|
||||
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))
|
||||
BETWEEN (NOW() + INTERVAL '2 days')
|
||||
AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
|
||||
"#;
|
||||
|
||||
const QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS: &str = r#"
|
||||
SELECT DISTINCT ch.user_id
|
||||
FROM falukant_data.election AS e
|
||||
JOIN falukant_data.character AS ch
|
||||
ON ch.region_id = e.region_id
|
||||
WHERE ch.user_id IS NOT NULL
|
||||
AND e.date >= NOW() - INTERVAL '1 day';
|
||||
"#;
|
||||
|
||||
const QUERY_GET_USERS_WITH_FILLED_OFFICES: &str = r#"
|
||||
SELECT DISTINCT ch.user_id
|
||||
FROM falukant_data.political_office AS po
|
||||
JOIN falukant_data.character AS ch
|
||||
ON po.character_id = ch.id
|
||||
WHERE ch.user_id IS NOT NULL
|
||||
AND po.created_at >= NOW() - INTERVAL '1 minute';
|
||||
"#;
|
||||
|
||||
const QUERY_PROCESS_ELECTIONS: &str = r#"
|
||||
SELECT office_id, office_type_id, character_id, region_id
|
||||
FROM falukant_data.process_elections();
|
||||
"#;
|
||||
|
||||
/// Schneidet für alle Amtstyp/Region-Kombinationen überzählige Einträge in
|
||||
/// `falukant_data.political_office` ab, so dass höchstens
|
||||
/// `seats_per_region` Ämter pro Kombination übrig bleiben.
|
||||
///
|
||||
/// Die Auswahl, welche Ämter entfernt werden, erfolgt deterministisch über
|
||||
/// `created_at DESC`: die **neuesten** Ämter bleiben bevorzugt im Amt,
|
||||
/// ältere Einträge werden zuerst entfernt. Damit lässt sich das Verhalten
|
||||
/// später leicht anpassen (z. B. nach bestimmten Prioritäten).
|
||||
const QUERY_TRIM_EXCESS_OFFICES_GLOBAL: &str = r#"
|
||||
WITH seats AS (
|
||||
SELECT
|
||||
pot.id AS office_type_id,
|
||||
rt.id AS region_id,
|
||||
pot.seats_per_region AS seats_total
|
||||
FROM falukant_type.political_office_type AS pot
|
||||
JOIN falukant_type.region AS rt
|
||||
ON pot.region_type = rt.label_tr
|
||||
),
|
||||
ranked AS (
|
||||
SELECT
|
||||
po.id,
|
||||
po.office_type_id,
|
||||
po.region_id,
|
||||
s.seats_total,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY po.office_type_id, po.region_id
|
||||
ORDER BY po.created_at DESC
|
||||
) AS rn
|
||||
FROM falukant_data.political_office AS po
|
||||
JOIN seats AS s
|
||||
ON s.office_type_id = po.office_type_id
|
||||
AND s.region_id = po.region_id
|
||||
),
|
||||
to_delete AS (
|
||||
SELECT id
|
||||
FROM ranked
|
||||
WHERE rn > seats_total
|
||||
)
|
||||
DELETE FROM falukant_data.political_office
|
||||
WHERE id IN (SELECT id FROM to_delete);
|
||||
"#;
|
||||
|
||||
impl PoliticsWorker {
|
||||
pub fn new(pool: ConnectionPool, broker: MessageBroker) -> Self {
|
||||
@@ -542,11 +99,11 @@ impl PoliticsWorker {
|
||||
broker: &MessageBroker,
|
||||
) -> Result<(), DbError> {
|
||||
// 1) Optional: Positionen evaluieren (aktuell nur Logging/Struktur)
|
||||
let _ = Self::evaluate_political_positions(pool)?;
|
||||
Self::evaluate_political_positions(pool)?;
|
||||
|
||||
// 2) Schema-Änderungen abgleichen: neue / zusätzliche Ämter anlegen,
|
||||
// ohne bestehende Amtsinhaber bei Reduktion zu entfernen.
|
||||
let _ = Self::sync_offices_with_types(pool)?;
|
||||
Self::sync_offices_with_types(pool)?;
|
||||
|
||||
// 3) Ämter, die bald auslaufen, benachrichtigen
|
||||
Self::notify_office_expirations(pool, broker)?;
|
||||
|
||||
Reference in New Issue
Block a user