aquarium_control/database/
sql_interface_midnight.rs

1/* Copyright 2025 Uwe Martin
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8*/
9
10//! Provides a trait and implementation for calculating the time until midnight using the database clock.
11//!
12//! This module defines an abstraction (`SqlInterfaceMidnightCalculatorTrait`) and a concrete
13//! implementation (`SqlInterfaceMidnightCalculator`) for determining the number of seconds
14//! remaining until the next midnight. The primary purpose of this module is to decouple
15//! time-sensitive components (like daily statistics aggregators) from the specific details
16//! of the database, adhering to the Dependency Inversion Principle.
17//!
18//! ## Key Components
19//!
20//! - **`SqlInterfaceMidnightCalculatorTrait`**: An abstraction that defines the contract for
21//!   any component capable of calculating the duration until midnight. This allows for
22//!   dependency injection, making it possible to use a mock implementation for testing.
23//!
24//! - **`SqlInterfaceMidnightCalculator`**: The concrete implementation of the trait. It holds
25//!   a database connection and executes an SQL query to get a precise time from the
26//!   database server's clock.
27//!
28//! ## Design and Purpose
29//!
30//! By relying on the database server for the current time, the application ensures that
31//! all time-based calculations are consistent, even if the application is running on a
32//! machine with a desynchronized clock.
33//!
34//! The use of a trait is a crucial architectural choice. It allows higher-level components,
35//! such as `SqlInterfaceHeatingStats`, to depend on the `SqlInterfaceMidnightCalculatorTrait`
36//! abstraction rather than the concrete `SqlInterfaceMidnightCalculator` struct. This
37//! makes the system more modular and significantly easier to test, as a mock calculator
38//! can be provided during unit tests without needing a live database.
39
40use crate::database::sql_interface::SqlInterface;
41use crate::database::sql_interface_error::SqlInterfaceError;
42use crate::database::sql_query_strings;
43use mysql::PooledConn;
44
45/// Trait for calculation the time interval until midnight in seconds.
46/// This trait allows running the main control with a mock implementation for testing.
47pub trait SqlInterfaceMidnightCalculatorTrait {
48    fn get_duration_until_midnight(&mut self) -> Result<i64, SqlInterfaceError>;
49}
50
51/// Struct holds connection data to the SQL database and provides implementation for calculating
52/// the time in seconds until midnight.
53pub struct SqlInterfaceMidnightCalculator {
54    /// Connection to the database
55    pub conn: PooledConn,
56}
57
58impl SqlInterfaceMidnightCalculatorTrait for SqlInterfaceMidnightCalculator {
59    /// Queries the database to determine the number of seconds remaining until the next midnight.
60    ///
61    /// This function executes a standard SQL query to retrieve a precise count of seconds
62    /// from the current database time until 00:00:00 of the upcoming day.
63    ///
64    /// # Returns
65    /// A `Result` containing the number of seconds (as an `i64`) remaining until midnight on success.
66    ///
67    /// # Errors
68    /// This function will return an `Err` variant of `SqlInterfaceError` if the underlying
69    /// call to `get_single_integer_from_database` fails. This can happen if:
70    /// - The database connection is lost or invalid.
71    /// - The SQL query fails to execute for other reasons (e.g., syntax error, permissions).
72    /// - The query does not return exactly one row and one column containing an integer.
73    fn get_duration_until_midnight(&mut self) -> Result<i64, SqlInterfaceError> {
74        SqlInterface::get_single_integer_from_database(
75            &mut self.conn,
76            sql_query_strings::SQL_QUERY_READ_DURATION_UNTIL_MIDNIGHT,
77        )
78    }
79}
80
81impl SqlInterfaceMidnightCalculator {
82    /// Creates a new `SqlInterfaceMidnightCalculator` instance.
83    ///
84    /// This constructor initializes the calculator with an established database connection,
85    /// which it uses to perform time-related queries, such as determining time until midnight.
86    ///
87    /// # Arguments
88    /// * `conn` - An active database connection pool object.
89    ///
90    /// # Returns
91    /// A new `SqlInterfaceMidnightCalculator` struct.
92    pub fn new(conn: PooledConn) -> SqlInterfaceMidnightCalculator {
93        SqlInterfaceMidnightCalculator { conn }
94    }
95}