Skip to content

Commit 429aa94

Browse files
committed
editoast: add models and views
1 parent e289736 commit 429aa94

File tree

7 files changed

+784
-7
lines changed

7 files changed

+784
-7
lines changed

editoast/migrations/2024-02-01-002011_create_v2_scenario_trainschedule_timetable/up.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ CREATE TABLE trainschedulev2 (
1717
id int8 PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
1818
train_name varchar(128) NOT NULL,
1919
labels jsonb NOT NULL,
20-
rolling_stock_id int8 NOT NULL REFERENCES rolling_stock(id) ON DELETE CASCADE,
20+
rolling_stock_id varchar(128) NOT NULL,
2121
timetable_id int8 NOT NULL UNIQUE REFERENCES timetablev2(id) DEFERRABLE INITIALLY DEFERRED,
22-
departure_time float8 NOT NULL,
22+
departure_time timestamptz NOT NULL,
2323
scheduled_points jsonb NOT NULL,
2424
allowances jsonb NOT NULL,
2525
initial_speed float8 NOT NULL,
2626
comfort varchar(8) NOT NULL,
27+
path jsonb NOT NULL,
28+
tag_constrains jsonb NOT NULL,
2729
speed_limit_tags varchar(128) NULL,
2830
power_restriction_ranges jsonb NULL,
2931
options jsonb NULL

editoast/src/modelsv2/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod infra_objects;
33
pub mod scenario;
44
pub mod timetable;
55
pub mod trainschedule;
6+
pub use scenario::ScenarioV2WithCountTrains;
67

78
pub use documents::Document;
89

editoast/src/modelsv2/scenario.rs

+234
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
use actix_web::web::Data;
12
use chrono::NaiveDateTime;
3+
use diesel::sql_types::{BigInt, Text};
24
use editoast_derive::ModelV2;
5+
use serde_derive::{Deserialize, Serialize};
6+
use utoipa::ToSchema;
37

48
#[derive(Debug, Default, Clone, ModelV2)]
9+
#[model(changeset(derive(Deserialize)))]
510
#[model(table = crate::tables::scenariov2)]
611
pub struct ScenarioV2 {
712
pub id: i64,
@@ -14,3 +19,232 @@ pub struct ScenarioV2 {
1419
pub timetable_id: i64,
1520
pub study_id: i64,
1621
}
22+
23+
crate::schemas! {
24+
ScenarioV2WithCountTrains
25+
}
26+
27+
#[derive(Debug, Clone, Deserialize, Serialize, QueryableByName, ToSchema)]
28+
pub struct ScenarioWithDetails {
29+
pub id: i64,
30+
pub infra_id: i64,
31+
pub name: String,
32+
pub description: String,
33+
pub creation_date: NaiveDateTime,
34+
pub last_modification: NaiveDateTime,
35+
pub tags: Vec<Option<String>>,
36+
pub timetable_id: i64,
37+
pub study_id: i64,
38+
#[diesel(sql_type = Text)]
39+
pub infra_name: String,
40+
#[diesel(sql_type = Nullable<Text>)]
41+
pub electrical_profile_set_name: Option<String>,
42+
#[diesel(sql_type = Array<LightTrainSchedule>)]
43+
pub train_schedules: Vec<LightTrainSchedule>,
44+
#[diesel(sql_type = BigInt)]
45+
pub trains_count: i64,
46+
}
47+
48+
#[derive(Debug, Clone, Serialize, QueryableByName, ToSchema)]
49+
pub struct ScenarioV2WithCountTrains {
50+
pub id: i64,
51+
pub infra_id: i64,
52+
pub name: String,
53+
pub description: String,
54+
pub creation_date: NaiveDateTime,
55+
pub last_modification: NaiveDateTime,
56+
pub tags: Vec<Option<String>>,
57+
pub timetable_id: i64,
58+
pub study_id: i64,
59+
#[diesel(sql_type = BigInt)]
60+
pub trains_count: i64,
61+
#[diesel(sql_type = Text)]
62+
pub infra_name: String,
63+
}
64+
65+
impl ScenarioV2 {
66+
pub async fn with_details(self, db_pool: Data<DbPool>) -> Result<ScenarioWithDetails> {
67+
let mut conn = db_pool.get().await?;
68+
self.with_details_conn(&mut conn).await
69+
}
70+
71+
pub async fn with_details_conn(self, conn: &mut PgConnection) -> Result<ScenarioWithDetails> {
72+
use crate::tables::electrical_profile_set::dsl as elec_dsl;
73+
use crate::tables::infra::dsl as infra_dsl;
74+
use crate::tables::train_schedule::dsl::*;
75+
76+
let infra_name = infra_dsl::infra
77+
.filter(infra_dsl::id.eq(self.infra_id.unwrap()))
78+
.select(infra_dsl::name)
79+
.first::<String>(conn)
80+
.await?;
81+
82+
let electrical_profile_set_name = match self.electrical_profile_set_id.unwrap() {
83+
Some(electrical_profile_set) => Some(
84+
elec_dsl::electrical_profile_set
85+
.filter(elec_dsl::id.eq(electrical_profile_set))
86+
.select(elec_dsl::name)
87+
.first::<String>(conn)
88+
.await?,
89+
),
90+
None => None,
91+
};
92+
93+
let train_schedules = train_schedule
94+
.filter(timetable_id.eq(self.timetable_id.unwrap()))
95+
.select((id, train_name, departure_time, path_id))
96+
.load::<LightTrainSchedule>(conn)
97+
.await?;
98+
99+
let trains_count = train_schedules.len() as i64;
100+
101+
Ok(ScenarioWithDetails {
102+
scenario: self,
103+
infra_name,
104+
electrical_profile_set_name,
105+
train_schedules,
106+
trains_count,
107+
})
108+
}
109+
}
110+
111+
/// Delete a scenario.
112+
/// When we delete a scenario, the associated timetable is deleted too.
113+
#[async_trait]
114+
impl Delete for Scenario {
115+
async fn delete_conn(conn: &mut PgConnection, scenario_id: i64) -> Result<bool> {
116+
use crate::tables::scenario::dsl as scenario_dsl;
117+
use crate::tables::timetable::dsl as timetable_dsl;
118+
119+
// Delete scenario
120+
let scenario = match delete(scenario_dsl::scenario.filter(scenario_dsl::id.eq(scenario_id)))
121+
.get_result::<Scenario>(conn)
122+
.await
123+
{
124+
Ok(scenario) => scenario,
125+
Err(DieselError::NotFound) => return Ok(false),
126+
Err(err) => return Err(err.into()),
127+
};
128+
129+
// Delete timetable
130+
delete(
131+
timetable_dsl::timetable.filter(timetable_dsl::id.eq(scenario.timetable_id.unwrap())),
132+
)
133+
.execute(conn)
134+
.await?;
135+
Ok(true)
136+
}
137+
}
138+
139+
#[async_trait]
140+
impl List<(i64, Ordering)> for ScenarioWithCountTrains {
141+
/// List all scenarios with the number of trains.
142+
/// This functions takes a study_id to filter scenarios.
143+
async fn list_conn(
144+
conn: &mut PgConnection,
145+
page: i64,
146+
page_size: i64,
147+
params: (i64, Ordering),
148+
) -> Result<PaginatedResponse<Self>> {
149+
let study_id = params.0;
150+
let ordering = params.1.to_sql();
151+
sql_query(format!("WITH scenarios_with_train_counts AS (
152+
SELECT t.*, COUNT(train_schedule.id) as trains_count
153+
FROM scenario as t
154+
LEFT JOIN train_schedule ON t.timetable_id = train_schedule.timetable_id WHERE t.study_id = $1
155+
GROUP BY t.id ORDER BY {ordering}
156+
)
157+
SELECT scenarios_with_train_counts.*, infra.name as infra_name
158+
FROM scenarios_with_train_counts
159+
JOIN infra ON infra.id = infra_id"))
160+
.bind::<BigInt, _>(study_id)
161+
.paginate(page, page_size)
162+
.load_and_count(conn).await
163+
}
164+
}
165+
166+
#[cfg(test)]
167+
pub mod test {
168+
use super::*;
169+
use crate::fixtures::tests::{db_pool, scenario_fixture_set, ScenarioFixtureSet, TestFixture};
170+
use crate::models::Delete;
171+
use crate::models::List;
172+
use crate::models::Ordering;
173+
use crate::models::Retrieve;
174+
use crate::models::Timetable;
175+
use rstest::rstest;
176+
177+
#[rstest]
178+
async fn create_delete_scenario(db_pool: Data<DbPool>) {
179+
let ScenarioFixtureSet { scenario, .. } = scenario_fixture_set().await;
180+
181+
// Delete the scenario
182+
Scenario::delete(db_pool.clone(), scenario.id())
183+
.await
184+
.unwrap();
185+
186+
// Second delete should fail
187+
assert!(!Scenario::delete(db_pool.clone(), scenario.id())
188+
.await
189+
.unwrap());
190+
}
191+
192+
#[rstest]
193+
async fn get_study(db_pool: Data<DbPool>) {
194+
let ScenarioFixtureSet { study, .. } = scenario_fixture_set().await;
195+
196+
// Get a scenario
197+
assert!(Scenario::retrieve(db_pool.clone(), study.id())
198+
.await
199+
.is_ok());
200+
assert!(ScenarioWithCountTrains::list(
201+
db_pool.clone(),
202+
1,
203+
25,
204+
(study.id(), Ordering::LastModifiedAsc)
205+
)
206+
.await
207+
.is_ok());
208+
}
209+
210+
#[rstest]
211+
async fn sort_scenario(db_pool: Data<DbPool>) {
212+
let ScenarioFixtureSet {
213+
scenario,
214+
study,
215+
timetable,
216+
..
217+
} = scenario_fixture_set().await;
218+
219+
// Create second timetable
220+
let timetable_2 = TestFixture::create_legacy(
221+
Timetable {
222+
id: None,
223+
name: Some(timetable.model.name.clone().unwrap() + "_bis"),
224+
},
225+
db_pool.clone(),
226+
)
227+
.await;
228+
229+
// Create second scenario
230+
let scenario_2 = Scenario {
231+
name: Some(scenario.model.name.clone().unwrap() + "_bis"),
232+
id: None,
233+
timetable_id: Some(timetable_2.id()),
234+
..scenario.model.clone()
235+
};
236+
let _scenario_2 = TestFixture::create_legacy(scenario_2, db_pool.clone());
237+
238+
let scenarios =
239+
ScenarioWithCountTrains::list(db_pool.clone(), 1, 25, (study.id(), Ordering::NameDesc))
240+
.await
241+
.unwrap()
242+
.results;
243+
244+
for (p1, p2) in scenarios.iter().zip(scenarios.iter().skip(1)) {
245+
let name_1 = p1.scenario.name.as_ref().unwrap().to_lowercase();
246+
let name_2 = p2.scenario.name.as_ref().unwrap().to_lowercase();
247+
assert!(name_1.ge(&name_2));
248+
}
249+
}
250+
}

editoast/src/modelsv2/trainschedule.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::DieselJson;
2+
use chrono::NaiveDateTime;
23
use editoast_derive::ModelV2;
34

45
#[derive(Debug, Default, Clone, ModelV2)]
@@ -7,13 +8,15 @@ pub struct TrainScheduleV2 {
78
pub id: i64,
89
pub train_name: String,
910
pub labels: DieselJson<Vec<String>>,
10-
pub rolling_stock_id: i64,
11+
pub rolling_stock_id: String,
1112
pub timetable_id: i64,
12-
pub departure_time: f64,
13+
pub departure_time: NaiveDateTime,
1314
pub scheduled_points: DieselJson<Vec<String>>,
1415
pub allowances: DieselJson<Vec<String>>,
1516
pub initial_speed: f64,
1617
pub comfort: String,
18+
pub path: DieselJson<Vec<String>>,
19+
pub tag_constrains: DieselJson<Vec<String>>,
1720
pub speed_limit_tags: Option<String>,
1821
pub power_restriction_ranges: Option<DieselJson<Vec<String>>>,
1922
pub options: Option<DieselJson<Vec<String>>>,

editoast/src/tables.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -680,14 +680,17 @@ diesel::table! {
680680
#[max_length = 128]
681681
train_name -> Varchar,
682682
labels -> Jsonb,
683-
rolling_stock_id -> Int8,
683+
#[max_length = 128]
684+
rolling_stock_id -> Varchar,
684685
timetable_id -> Int8,
685-
departure_time -> Float8,
686+
departure_time -> Timestamptz,
686687
scheduled_points -> Jsonb,
687688
allowances -> Jsonb,
688689
initial_speed -> Float8,
689690
#[max_length = 8]
690691
comfort -> Varchar,
692+
path -> Jsonb,
693+
tag_constrains -> Jsonb,
691694
#[max_length = 128]
692695
speed_limit_tags -> Nullable<Varchar>,
693696
power_restriction_ranges -> Nullable<Jsonb>,
@@ -741,7 +744,6 @@ diesel::joinable!(timetablev2 -> electrical_profile_set (electrical_profile_set_
741744
diesel::joinable!(train_schedule -> pathfinding (path_id));
742745
diesel::joinable!(train_schedule -> rolling_stock (rolling_stock_id));
743746
diesel::joinable!(train_schedule -> timetable (timetable_id));
744-
diesel::joinable!(trainschedulev2 -> rolling_stock (rolling_stock_id));
745747
diesel::joinable!(trainschedulev2 -> timetablev2 (timetable_id));
746748

747749
diesel::allow_tables_to_appear_in_same_query!(

editoast/src/views/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod pathfinding;
1010
pub mod projects;
1111
pub mod rolling_stocks;
1212
pub mod scenario;
13+
pub mod scenariov2;
1314
pub mod search;
1415
mod single_simulation;
1516
pub mod sprites;

0 commit comments

Comments
 (0)