Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 28 additions & 167 deletions stationapi/src/infrastructure/station_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ use crate::{
proto::StopCondition,
};

#[derive(sqlx::FromRow)]
struct TrainTypesCountRow {
train_types_count: Option<i32>,
}

#[derive(sqlx::FromRow)]
struct ConnectedRoutePatternStopRow {
line_group_cd: i32,
Expand Down Expand Up @@ -330,25 +325,13 @@ impl StationRepository for MyStationRepository {
direction_id: Option<u32>,
) -> Result<Vec<Station>, DomainError> {
let mut conn = self.pool.acquire().await?;
match station_id {
Some(station_id) => {
InternalStationRepository::get_by_line_id_and_station_id(
line_id,
station_id,
direction_id,
&mut conn,
)
.await
}
None => {
InternalStationRepository::get_by_line_id_without_train_types(
line_id,
direction_id,
&mut conn,
)
.await
}
}
InternalStationRepository::get_by_line_id_with_train_type(
line_id,
station_id,
direction_id,
&mut conn,
)
.await
}
async fn get_by_line_id_vec(&self, line_ids: &[u32]) -> Result<Vec<Station>, DomainError> {
let mut conn = self.pool.acquire().await?;
Expand Down Expand Up @@ -532,29 +515,6 @@ impl InternalStationRepository {
})
.collect())
}

async fn fetch_has_local_train_types_by_station_id(
id: u32,
conn: &mut PgConnection,
) -> Result<bool, DomainError> {
let row: TrainTypesCountRow = sqlx::query_as!(
TrainTypesCountRow,
"SELECT COUNT(sst.line_group_cd)::integer AS train_types_count
FROM station_station_types AS sst
JOIN types AS t ON t.type_cd = sst.type_cd
WHERE sst.station_cd = $1
AND (
t.kind IN (0, 1)
OR t.priority > 0
)",
id as i32,
)
.fetch_one(conn)
.await?;

Ok(row.train_types_count.unwrap_or(0) > 0)
}

