1use chrono::NaiveDateTime;
47use mysql::prelude::*;
48use mysql::*;
49
50use crate::database::{
51 sql_interface::SqlInterface, sql_interface_error::SqlInterfaceError, sql_query_strings,
52};
53use crate::food::feed_schedule_entry::FeedScheduleEntry;
54
55#[derive(Clone)]
56pub struct SqlFeedpatternHeader {
58 pub profile_id: i32,
59 pub profile_name: String,
60}
61
62#[derive(Clone)]
63pub struct SqlFeedPhase {
66 pub pause_duration: i16,
68
69 pub pause_skimmer: i16,
71
72 pub pause_main_pump_1: i16,
74
75 pub pause_main_pump_2: i16,
77
78 pub pause_aux_pump_1: i16,
80
81 pub pause_aux_pump_2: i16,
83
84 pub feed_duration: i16,
86
87 pub feed_skimmer: i16,
89
90 pub feed_main_pump_1: i16,
92
93 pub feed_main_pump_2: i16,
95
96 pub feed_aux_pump_1: i16,
98
99 pub feed_aux_pump_2: i16,
101}
102
103pub struct SqlFeedScheduleEntry {
106 pub timestamp: NaiveDateTime,
107 pub profile_id: i32,
108 pub profile_name: String,
109 pub repeat_daily: i16,
110}
111
112#[derive(Debug)]
114pub struct SqlInterfaceFeed {
115 pub conn: PooledConn,
117}
118
119impl SqlInterfaceFeed {
120 pub fn new(
144 mut conn: PooledConn,
145 max_rows_feed_pattern: u64,
146 max_rows_feed_schedule: u64,
147 max_rows_feed_log: u64,
148 ) -> Result<Self, SqlInterfaceError> {
149 let count_null_values_feedpatterns = SqlInterface::get_single_integer_from_database(
151 &mut conn,
152 sql_query_strings::SQL_QUERY_CHECK_FEEDPATTERNS_NULL,
153 )
154 .map_err(|e| SqlInterfaceError::DatabaseCheckFeedSetValsFailure {
155 location: module_path!().to_string(),
156 table_name: sql_query_strings::SQL_TABLE_FEEDPATTERNS.to_string(),
157 source: Box::new(e),
158 })?;
159 let count_rows_feedpatterns = SqlInterface::get_single_integer_from_database(
160 &mut conn,
161 sql_query_strings::SQL_QUERY_CHECK_FEEDPATTERN_COUNT,
162 )
163 .map_err(|e| SqlInterfaceError::DatabaseCheckFeedSetValsFailure {
164 location: module_path!().to_string(),
165 table_name: sql_query_strings::SQL_TABLE_FEEDPATTERNS.to_string(),
166 source: Box::new(e),
167 })?;
168
169 let count_null_values_feedschedule = SqlInterface::get_single_integer_from_database(
171 &mut conn,
172 sql_query_strings::SQL_QUERY_CHECK_FEEDSCHEDULE_NULL,
173 )
174 .map_err(|e| SqlInterfaceError::DatabaseCheckFeedSetValsFailure {
175 location: module_path!().to_string(),
176 table_name: sql_query_strings::SQL_TABLE_FEEDSCHEDULE.to_string(),
177 source: Box::new(e),
178 })?;
179 let count_rows_feedschedule = SqlInterface::get_single_integer_from_database(
180 &mut conn,
181 sql_query_strings::SQL_QUERY_CHECK_FEEDSCHEDULE_COUNT,
182 )
183 .map_err(|e| SqlInterfaceError::DatabaseCheckFeedSetValsFailure {
184 location: module_path!().to_string(),
185 table_name: sql_query_strings::SQL_TABLE_FEEDPATTERNS.to_string(),
186 source: Box::new(e),
187 })?;
188
189 let count_rows_feedlog = SqlInterface::get_single_integer_from_database(
190 &mut conn,
191 sql_query_strings::SQL_QUERY_CHECK_FEEDLOG_COUNT,
192 )
193 .map_err(|e| SqlInterfaceError::DatabaseCheckFeedSetValsFailure {
194 location: module_path!().to_string(),
195 table_name: sql_query_strings::SQL_TABLE_FEEDLOG.to_string(),
196 source: Box::new(e),
197 })?;
198
199 if count_null_values_feedpatterns < 0 || count_rows_feedpatterns < 0 {
201 return Err(SqlInterfaceError::DatabaseFeedTableNegativeValue(
202 module_path!().to_string(),
203 sql_query_strings::SQL_TABLE_FEEDPATTERNS.to_string(),
204 count_null_values_feedpatterns,
205 count_rows_feedpatterns,
206 ));
207 }
208 if count_null_values_feedschedule < 0 || count_rows_feedschedule < 0 {
209 return Err(SqlInterfaceError::DatabaseFeedTableNegativeValue(
210 module_path!().to_string(),
211 sql_query_strings::SQL_TABLE_FEEDSCHEDULE.to_string(),
212 count_null_values_feedschedule,
213 count_rows_feedschedule,
214 ));
215 }
216 if count_rows_feedlog < 0 {
217 return Err(SqlInterfaceError::DatabaseFeedTableNegativeValue(
218 module_path!().to_string(),
219 sql_query_strings::SQL_TABLE_FEEDLOG.to_string(),
220 -1,
221 count_rows_feedlog,
222 ));
223 }
224
225 if count_null_values_feedpatterns > 0 {
227 return Err(SqlInterfaceError::DatabaseFeedSetValTableContainsNull(
228 module_path!().to_string(),
229 sql_query_strings::SQL_QUERY_CHECK_FEEDPATTERNS_NULL.to_string(),
230 count_null_values_feedpatterns,
231 ));
232 }
233 if count_null_values_feedschedule > 0 {
234 return Err(SqlInterfaceError::DatabaseFeedSetValTableContainsNull(
235 module_path!().to_string(),
236 sql_query_strings::SQL_QUERY_CHECK_FEEDSCHEDULE_NULL.to_string(),
237 count_null_values_feedschedule,
238 ));
239 }
240
241 if max_rows_feed_pattern > 0 {
242 if count_rows_feedpatterns as u64 > max_rows_feed_pattern {
244 return Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
245 module_path!().to_string(),
246 sql_query_strings::SQL_TABLE_FEEDPATTERNS.to_string(),
247 count_rows_feedpatterns as u64,
248 max_rows_feed_pattern,
249 ));
250 }
251 }
252
253 if max_rows_feed_schedule > 0 {
254 if count_rows_feedschedule as u64 > max_rows_feed_schedule {
256 return Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
257 module_path!().to_string(),
258 sql_query_strings::SQL_TABLE_FEEDSCHEDULE.to_string(),
259 count_rows_feedschedule as u64,
260 max_rows_feed_schedule,
261 ));
262 }
263 }
264
265 if max_rows_feed_log > 0 {
266 if count_rows_feedlog as u64 > max_rows_feed_log {
268 return Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
269 module_path!().to_string(),
270 sql_query_strings::SQL_TABLE_FEEDLOG.to_string(),
271 count_rows_feedlog as u64,
272 max_rows_feed_log,
273 ));
274 }
275 }
276
277 Ok(SqlInterfaceFeed { conn })
278 }
279
280 pub(crate) fn get_feedschedule_entries_from_database(
299 &mut self,
300 sql_query: &str,
301 ) -> Result<Option<Vec<FeedScheduleEntry>>, SqlInterfaceError> {
302 let sql_feedschedule_entry_array = match self.conn.query_map(
303 sql_query,
304 |(timestamp, profile_id, profile_name, repeat_daily)| SqlFeedScheduleEntry {
305 timestamp,
306 profile_id,
307 profile_name,
308 repeat_daily,
309 },
310 ) {
311 Ok(c) => c,
312 Err(e) => {
313 return Err(SqlInterfaceError::FeedScheduleEntryRequestFailure {
314 location: module_path!().to_string(),
315 query: sql_query.to_string(),
316 source: e,
317 });
318 }
319 };
320
321 if sql_feedschedule_entry_array.is_empty() {
323 return Ok(None);
324 }
325
326 let feedschedule_entries_result: Result<Vec<FeedScheduleEntry>, _> =
327 sql_feedschedule_entry_array
328 .iter()
329 .map(FeedScheduleEntry::new)
330 .collect();
331
332 let feedschedule_entries = feedschedule_entries_result.map_err(|e| {
334 SqlInterfaceError::FeedScheduleEntryTimeStampProcessingFailure {
335 location: module_path!().to_string(),
336 number_entries: sql_feedschedule_entry_array.len(),
337 source: Box::new(e),
338 }
339 })?;
340
341 if feedschedule_entries.is_empty() {
342 Ok(None)
343 } else {
344 Ok(Some(feedschedule_entries))
345 }
346 }
347
348 #[cfg(test)]
349 fn get_future_feedschedule_entry_from_database(
362 &mut self,
363 ) -> Result<Option<FeedScheduleEntry>, SqlInterfaceError> {
364 let entries_opt = self.get_feedschedule_entries_from_database(
365 sql_query_strings::SQL_QUERY_READ_FUTURE_FEED_SCHEDULE_ENTRY,
366 )?;
367 Ok(entries_opt.and_then(|mut vec| vec.pop()))
368 }
369
370 #[cfg(test)]
371 fn get_future_feedschedule_entries_from_database(
384 &mut self,
385 ) -> Result<Option<Vec<FeedScheduleEntry>>, SqlInterfaceError> {
386 self.get_feedschedule_entries_from_database(
387 sql_query_strings::SQL_QUERY_READ_FUTURE_FEED_SCHEDULE_ENTRY,
388 )
389 }
390
391 pub fn get_feedpattern_header_from_database(
407 &mut self,
408 profile_id: i32,
409 ) -> Result<SqlFeedpatternHeader, SqlInterfaceError> {
410 let params = params! { "profile_id" => profile_id};
412
413 let single_feedpattern_entry_array = self
414 .conn
415 .exec_map(
416 sql_query_strings::SQL_QUERY_READ_FEEDPATTERN_HEADER,
417 params,
418 |(profile_id, profile_name)| SqlFeedpatternHeader {
419 profile_id,
420 profile_name,
421 },
422 )
423 .map_err(|e| SqlInterfaceError::SingleFeedpatternRequestFailure {
424 location: module_path!().to_string(),
425 query: sql_query_strings::SQL_QUERY_READ_FEEDPATTERN_HEADER.to_string(),
426 profile_id,
427 source: e,
428 })?;
429
430 if single_feedpattern_entry_array.is_empty() {
432 return Err(SqlInterfaceError::SingleFeedpatternRequestEmptyResponse(
433 module_path!().to_string(),
434 sql_query_strings::SQL_QUERY_READ_FEEDPATTERN_HEADER.to_string(),
435 profile_id,
436 ));
437 }
438
439 if single_feedpattern_entry_array.len() > 1 {
441 return Err(SqlInterfaceError::SingleFeedpatternRequestNoSingleResponse(
442 module_path!().to_string(),
443 sql_query_strings::SQL_QUERY_READ_FEEDPATTERN_HEADER.to_string(),
444 profile_id,
445 ));
446 }
447
448 let feedpattern_header = &single_feedpattern_entry_array[0];
449
450 Ok(feedpattern_header.clone())
451 }
452
453 pub fn get_feed_phase_from_database(
470 &mut self,
471 profile_id: i32,
472 phase_nr: i32,
473 ) -> Result<SqlFeedPhase, SqlInterfaceError> {
474 let params = params!("profile_id" => profile_id);
475 let sql_query = str::replace(
476 sql_query_strings::SQL_QUERY_READ_FEED_PHASE,
477 "#",
478 format!("{phase_nr:02}").as_str(),
479 );
480
481 let single_feedpattern_entry_array = match self.conn.exec_map(
482 sql_query.clone(),
483 params,
484 |(
485 pause_duration,
486 pause_skimmer,
487 pause_main_pump_1,
488 pause_main_pump_2,
489 pause_aux_pump_1,
490 pause_aux_pump_2,
491 feed_duration,
492 feed_skimmer,
493 feed_main_pump_1,
494 feed_main_pump_2,
495 feed_aux_pump_1,
496 feed_aux_pump_2,
497 )| SqlFeedPhase {
498 pause_duration,
499 pause_skimmer,
500 pause_main_pump_1,
501 pause_main_pump_2,
502 pause_aux_pump_1,
503 pause_aux_pump_2,
504 feed_duration,
505 feed_skimmer,
506 feed_main_pump_1,
507 feed_main_pump_2,
508 feed_aux_pump_1,
509 feed_aux_pump_2,
510 },
511 ) {
512 Ok(c) => c,
513 Err(e) => {
514 return Err(SqlInterfaceError::SingleFeedpatternRequestFailure {
515 location: module_path!().to_string(),
516 query: sql_query,
517 profile_id,
518 source: e,
519 });
520 }
521 };
522
523 if single_feedpattern_entry_array.is_empty() {
525 return Err(SqlInterfaceError::SingleFeedpatternRequestEmptyResponse(
526 module_path!().to_string(),
527 sql_query,
528 profile_id,
529 ));
530 }
531
532 if single_feedpattern_entry_array.len() > 1 {
534 return Err(SqlInterfaceError::SingleFeedpatternRequestNoSingleResponse(
535 module_path!().to_string(),
536 sql_query,
537 profile_id,
538 ));
539 }
540
541 let feed_phase = &single_feedpattern_entry_array[0];
542
543 if feed_phase.pause_duration < 0 || feed_phase.feed_duration < 0 {
545 return Err(SqlInterfaceError::DatabaseFeedPhaseDurationNegative(
546 module_path!().to_string(),
547 feed_phase.pause_duration,
548 feed_phase.feed_duration,
549 ));
550 }
551 Ok(feed_phase.clone())
552 }
553}
554
555#[cfg(test)]
556pub mod tests {
557 use chrono::{Duration, Local, NaiveDate, NaiveDateTime};
558 use mysql::{params, prelude::Queryable};
559
560 use crate::database::database_interface_feed_trait::DatabaseInterfaceFeedTrait;
561 use crate::database::sql_interface_feed::{FeedScheduleEntry, SqlInterfaceFeed};
562 use crate::database::sql_query_strings;
563 use crate::database::sql_query_strings::{
564 SQL_TABLE_FEEDLOG, SQL_TABLE_FEEDPATTERNS, SQL_TABLE_FEEDSCHEDULE,
565 };
566 use crate::database::{sql_interface::SqlInterface, sql_interface_error::SqlInterfaceError};
567 use crate::utilities::config::{read_config_file_with_test_database, ConfigData};
568
569 pub struct SqlFeedEvent {
570 timestamp: String,
571 feeder_run_time: f32,
572 feed_profile_name: String,
573 feed_profile_id: i32,
574 }
575
576 pub struct FeedEvent {
577 timestamp: NaiveDateTime,
578 feeder_run_time: f32,
579 feed_profile_name: String,
580 feed_profile_id: i32,
581 }
582
583 #[cfg(test)]
584 pub fn insert_feed_patterns(
592 sql_interface: &mut SqlInterface,
593 sql_interface_feed: &mut SqlInterfaceFeed,
594 ) {
595 match SqlInterface::truncate_table(sql_interface, SQL_TABLE_FEEDPATTERNS.to_string()) {
597 Ok(_) => {}
598 Err(e) => panic!("Could not prepare test case: {e:?}"),
599 }
600 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST.to_string();
602 match sql_interface_feed.conn.exec_drop::<_, _>(
603 sql_query_string,
604 params! {
605 "profile_id" => 1, "profile_name" => "test"
606 },
607 ) {
608 Ok(_) => {}
609 Err(e) => {
610 panic!("Error when inserting test feed pattern #1 into database: {e:?}");
611 }
612 };
613 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST2.to_string();
615 match sql_interface_feed.conn.exec_drop::<_, _>(
616 sql_query_string,
617 params! {
618 "profile_id" => 2,
619 },
620 ) {
621 Ok(_) => {}
622 Err(e) => {
623 panic!("Error when inserting test feed pattern #2 into database: {e:?}");
624 }
625 };
626 }
627
628 #[test]
629 fn test_sql_interface_feed_new() {
636 let config: ConfigData = read_config_file_with_test_database(
638 "/config/aquarium_control_test_generic.toml".to_string(),
639 59,
640 );
641 println!("Testing with database {}", config.sql_interface.db_name);
642 let mut sql_interface: SqlInterface = SqlInterface::new(config.sql_interface)
643 .expect("Initialization of SQL interface for test failed.");
644
645 println!("* Testing new() with valid data (Happy Path)...");
647 SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDPATTERNS.to_string())
648 .unwrap();
649 SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDSCHEDULE.to_string())
650 .unwrap();
651 SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDLOG.to_string()).unwrap();
652
653 let mut conn = sql_interface.get_connection().unwrap();
655 conn.exec_drop(
656 sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST,
657 params! { "profile_id" => 1, "profile_name" => "test_profile1" },
658 )
659 .unwrap();
660 conn.exec_drop(
661 sql_query_strings::SQL_QUERY_WRITE_FEED_SCHEDULE_ENTRY,
662 params! { "timestamp" => "2024-01-01 12:00:00", "profile_id" => 1, "profile_name" => "test_profile1", "is_weekly" => false, "is_daily" => true },
663 ).unwrap();
664 conn.exec_drop(
665 sql_query_strings::SQL_QUERY_WRITE_FEED_EVENT,
666 params! { "timestamp" => Local::now().naive_local(), "feeder_run_time" => 1.0, "profile_name" => "test_profile1", "profile_id" => 1 },
667 ).unwrap();
668
669 let result = SqlInterfaceFeed::new(conn, 10, 10, 10);
670 assert!(
671 result.is_ok(),
672 "Expected new() to succeed on happy path, but it failed: {:?}",
673 result.err()
674 );
675 println!("* Succeeded: Happy path initialization is successful.");
676
677 println!("* Testing new() with too many rows in feedpatterns...");
679 SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDPATTERNS.to_string())
680 .unwrap();
681 let mut conn = sql_interface.get_connection().unwrap();
682 conn.exec_drop(
683 sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST,
684 params! { "profile_id" => 1, "profile_name" => "row1" },
685 )
686 .unwrap();
687 conn.exec_drop(
688 sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST,
689 params! { "profile_id" => 2, "profile_name" => "row2" },
690 )
691 .unwrap();
692
693 let result = SqlInterfaceFeed::new(conn, 1, 10, 10);
695 assert!(
696 matches!(
697 result,
698 Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
699 _,
700 _,
701 _,
702 _
703 ))
704 ),
705 "Expected row limit error for feedpatterns, but got {:?}",
706 result
707 );
708 println!("* Succeeded: Initialization fails if feedpatterns exceeds row limit.");
709
710 println!("* Testing new() with deactivated row limit checks (limits = 0)...");
712 let conn = sql_interface.get_connection().unwrap();
714 let result = SqlInterfaceFeed::new(conn, 0, 0, 0);
716 assert!(
717 result.is_ok(),
718 "Expected new() to succeed with deactivated checks, but it failed: {:?}",
719 result.err()
720 );
721 println!("* Succeeded: Initialization passes when row limit checks are deactivated.");
722 }
723
724 #[test]
725 pub fn test_sql_interface_feedpattern() {
728 let config: ConfigData = read_config_file_with_test_database(
729 "/config/aquarium_control_test_generic.toml".to_string(),
730 33,
731 );
732 println!("Testing with database {}", config.sql_interface.db_name);
733 let max_rows_feed_pattern = config.sql_interface.max_rows_feed_pattern;
734 let max_rows_feed_schedule = config.sql_interface.max_rows_feed_schedule;
735 let max_rows_feed_log = config.sql_interface.max_rows_feed_log;
736 let mut sql_interface: SqlInterface = SqlInterface::new(config.sql_interface)
737 .expect("Initialization of SQL interface for test failed.");
738 let mut sql_interface_feed = SqlInterfaceFeed::new(
739 sql_interface.get_connection().unwrap(),
740 max_rows_feed_pattern,
741 max_rows_feed_schedule,
742 max_rows_feed_log,
743 )
744 .unwrap();
745
746 match SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDPATTERNS.to_string()) {
748 Ok(_) => {}
749 Err(e) => panic!("Could not prepare test case: {e:?}"),
750 }
751 match sql_interface_feed.get_single_feedpattern_from_database(1) {
752 Ok(_) => {
753 panic!("Call to get_single_feedpattern_from_database should have failed.");
754 }
755 Err(e) => {
756 assert!(matches!(
757 e,
758 SqlInterfaceError::SingleFeedpatternRequestEmptyResponse(_, _, _)
759 ));
760 }
761 }
762 println!("* checking retrieving of Feed pattern (FAIL) succeeded.");
763 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEEDPATTERN_TEST.to_string();
768 match sql_interface_feed.conn.exec_drop::<_, _>(
769 sql_query_string.clone(),
770 params! {
771 "profile_id" => 1, "profile_name" => "test",
772 },
773 ) {
774 Ok(_) => {}
775 Err(e) => {
776 panic!("Error when inserting feed pattern into database: {e:?}");
777 }
778 };
779 match sql_interface_feed.get_single_feedpattern_from_database(1) {
780 Ok(c) => {
781 assert_eq!(c.profile_id, 1);
782 assert_eq!(c.profile_name, "test".to_string());
783 assert_eq!(c.feedphases[0].pause_duration, 0);
784 assert_eq!(c.feedphases[0].pause_skimmer, true);
785 assert_eq!(c.feedphases[0].pause_main_pump_1, false);
786 assert_eq!(c.feedphases[0].pause_main_pump_2, false);
787 assert_eq!(c.feedphases[0].pause_aux_pump_1, false);
788 assert_eq!(c.feedphases[0].pause_aux_pump_2, false);
789 assert_eq!(c.feedphases[0].feed_duration, 1);
790 assert_eq!(c.feedphases[0].feed_skimmer, false);
791 assert_eq!(c.feedphases[0].feed_main_pump_1, true);
792 assert_eq!(c.feedphases[0].feed_main_pump_2, false);
793 assert_eq!(c.feedphases[0].feed_aux_pump_1, false);
794 assert_eq!(c.feedphases[0].feed_aux_pump_2, false);
795
796 assert_eq!(c.feedphases[1].pause_duration, 2);
797 assert_eq!(c.feedphases[1].pause_skimmer, false);
798 assert_eq!(c.feedphases[1].pause_main_pump_1, false);
799 assert_eq!(c.feedphases[1].pause_main_pump_2, true);
800 assert_eq!(c.feedphases[1].pause_aux_pump_1, false);
801 assert_eq!(c.feedphases[1].pause_aux_pump_2, false);
802 assert_eq!(c.feedphases[1].feed_duration, 3);
803 assert_eq!(c.feedphases[1].feed_skimmer, false);
804 assert_eq!(c.feedphases[1].feed_main_pump_1, false);
805 assert_eq!(c.feedphases[1].feed_main_pump_2, false);
806 assert_eq!(c.feedphases[1].feed_aux_pump_1, true);
807 assert_eq!(c.feedphases[1].feed_aux_pump_2, false);
808
809 assert_eq!(c.feedphases[2].pause_duration, 4);
810 assert_eq!(c.feedphases[2].pause_skimmer, false);
811 assert_eq!(c.feedphases[2].pause_main_pump_1, false);
812 assert_eq!(c.feedphases[2].pause_main_pump_2, false);
813 assert_eq!(c.feedphases[2].pause_aux_pump_1, false);
814 assert_eq!(c.feedphases[2].pause_aux_pump_2, true);
815 assert_eq!(c.feedphases[2].feed_duration, 5);
816 assert_eq!(c.feedphases[2].feed_skimmer, true);
817 assert_eq!(c.feedphases[2].feed_main_pump_1, true);
818 assert_eq!(c.feedphases[2].feed_main_pump_2, false);
819 assert_eq!(c.feedphases[2].feed_aux_pump_1, false);
820 assert_eq!(c.feedphases[2].feed_aux_pump_2, false);
821
822 assert_eq!(c.feedphases[3].pause_duration, 6);
823 assert_eq!(c.feedphases[3].pause_skimmer, true);
824 assert_eq!(c.feedphases[3].pause_main_pump_1, false);
825 assert_eq!(c.feedphases[3].pause_main_pump_2, true);
826 assert_eq!(c.feedphases[3].pause_aux_pump_1, false);
827 assert_eq!(c.feedphases[3].pause_aux_pump_2, false);
828 assert_eq!(c.feedphases[3].feed_duration, 7);
829 assert_eq!(c.feedphases[3].feed_skimmer, true);
830 assert_eq!(c.feedphases[3].feed_main_pump_1, false);
831 assert_eq!(c.feedphases[3].feed_main_pump_2, false);
832 assert_eq!(c.feedphases[3].feed_aux_pump_1, true);
833 assert_eq!(c.feedphases[3].feed_aux_pump_2, false);
834
835 assert_eq!(c.feedphases[4].pause_duration, 8);
836 assert_eq!(c.feedphases[4].pause_skimmer, true);
837 assert_eq!(c.feedphases[4].pause_main_pump_1, false);
838 assert_eq!(c.feedphases[4].pause_main_pump_2, false);
839 assert_eq!(c.feedphases[4].pause_aux_pump_1, false);
840 assert_eq!(c.feedphases[4].pause_aux_pump_2, true);
841 assert_eq!(c.feedphases[4].feed_duration, 9);
842 assert_eq!(c.feedphases[4].feed_skimmer, true);
843 assert_eq!(c.feedphases[4].feed_main_pump_1, true);
844 assert_eq!(c.feedphases[4].feed_main_pump_2, true);
845 assert_eq!(c.feedphases[4].feed_aux_pump_1, false);
846 assert_eq!(c.feedphases[4].feed_aux_pump_2, false);
847
848 assert_eq!(c.feedphases[5].pause_duration, 10);
849 assert_eq!(c.feedphases[5].pause_skimmer, true);
850 assert_eq!(c.feedphases[5].pause_main_pump_1, true);
851 assert_eq!(c.feedphases[5].pause_main_pump_2, true);
852 assert_eq!(c.feedphases[5].pause_aux_pump_1, false);
853 assert_eq!(c.feedphases[5].pause_aux_pump_2, false);
854 assert_eq!(c.feedphases[5].feed_duration, 11);
855 assert_eq!(c.feedphases[5].feed_skimmer, true);
856 assert_eq!(c.feedphases[5].feed_main_pump_1, true);
857 assert_eq!(c.feedphases[5].feed_main_pump_2, false);
858 assert_eq!(c.feedphases[5].feed_aux_pump_1, false);
859 assert_eq!(c.feedphases[5].feed_aux_pump_2, true);
860
861 assert_eq!(c.feedphases[6].pause_duration, 12);
862 assert_eq!(c.feedphases[6].pause_skimmer, true);
863 assert_eq!(c.feedphases[6].pause_main_pump_1, true);
864 assert_eq!(c.feedphases[6].pause_main_pump_2, true);
865 assert_eq!(c.feedphases[6].pause_aux_pump_1, true);
866 assert_eq!(c.feedphases[6].pause_aux_pump_2, false);
867 assert_eq!(c.feedphases[6].feed_duration, 13);
868 assert_eq!(c.feedphases[6].feed_skimmer, true);
869 assert_eq!(c.feedphases[6].feed_main_pump_1, true);
870 assert_eq!(c.feedphases[6].feed_main_pump_2, true);
871 assert_eq!(c.feedphases[6].feed_aux_pump_1, false);
872 assert_eq!(c.feedphases[6].feed_aux_pump_2, true);
873
874 assert_eq!(c.feedphases[7].pause_duration, 14);
875 assert_eq!(c.feedphases[7].pause_skimmer, true);
876 assert_eq!(c.feedphases[7].pause_main_pump_1, true);
877 assert_eq!(c.feedphases[7].pause_main_pump_2, true);
878 assert_eq!(c.feedphases[7].pause_aux_pump_1, true);
879 assert_eq!(c.feedphases[7].pause_aux_pump_2, true);
880 assert_eq!(c.feedphases[7].feed_duration, 15);
881 assert_eq!(c.feedphases[7].feed_skimmer, true);
882 assert_eq!(c.feedphases[7].feed_main_pump_1, true);
883 assert_eq!(c.feedphases[7].feed_main_pump_2, true);
884 assert_eq!(c.feedphases[7].feed_aux_pump_1, true);
885 assert_eq!(c.feedphases[7].feed_aux_pump_2, false);
886
887 assert_eq!(c.feedphases[8].pause_duration, 16);
888 assert_eq!(c.feedphases[8].pause_skimmer, true);
889 assert_eq!(c.feedphases[8].pause_main_pump_1, true);
890 assert_eq!(c.feedphases[8].pause_main_pump_2, true);
891 assert_eq!(c.feedphases[8].pause_aux_pump_1, false);
892 assert_eq!(c.feedphases[8].pause_aux_pump_2, false);
893 assert_eq!(c.feedphases[8].feed_duration, 17);
894 assert_eq!(c.feedphases[8].feed_skimmer, true);
895 assert_eq!(c.feedphases[8].feed_main_pump_1, true);
896 assert_eq!(c.feedphases[8].feed_main_pump_2, false);
897 assert_eq!(c.feedphases[8].feed_aux_pump_1, false);
898 assert_eq!(c.feedphases[8].feed_aux_pump_2, false);
899
900 assert_eq!(c.feedphases[9].pause_duration, 18);
901 assert_eq!(c.feedphases[9].pause_skimmer, true);
902 assert_eq!(c.feedphases[9].pause_main_pump_1, false);
903 assert_eq!(c.feedphases[9].pause_main_pump_2, false);
904 assert_eq!(c.feedphases[9].pause_aux_pump_1, false);
905 assert_eq!(c.feedphases[9].pause_aux_pump_2, false);
906 assert_eq!(c.feedphases[9].feed_duration, 19);
907 assert_eq!(c.feedphases[9].feed_skimmer, false);
908 assert_eq!(c.feedphases[9].feed_main_pump_1, false);
909 assert_eq!(c.feedphases[9].feed_main_pump_2, false);
910 assert_eq!(c.feedphases[9].feed_aux_pump_1, false);
911 assert_eq!(c.feedphases[9].feed_aux_pump_2, false);
912 }
913 Err(e) => {
914 panic!("Call to get_single_feedpattern_from_database has failed: {e:?}.");
915 }
916 }
917 println!("* checking retrieving of feed pattern succeeded.");
918
919 match sql_interface_feed.conn.exec_drop::<_, _>(
920 sql_query_string,
921 params! {
922 "profile_id" => 2, "profile_name" => "test2"
923 },
924 ) {
925 Ok(_) => {}
926 Err(e) => {
927 panic!("Error when inserting feed pattern into database: {e:?}");
928 }
929 };
930 let test_result: Result<SqlInterfaceFeed, SqlInterfaceError> =
931 SqlInterfaceFeed::new(sql_interface.get_connection().unwrap(), 1, 100, 0);
932 assert!(matches!(
933 test_result,
934 Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
935 _,
936 _,
937 2,
938 1
939 ))
940 ));
941 println!("* checking of feed pattern max rows limitation succeeded.");
942
943 }
945
946 #[test]
947 pub fn test_sql_interface_feedlog() {
950 let config: ConfigData = read_config_file_with_test_database(
951 "/config/aquarium_control_test_generic.toml".to_string(),
952 34,
953 );
954 println!("Testing with database {}", config.sql_interface.db_name);
955 let max_rows_feed_pattern = config.sql_interface.max_rows_feed_pattern;
956 let max_rows_feed_schedule = config.sql_interface.max_rows_feed_schedule;
957 let max_rows_feed_log = config.sql_interface.max_rows_feed_log;
958 let mut sql_interface: SqlInterface = SqlInterface::new(config.sql_interface)
959 .expect("Initialization of SQL interface for test failed.");
960 let mut sql_interface_feed = SqlInterfaceFeed::new(
961 sql_interface.get_connection().unwrap(),
962 max_rows_feed_pattern,
963 max_rows_feed_schedule,
964 max_rows_feed_log,
965 )
966 .unwrap();
967
968 match SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDLOG.to_string()) {
970 Ok(_) => {}
971 Err(e) => panic!("Could not prepare test case: {e:?}"),
972 }
973 let feed_timestamp =
974 NaiveDateTime::parse_from_str("2022-04-11 11:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
975 match sql_interface_feed.insert_feed_event(feed_timestamp, 5.5, "Test".to_string(), 1) {
976 Ok(_) => {}
977 Err(e) => panic!("Could not insert feed event into database: {e:?}"),
978 }
979 let single_feed_event_entry_array = match sql_interface_feed.conn.query_map(
981 sql_query_strings::SQL_QUERY_READ_LAST_FEED_EVENT,
982 |(timestamp, feeder_run_time, feed_profile_name, feed_profile_id)| SqlFeedEvent {
983 timestamp,
984 feeder_run_time,
985 feed_profile_name,
986 feed_profile_id,
987 },
988 ) {
989 Ok(c) => c,
990 Err(e) => {
991 panic!("Could not read feed event from data base: {e:?}");
992 }
993 };
994 assert_eq!(single_feed_event_entry_array.len(), 1);
996 let feed_event = FeedEvent {
998 timestamp: NaiveDateTime::parse_from_str(
999 single_feed_event_entry_array[0].timestamp.as_str(),
1000 "%Y-%m-%d %H:%M:%S",
1001 )
1002 .expect("conversion of timestamp to NaiveDate failed."),
1003 feeder_run_time: single_feed_event_entry_array[0].feeder_run_time,
1004 feed_profile_name: single_feed_event_entry_array[0].feed_profile_name.clone(),
1005 feed_profile_id: single_feed_event_entry_array[0].feed_profile_id,
1006 };
1007 assert_eq!(feed_event.timestamp, feed_timestamp);
1008 assert_eq!(feed_event.feeder_run_time, 5.5);
1009 assert_eq!(feed_event.feed_profile_name, "Test");
1010 assert_eq!(feed_event.feed_profile_id, 1);
1011 println!("* checking inserting of feed event into log succeeded.");
1012 let feed_timestamp =
1016 NaiveDateTime::parse_from_str("2022-04-12 11:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
1017 match sql_interface_feed.insert_feed_event(feed_timestamp, 5.5, "Test2".to_string(), 2) {
1018 Ok(_) => {}
1019 Err(e) => panic!("Could not insert feed event into database: {e:?}"),
1020 }
1021 let test_result = SqlInterfaceFeed::new(sql_interface.get_connection().unwrap(), 0, 0, 1);
1022 assert!(matches!(
1023 test_result,
1024 Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
1025 _,
1026 _,
1027 _,
1028 _
1029 ))
1030 ));
1031 }
1032
1033 #[test]
1034 pub fn test_sql_interface_feedschedule() {
1037 let config: ConfigData = read_config_file_with_test_database(
1038 "/config/aquarium_control_test_generic.toml".to_string(),
1039 35,
1040 );
1041 println!("Testing with database {}", config.sql_interface.db_name);
1042 let max_rows_feed_pattern = config.sql_interface.max_rows_feed_pattern;
1043 let max_rows_feed_schedule = config.sql_interface.max_rows_feed_schedule;
1044 let max_rows_feed_log = config.sql_interface.max_rows_feed_log;
1045 let mut sql_interface: SqlInterface = SqlInterface::new(config.sql_interface)
1046 .expect("Initialization of SQL interface for test failed.");
1047 let mut sql_interface_feed = SqlInterfaceFeed::new(
1048 sql_interface.get_connection().unwrap(),
1049 max_rows_feed_pattern,
1050 max_rows_feed_schedule,
1051 max_rows_feed_log,
1052 )
1053 .unwrap();
1054 match SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDSCHEDULE.to_string()) {
1056 Ok(_) => {}
1057 Err(e) => panic!("Could not prepare test case: {e:?}"),
1058 }
1059 match sql_interface_feed.get_past_feedschedule_entries_from_database() {
1060 Ok(c) => {
1061 assert_eq!(c, None);
1062 }
1063 Err(_) => {
1064 panic!("Error when trying to load feed schedule entry from empty table.");
1065 }
1066 }
1067 println!("* reading from empty feed schedule (no result) succeeded.");
1068 let timestamp_reference = "2024-04-10 14:00:00";
1072 let profile_id_reference = 1;
1073 let profile_name_reference = "test";
1074 let repeat_daily_reference = true;
1075
1076 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEED_SCHEDULE_ENTRY.to_string();
1077 match sql_interface_feed.conn.exec_drop::<_, _>(
1078 sql_query_string,
1079 params! {
1080 "timestamp" => timestamp_reference,
1081 "profile_id" => profile_id_reference,
1082 "profile_name" => profile_name_reference,
1083 "is_weekly" => false,
1084 "is_daily" => repeat_daily_reference,
1085 },
1086 ) {
1087 Ok(_) => {}
1088 Err(e) => {
1089 panic!("Error when inserting feed schedule entry into database: {e:?}");
1090 }
1091 };
1092
1093 let feedschedule_entry_before_update = match sql_interface_feed
1094 .get_past_feedschedule_entries_from_database()
1095 {
1096 Ok(feedschedule_entry_vector_opt) => match feedschedule_entry_vector_opt {
1097 Some(feedschedule_entry_vector) => {
1098 if feedschedule_entry_vector.len() != 1 {
1099 panic!(
1100 "expected to find one feed schedule entry in data base, found {}",
1101 feedschedule_entry_vector.len()
1102 )
1103 }
1104 let feedschedule_entry = feedschedule_entry_vector.get(0).cloned().unwrap();
1105 assert_eq!(
1106 feedschedule_entry.timestamp,
1107 NaiveDateTime::parse_from_str(timestamp_reference, "%Y-%m-%d %H:%M:%S")
1108 .unwrap()
1109 );
1110 println!(
1111 "timestamp read from the database: {}",
1112 feedschedule_entry.timestamp
1113 );
1114 assert_eq!(feedschedule_entry.profile_id, profile_id_reference);
1115 assert_eq!(feedschedule_entry.profile_name, profile_name_reference);
1116 assert_eq!(feedschedule_entry.repeat_daily, repeat_daily_reference);
1117 feedschedule_entry
1118 }
1119 None => {
1120 panic!("SQL database returned empty result although feed schedule entry was inserted.");
1121 }
1122 },
1123 Err(_) => {
1124 panic!("Error occurred when trying to load feed schedule entry.");
1125 }
1126 };
1127 println!("* reading existing entry from feed schedule succeeded.");
1128 println!(
1132 "Feed schedule entry before database update: {}",
1133 feedschedule_entry_before_update
1134 );
1135
1136 let local_date_tomorrow: NaiveDate =
1138 (Local::now().naive_local() + Duration::days(1)).into();
1139 let reference_timestamp_tomorrow_same_time = NaiveDateTime::new(
1140 local_date_tomorrow,
1141 feedschedule_entry_before_update.timestamp.time(),
1142 );
1143
1144 let mut feedschedule_entries_before_update: Vec<FeedScheduleEntry> = Vec::new();
1145 feedschedule_entries_before_update.push(feedschedule_entry_before_update);
1146
1147 match sql_interface_feed
1148 .update_feedschedule_entries_in_database(&mut feedschedule_entries_before_update)
1149 {
1150 Ok(()) => {}
1151 Err(e) => {
1152 panic!("Error occurred when trying to update feedschedule entry which is marked for repetition: {e:?}");
1153 }
1154 }
1155
1156 let mut feedschedule_entry_after_update = match sql_interface_feed
1157 .get_future_feedschedule_entry_from_database()
1158 {
1159 Ok(c) => match c {
1160 Some(d) => {
1161 assert_eq!(d.timestamp, reference_timestamp_tomorrow_same_time);
1162 assert_eq!(d.profile_id, profile_id_reference);
1163 assert_eq!(d.profile_name, profile_name_reference);
1164 assert_eq!(d.repeat_daily, repeat_daily_reference);
1165 d
1166 }
1167 None => {
1168 panic!("SQL database returned empty result although feed schedule entry was inserted.");
1169 }
1170 },
1171 Err(_) => {
1172 panic!("Error occurred when trying to load feed schedule entry.");
1173 }
1174 };
1175 println!(
1176 "Feed schedule entry after database update: {}",
1177 feedschedule_entry_after_update
1178 );
1179 println!("* update existing entry from feed schedule succeeded.");
1180 feedschedule_entry_after_update.repeat_daily = false;
1185 let mut feedschedule_entries_after_update: Vec<FeedScheduleEntry> = Vec::new();
1186 feedschedule_entries_after_update.push(feedschedule_entry_after_update);
1187
1188 match sql_interface_feed
1189 .update_feedschedule_entries_in_database(&mut feedschedule_entries_after_update)
1190 {
1191 Ok(()) => {}
1192 Err(e) => {
1193 panic!("Error occurred when trying to update feedschedule entry which is not marked for repetition: {e:?}");
1194 }
1195 }
1196
1197 match sql_interface_feed.get_past_feedschedule_entries_from_database() {
1198 Ok(c) => {
1199 assert_eq!(c, None);
1200 }
1201 Err(_) => {
1202 panic!("Error when trying to load feed schedule entry from empty table.");
1203 }
1204 };
1205 println!("* deleting existing entry from feed schedule succeeded.");
1206 match SqlInterface::truncate_table(&mut sql_interface, SQL_TABLE_FEEDSCHEDULE.to_string()) {
1211 Ok(_) => {}
1212 Err(e) => panic!("Could not prepare test case: {e:?}"),
1213 }
1214
1215 let timestamp_0 = "2024-04-10 14:00:00";
1216 let profile_id_0 = 0;
1217 let profile_name_0 = "test0";
1218 let repeat_daily_0 = true;
1219
1220 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEED_SCHEDULE_ENTRY.to_string();
1221 match sql_interface_feed.conn.exec_drop::<_, _>(
1222 sql_query_string,
1223 params! {
1224 "timestamp" => timestamp_0,
1225 "profile_id" => profile_id_0,
1226 "profile_name" => profile_name_0,
1227 "is_weekly" => false,
1228 "is_daily" => repeat_daily_0,
1229 },
1230 ) {
1231 Ok(_) => {}
1232 Err(e) => {
1233 panic!("Error when inserting feed schedule entry into database: {e:?}");
1234 }
1235 };
1236
1237 let timestamp_1 = "2024-04-10 15:00:00";
1238 let profile_id_1 = 1;
1239 let profile_name_1 = "test1";
1240 let repeat_daily_1 = true;
1241
1242 let sql_query_string = sql_query_strings::SQL_QUERY_WRITE_FEED_SCHEDULE_ENTRY.to_string();
1243 match sql_interface_feed.conn.exec_drop::<_, _>(
1244 sql_query_string,
1245 params! {
1246 "timestamp" => timestamp_1,
1247 "profile_id" => profile_id_1,
1248 "profile_name" => profile_name_1,
1249 "is_weekly" => false,
1250 "is_daily" => repeat_daily_1,
1251 },
1252 ) {
1253 Ok(_) => {}
1254 Err(e) => {
1255 panic!("Error when inserting feed schedule entry into database: {e:?}");
1256 }
1257 };
1258
1259 let mut feedschedule_entries_before_update = match sql_interface_feed
1260 .get_past_feedschedule_entries_from_database()
1261 {
1262 Ok(feedschedule_entry_vector_opt) => match feedschedule_entry_vector_opt {
1263 Some(feedschedule_entry_vector) => feedschedule_entry_vector,
1264 None => {
1265 panic!("SQL database returned empty result although feed schedule entries were inserted.");
1266 }
1267 },
1268 Err(_) => {
1269 panic!("Error occurred when trying to load feed schedule entry.");
1270 }
1271 };
1272
1273 let local_date_tomorrow: NaiveDate =
1274 (Local::now().naive_local() + Duration::days(1)).into();
1275
1276 let reference_timestamp0_tomorrow_same_time = NaiveDateTime::new(
1277 local_date_tomorrow,
1278 feedschedule_entries_before_update
1279 .get(1)
1280 .unwrap()
1281 .timestamp
1282 .time(),
1283 );
1284 let reference_timestamp1_tomorrow_same_time = NaiveDateTime::new(
1285 local_date_tomorrow,
1286 feedschedule_entries_before_update
1287 .get(0)
1288 .unwrap()
1289 .timestamp
1290 .time(),
1291 );
1292
1293 match sql_interface_feed
1294 .update_feedschedule_entries_in_database(&mut feedschedule_entries_before_update)
1295 {
1296 Ok(()) => {}
1297 Err(e) => {
1298 panic!("Error occurred when trying to update feedschedule entry which is marked for repetition: {e:?}");
1299 }
1300 }
1301
1302 match sql_interface_feed.get_future_feedschedule_entries_from_database() {
1303 Ok(c) => match c {
1304 Some(d) => {
1305 assert_eq!(d.len(), 2);
1306 assert_eq!(
1307 d.get(0).unwrap().timestamp,
1308 reference_timestamp0_tomorrow_same_time
1309 );
1310 assert_eq!(d.get(0).unwrap().profile_id, profile_id_0);
1311 assert_eq!(d.get(0).unwrap().profile_name, profile_name_0);
1312 assert_eq!(d.get(0).unwrap().repeat_daily, repeat_daily_0);
1313 assert_eq!(
1314 d.get(1).unwrap().timestamp,
1315 reference_timestamp1_tomorrow_same_time
1316 );
1317 assert_eq!(d.get(1).unwrap().profile_id, profile_id_1);
1318 assert_eq!(d.get(1).unwrap().profile_name, profile_name_1);
1319 assert_eq!(d.get(1).unwrap().repeat_daily, repeat_daily_1);
1320 }
1321 None => {
1322 panic!("SQL database returned empty result although feed schedule entry was inserted.");
1323 }
1324 },
1325 Err(_) => {
1326 panic!("Error occurred when trying to load feed schedule entry.");
1327 }
1328 };
1329 println!("* update of existing entries from feed schedule succeeded.");
1330 let test_result: Result<SqlInterfaceFeed, SqlInterfaceError> =
1333 SqlInterfaceFeed::new(sql_interface.get_connection().unwrap(), 100, 1, 0);
1334 assert!(matches!(
1335 test_result,
1336 Err(SqlInterfaceError::DatabaseFeedTableContainsTooManyRows(
1337 _,
1338 _,
1339 2,
1340 1
1341 ))
1342 ));
1343 println!("* checking of feed schedule max rows limitation succeeded.");
1344 }
1345}