async fn find_by_id(id: u32, conn: &mut PgConnection) -> Result<Option<Station>, DomainError> {
let rows: Option<StationRow> = sqlx::query_as!(
StationRow,
Expand Down Expand Up @@ -759,99 +719,6 @@ impl InternalStationRepository {
Ok(stations)
}

async fn get_by_line_id_without_train_types(
line_id: u32,
direction_id: Option<u32>,
conn: &mut PgConnection,
) -> Result<Vec<Station>, DomainError> {
// When direction_id = 1 (上り) or 2 (下り), reverse the order
let order_clause = if matches!(direction_id, Some(1) | Some(2)) {
"ORDER BY s.e_sort DESC, s.station_cd DESC"
} else {
"ORDER BY s.e_sort ASC, s.station_cd ASC"
};

let query_str = format!(
r#"SELECT
s.station_cd,
s.station_g_cd,
s.station_name,
s.station_name_k,
s.station_name_r,
s.station_name_rn,
s.station_name_zh,
s.station_name_ko,
s.station_number1,
s.station_number2,
s.station_number3,
s.station_number4,
s.three_letter_code,
s.line_cd,
s.pref_cd,
s.post,
s.address,
s.lon,
s.lat,
s.open_ymd,
s.close_ymd,
s.e_status,
s.e_sort,
l.company_cd,
COALESCE(NULLIF(COALESCE(a.line_name, l.line_name), ''), NULL) AS line_name,
COALESCE(NULLIF(COALESCE(a.line_name_k, l.line_name_k), ''), NULL) AS line_name_k,
COALESCE(NULLIF(COALESCE(a.line_name_h, l.line_name_h), ''), NULL) AS line_name_h,
COALESCE(NULLIF(COALESCE(a.line_name_r, l.line_name_r), ''), NULL) AS line_name_r,
COALESCE(NULLIF(COALESCE(a.line_name_zh, l.line_name_zh), ''), NULL) AS line_name_zh,
COALESCE(NULLIF(COALESCE(a.line_name_ko, l.line_name_ko), ''), NULL) AS line_name_ko,
COALESCE(NULLIF(COALESCE(a.line_color_c, l.line_color_c), ''), NULL) AS line_color_c,
l.line_type,
l.line_symbol1,
l.line_symbol2,
l.line_symbol3,
l.line_symbol4,
l.line_symbol1_color,
l.line_symbol2_color,
l.line_symbol3_color,
l.line_symbol4_color,
l.line_symbol1_shape,
l.line_symbol2_shape,
l.line_symbol3_shape,
l.line_symbol4_shape,
COALESCE(l.average_distance, 0.0)::DOUBLE PRECISION AS average_distance,
NULL::int AS type_id,
NULL::int AS sst_id,
NULL::int AS type_cd,
NULL::int AS line_group_cd,
NULL::int AS pass,
NULL::text AS type_name,
NULL::text AS type_name_k,
NULL::text AS type_name_r,
NULL::text AS type_name_zh,
NULL::text AS type_name_ko,
NULL::text AS color,
NULL::int AS direction,
NULL::int AS kind,
s.transport_type
FROM stations AS s
JOIN lines AS l ON l.line_cd = s.line_cd
LEFT JOIN line_aliases AS la ON la.station_cd = s.station_cd
LEFT JOIN aliases AS a ON a.id = la.alias_cd
WHERE l.line_cd = $1
AND s.e_status = 0
AND l.e_status = 0
{order_clause}"#
);

let rows = sqlx::query_as::<_, StationRow>(&query_str)
.bind(line_id as i32)
.fetch_all(conn)
.await?;

let stations: Vec<Station> = rows.into_iter().map(|row| row.into()).collect();

Ok(stations)
}

async fn get_by_line_id_vec(
line_ids: &[u32],
conn: &mut PgConnection,
Expand Down Expand Up @@ -1054,31 +921,27 @@ impl InternalStationRepository {
Ok(stations)
}

async fn get_by_line_id_and_station_id(
async fn get_by_line_id_with_train_type(
line_id: u32,
station_id: u32,
station_id: Option<u32>,
direction_id: Option<u32>,
conn: &mut PgConnection,
) -> Result<Vec<Station>, DomainError> {
let stations: Vec<Station> = match Self::fetch_has_local_train_types_by_station_id(
station_id, conn,
)
.await?
{
true => {
// When direction_id = 1 (上り) or 2 (下り), reverse the order
let order_clause = if matches!(direction_id, Some(1) | Some(2)) {
"ORDER BY sst.id DESC"
} else {
"ORDER BY sst.id ASC"
};

let query_str = format!(
r#"WITH target_line_group AS (
// When direction_id = 1 (上り) or 2 (下り), reverse the order
let order_clause = if matches!(direction_id, Some(1) | Some(2)) {
"ORDER BY sst.id DESC"
} else {
"ORDER BY sst.id ASC"
};

let query_str = format!(
r#"WITH target_line_group AS (
SELECT sst_inner.line_group_cd
FROM station_station_types AS sst_inner
LEFT JOIN types AS t_inner ON sst_inner.type_cd = t_inner.type_cd
WHERE sst_inner.station_cd = $1
JOIN stations AS seed_station ON seed_station.station_cd = sst_inner.station_cd
WHERE seed_station.line_cd = $1
AND ($2::int IS NULL OR sst_inner.station_cd = $2)
AND (
(t_inner.priority > 0 AND sst_inner.pass <> 1 AND sst_inner.type_cd = t_inner.type_cd)
OR (NOT (t_inner.priority > 0 AND sst_inner.pass <> 1) AND t_inner.kind IN (0,1))
Expand Down Expand Up @@ -1154,16 +1017,14 @@ impl InternalStationRepository {
WHERE s.e_status = 0
AND l.e_status = 0
{order_clause}"#
);
);

let rows = sqlx::query_as::<_, StationRow>(&query_str)
.bind(station_id as i32)
.fetch_all(conn)
.await?;
rows.into_iter().map(|row| row.into()).collect()
}
false => Self::get_by_line_id_without_train_types(line_id, direction_id, conn).await?,
};
let rows = sqlx::query_as::<_, StationRow>(&query_str)
.bind(line_id as i32)
.bind(station_id.map(|id| id as i32))
.fetch_all(conn)
.await?;
let stations = rows.into_iter().map(|row| row.into()).collect();

Ok(stations)
}
Expand Down
90 changes: 32 additions & 58 deletions stationapi/src/use_case/interactor/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,31 +1059,13 @@ where
to_station_id: u32,
line_group_id: Option<u32>,
) -> Result<Vec<proto::TrainRouteSegment>, UseCaseError> {
// line_group_id 未指定は種別なし(各駅停車)の単一路線走行。
// from駅の所属路線の駅列をそのまま経路として扱う。
let stations = match line_group_id {
Some(line_group_id) => {
self.get_stations_by_line_group_id(line_group_id, TransportTypeFilter::RailAndBus)
.await?
}
None => {
let from_station = self
.station_repository
.find_by_id(from_station_id)
.await?
.ok_or_else(|| UseCaseError::NotFound {
entity_type: "station",
entity_id: from_station_id.to_string(),
})?;
self.get_stations_by_line_id(
from_station.line_cd as u32,
None,
None,
TransportTypeFilter::RailAndBus,
)
.await?
}
};
let line_group_id = line_group_id.ok_or_else(|| UseCaseError::NotFound {
entity_type: "line group",
entity_id: "unspecified".to_string(),
})?;
let stations = self
.get_stations_by_line_group_id(line_group_id, TransportTypeFilter::RailAndBus)
.await?;

let from_idx = stations
.iter()
Expand Down Expand Up @@ -1902,17 +1884,22 @@ where
}

fn build_route_tree_map<'a>(&self, stops: &'a [Station]) -> BTreeMap<i32, Vec<&'a Station>> {
stops.iter().fold(
BTreeMap::new(),
|mut acc: BTreeMap<i32, Vec<&'a Station>>, value| {
if let Some(line_group_cd) = value.line_group_cd {
acc.entry(line_group_cd).or_default().push(value);
} else {
acc.entry(value.line_cd).or_default().push(value);
};
acc
},
)
stops
.iter()
.map(|stop| {
(
stop.line_group_cd
.expect("route stop must belong to a train type group"),
stop,
)
})
.fold(
BTreeMap::new(),
|mut acc: BTreeMap<i32, Vec<&'a Station>>, (line_group_cd, stop)| {
acc.entry(line_group_cd).or_default().push(stop);
acc
},
)
}

fn build_station_from_row(
Expand Down Expand Up @@ -3028,7 +3015,7 @@ mod tests {
data.iter()
.enumerate()
.map(|(i, &(cd, lat, lon))| {
let mut s = create_test_station(cd, 9930100, 99301, None);
let mut s = create_test_station(cd, 9930100, 99301, Some(9930100));
s.lat = lat;
s.lon = lon;
s.e_sort = 9930101 + i as i32;
Expand Down Expand Up @@ -3107,7 +3094,7 @@ mod tests {
data.iter()
.enumerate()
.map(|(i, &(cd, lat, lon))| {
let mut s = create_test_station(cd, cd, 28008, None);
let mut s = create_test_station(cd, cd, 28008, Some(2800800));
s.lat = lat;
s.lon = lon;
s.e_sort = 2800801 + i as i32;
Expand All @@ -3126,7 +3113,8 @@ mod tests {
/// 修正前は種別倍率(×1.15)が掛かり駅間別較正も外れて約15分に縮んでいた。
#[tokio::test]
async fn test_estimate_route_arrival_times_through_express_all_stops_matches_local() {
let local = build_interactor(hanzomon_stops(None), vec![], vec![], vec![]);
let default_kind = Some(proto::TrainTypeKind::Default as i32);
let local = build_interactor(hanzomon_stops(default_kind), vec![], vec![], vec![]);
let local_est = local
.estimate_route_arrival_times(2800813, 2800807, &[], None)
.await
Expand Down Expand Up @@ -3464,18 +3452,12 @@ mod tests {
}

#[test]
fn test_build_route_tree_map_groups_by_line_cd_when_no_line_group() {
#[should_panic(expected = "route stop must belong to a train type group")]
fn test_build_route_tree_map_requires_line_group() {
let interactor = create_interactor();
let stops = vec![
create_test_station(1, 1, 100, None),
create_test_station(2, 2, 100, None),
create_test_station(3, 3, 200, None),
];
let result = interactor.build_route_tree_map(&stops);
let stops = vec![create_test_station(1, 1, 100, None)];

assert_eq!(result.len(), 2);
assert_eq!(result.get(&100).unwrap().len(), 2);
assert_eq!(result.get(&200).unwrap().len(), 1);
interactor.build_route_tree_map(&stops);
}

#[test]
Expand Down Expand Up @@ -5425,9 +5407,6 @@ mod tests {
// line_group 300: 発着駅を含まない → 除外
create_route_stop(3105, 5, 33, Some(300)),
create_route_stop(3106, 6, 33, Some(300)),
// line_group_cdなし: line_cd(44)でグループ化され種別なし
create_route_stop(4101, 1, 44, None),
create_route_stop(4103, 3, 44, None),
];
let lines = vec![
create_route_line(11, 100),
Expand All @@ -5441,7 +5420,7 @@ mod tests {

// 発着駅を含まないline_group 300は除外され、BTreeMapのキー順に並ぶ
let route_ids: Vec<u32> = routes.iter().map(|r| r.id).collect();
assert_eq!(route_ids, vec![44, 100, 200]);
assert_eq!(route_ids, vec![100, 200]);

// 路線の取得は経路候補ごとではなく一括1回で、
// 除外されたグループ(300)のIDは要求されない
Expand Down Expand Up @@ -5473,11 +5452,6 @@ mod tests {
let line_ids: Vec<u32> = tt.lines.iter().map(|l| l.id).collect();
assert_eq!(line_ids, vec![22]);
}

// line_group_cdなしのグループは種別を持たない
let route44 = routes.iter().find(|r| r.id == 44).unwrap();
assert_eq!(route44.stops.len(), 2);
assert!(route44.stops.iter().all(|s| s.train_type.is_none()));
}

#[tokio::test]
Expand Down
Loading