aquarium_control/database/
sql_interface_error.rs

1/* Copyright 2024 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//! Defines the custom error type for all database-related operations.
11//!
12//! This module centralizes error handling for the entire database layer by providing a
13//! single, comprehensive enum, `SqlInterfaceError`. By using the `thiserror` crate,
14//! it automatically derives the `std::error::Error` trait and generates clear,
15//! user-friendly `Display` implementations for each error variant.
16//!
17//! ## Key Features
18//!
19//! - **Centralized Error Handling**: All potential failures within the `sql_interface`
20//!   and its submodules are codified here. This creates a single source of truth for
21//!   what can go wrong with database interactions.
22//!
23//! - **Rich Context**: Most error variants capture valuable context, such as
24//!   - `location`: The module path where the error occurred, provided by `module_path!()`.
25//!   - `query`: The specific SQL query string that failed.
26//!   - Other relevant parameters (e.g., `pump_id`, version numbers).
27//!   This makes debugging significantly easier by pinpointing the exact location and
28//!   cause of a failure.
29//!
30//! - **Error Chaining**: The `#[source]` attribute is used to wrap the underlying
31//!   error (e.g., a `mysql::Error` or `chrono::ParseError`), preserving the original
32//!   error chain for in-depth analysis.
33//!
34//! - **Clarity and Specificity**: Instead of generic "query failed" errors, this enum
35//!   provides specific variants for different failure modes, such as `WaitTimeoutTooLow`,
36//!   `RequiredTablesNotExisting`, or `SingleStringRequestNoSingleResponse`.
37//!
38//! ## Error Categories
39//!
40//! The errors can be grouped into several logical categories:
41//!
42//! - **Connection and Pool Errors**: Failures related to establishing or maintaining a
43//!   connection (e.g., `ConnectionPoolFailure`, `ConnectionFromPool`).
44//! - **Query Execution Errors**: General failures during the execution of a query
45//!   (e.g., `SingleStringRequestFailure`, `InsertDataFrameFailure`).
46//! - **Result Cardinality Errors**: When a query returns an unexpected number of rows
47//!   (e.g., `SingleIntegerRequestNoSingleResponse`, `MultipleStringRequestEmptyResponse`).
48//! - **Data Integrity and Validation Errors**: Failures found during startup or runtime
49//!   validation checks (e.g., `DatabaseBallingSetValTableContainsNull`, `DatabaseDataTableContainsTooManyRows`).
50//! - **Data Conversion Errors**: When data retrieved from the database cannot be parsed
51//!   into the expected Rust type (e.g., `TimestampConversionFailure`).
52
53use chrono::{NaiveDateTime, OutOfRangeError};
54use mysql::Error as MySqlError;
55use thiserror::Error;
56
57#[derive(Debug, Error)]
58/// Enum codifies the errors which the program may encounter in communication with the DB
59pub enum SqlInterfaceError {
60    /// Could not get a connection from the pool. The database may be down or the pool exhausted.
61    #[error(
62        "[{location}] Could not get a connection from the pool. The database may be down or the pool exhausted."
63    )]
64    ConnectionFromPool {
65        location: String,
66
67        #[source]
68        source: mysql::Error,
69    },
70
71    /// The database wait_timeout is less than the required minimum.
72    #[error("[{0}] The database wait_timeout ({1}s) is less than the required minimum ({2}s).")]
73    WaitTimeoutTooLow(String, u64, u64),
74
75    /// Could not parse wait_timeout value from the database
76    #[error("[{0}] Could not parse wait_timeout value from the database: {0}")]
77    WaitTimeoutInvalid(String, i64),
78
79    /// Could not establish database connection pool
80    #[error("[{location}] Could not establish database connection pool with {url}")]
81    ConnectionPoolFailure {
82        location: String,
83        url: String,
84
85        #[source]
86        source: mysql::Error,
87    },
88
89    /// Required tables are not existing in the database.
90    #[error("[{0}] Required tables ({1}) are not existing in the database ({2}).")]
91    RequiredTablesNotExisting(String, String, String),
92
93    /// Error when reading tables from the database, expected more than one result.
94    #[error("[{0}] Error when reading tables from the database, expected more than one result.")]
95    ShowTablesWithoutProperResult(String),
96
97    /// Could not get a single string from the database.
98    #[error("[{0}] Could not get a single string from the database using query: {1} with major={2} and minor={3} and build={4}")]
99    SingleVersionRequestFailure(String, String, i32, i32, i32),
100
101    /// Database responded with empty response.
102    #[error("[{0}] Database responded with empty response to query: {1} with major={2} and minor={3} and build={4}")]
103    SingleVersionRequestNotListed(String, String, i32, i32, i32),
104
105    /// The database responded with multiple responses to request.
106    #[error("[{0}] Database responded with multiple responses to request to query: {1} with major={2} and minor={3} and build={4}")]
107    SingleVersionRequestMultipleResults(String, String, i32, i32, i32),
108
109    /// Could not get a string array from the database.
110    #[error("[{location}] Could not get a string array from the database using query: {query}")]
111    SingleStringRequestFailure {
112        location: String,
113        query: String,
114
115        #[source]
116        source: mysql::Error,
117    },
118
119    /// database responded with multiple responses to request.
120    #[error("[{0}] database responded with multiple responses to request to query: {1}")]
121    SingleStringRequestNoSingleResponse(String, String),
122
123    /// database responded with empty response to request.
124    #[error("[{0}] database responded with empty response to request to query: {1}")]
125    SingleStringRequestEmptyResponse(String, String),
126
127    /// database responded with empty response.
128    #[error("[{0}] Database responded with empty response to query: {1}")]
129    MultipleStringRequestEmptyResponse(String, String),
130
131    /// The database responded with single row response.
132    #[error("[{0}] Database responded with single row response to query: {1}")]
133    MultipleStringRequestSingleResponse(String, String),
134
135    /// Could not get a single integer from the database.
136    #[error("[{0}] Could not get a single integer from the database using query: {1}")]
137    SingleIntegerRequestFailure(String, String),
138
139    /// The database responded with multiple responses.
140    #[error("[{0}] Database responded with multiple responses to request to query: {1}")]
141    SingleIntegerRequestNoSingleResponse(String, String),
142
143    /// Could not get a single float value from the database.
144    #[error("[{0}] Could not get single float value from the database using query: {1}")]
145    SingleFloatRequestFailure(String, String),
146
147    /// database responded with empty response.
148    #[error("[{0}] Database responded with empty response to query: {1}")]
149    SingleFloatRequestEmptyResponse(String, String),
150
151    /// database responded with multiple responses to request.
152    #[error("[{0}] Database responded with multiple responses to request to query: {1}")]
153    SingleFloatRequestNoSingleResponse(String, String),
154
155    /// Could not get single heating stats entry from the database.
156    #[error(
157        "[{location}] Could not get single heating stats entry from database using query: {query}"
158    )]
159    SingleHeatingStatsRequestFailure {
160        location: String,
161        query: String,
162        #[source]
163        source: MySqlError,
164    },
165
166    /// database responded with empty response.
167    #[error("[{0}] Database responded with empty response to query: {1}")]
168    SingleHeatingStatsRequestEmptyResponse(String, String),
169
170    /// database responded with multiple responses to request.
171    #[error("[{0}] Database responded with multiple responses to request to query: {1}")]
172    SingleHeatingStatsRequestNoSingleResponse(String, String),
173
174    /// Could not get feed pattern header from the database
175    #[error(
176        "[{location}] Could not get feed pattern header from the database with query: {query} with profile_id={profile_id}"
177    )]
178    SingleFeedpatternRequestFailure {
179        location: String,
180        query: String,
181        profile_id: i32,
182
183        #[source]
184        source: MySqlError,
185    },
186
187    /// database responded with empty response.
188    #[error("[{0}] Database responded with empty response to query: {1} with profile_id={2}")]
189    SingleFeedpatternRequestEmptyResponse(String, String, i32),
190
191    /// The database responded with multiple responses.
192    #[error("[{0}] Database responded with multiple responses to request to query: {1} with profile_id={2}")]
193    SingleFeedpatternRequestNoSingleResponse(String, String, i32),
194
195    /// Could not get schedule entries from the database.
196    #[error("[{location}] Could not get schedule entries from database using query: {query}")]
197    ScheduleRequestFailure {
198        location: String,
199        query: String,
200        #[source]
201        source: MySqlError,
202    },
203
204    /// Could not process the results obtained from the database.
205    #[error("[{location}] Could not process the results obtained from database.")]
206    ScheduleProcessingFailure {
207        location: String,
208
209        #[source]
210        source: Box<SqlInterfaceError>,
211    },
212
213    /// Error in conversion of the database result to schedule entry.
214    #[error("[{0}] Error in conversion of database result to schedule entry ({1}).")]
215    ScheduleTypeConversionFailure(String, String),
216
217    /// Type-specific schedule could not be found in the database.
218    #[error(
219        "[{0}] Schedule of type {1} could not be found in database. Existing entries are: {2}."
220    )]
221    ScheduleTypeNotFound(String, String, String),
222
223    /// Error when inserting heating stats into the database.
224    #[error(
225        "[{location}] Error when inserting heating stats into the database using query: {query}"
226    )]
227    InsertHeatingStatsEntryFailure {
228        location: String,
229        query: String,
230        #[source]
231        source: MySqlError,
232    },
233
234    /// Error occurred when trying to insert a feed event into the database.
235    #[error("[{location}] Error occurred when trying to insert a feed event into the database: {source}")]
236    InsertFeedEventFailure {
237        location: String,
238
239        #[source]
240        source: MySqlError,
241    },
242
243    /// Error occurred when trying to insert a refill event into the database.
244    #[error("[{location}] Error occurred when trying to insert a refill event into the database using query: {query}.")]
245    InsertRefillEventFailure {
246        location: String,
247        query: String,
248        #[source]
249        source: MySqlError,
250    },
251
252    /// The Database contains log entry which is in the future.
253    #[error(
254        "[{0}] Database contains a Balling dosing event for pump {1} which is in the future: {2}."
255    )]
256    BallingDosingLogEntryInFuture(String, i64, NaiveDateTime),
257
258    /// Error occurred when trying to calculate duration from two NaiveDateTime timestamps
259    #[error("[{location}] Error occurred when trying to calculate dosing duration for pump {pump_id} from two NaiveDateTime timestamps ({current_datetime}, {last_dosing_datetime}): {source}")]
260    BallingDosingTimestampConversionFailure {
261        location: String,
262        pump_id: i64,
263        current_datetime: NaiveDateTime,
264        last_dosing_datetime: NaiveDateTime,
265
266        #[source]
267        source: OutOfRangeError,
268    },
269
270    /// Error occurred when trying to insert a Balling dosing event into the database.
271    #[error(
272        "[{location}] Error occurred when trying to insert a Balling dosing event into database."
273    )]
274    InsertBallingEventFailure {
275        location: String,
276        #[source]
277        source: MySqlError,
278    },
279
280    /// Database request for retrieving the refill history failed.
281    #[error("[{location}] Database request for retrieving the refill {category} of last {interval} failed.")]
282    RefillPastDataRetrievalFailed {
283        location: String,
284        category: String,
285        interval: String,
286        #[source]
287        source: Box<SqlInterfaceError>,
288    },
289
290    /// Could not get feed schedule entry from the database.
291    #[error(
292        "[{location}] Could not get feed schedule entry from the database using query: {query}"
293    )]
294    FeedScheduleEntryRequestFailure {
295        location: String,
296        query: String,
297
298        #[source]
299        source: MySqlError,
300    },
301
302    /// Could not convert feedschedule entry string(s) read from the database to timestamp.
303    #[error("[{location}] Repetition marker is out of range (repeat_daily = {repeat_daily}).")]
304    FeedScheduleEntryRepeatDailyOutOfRange { location: String, repeat_daily: i16 },
305
306    /// Error occurred when processing time stamp results from the database.
307    #[error(
308        "[{location}] Error occurred when processing {number_entries} results from the database."
309    )]
310    FeedScheduleEntryTimeStampProcessingFailure {
311        location: String,
312        number_entries: usize,
313        #[source]
314        source: Box<SqlInterfaceError>,
315    },
316
317    /// Error occurred when trying to calculate update of feed schedule entry.
318    #[error("[{location}] Error occurred when trying to calculate update of feed schedule entry.")]
319    FeedScheduleUpdateFailure {
320        location: String,
321        #[source]
322        source: Box<SqlInterfaceError>,
323    },
324
325    /// Error occurred when trying to drop a feed schedule entry from the database.
326    #[error(
327        "[{location}] Error occurred when trying to drop a feed schedule entry from the database."
328    )]
329    FeedScheduleDropFailure {
330        location: String,
331        #[source]
332        source: MySqlError,
333    },
334
335    // The database responded with one or even two negative values for feed phase/feed pause duration.
336    #[error(
337        "[{0}] Database responded with negative value(s) for feed phase/feed pause duration: {1} {2}"
338    )]
339    DatabaseFeedPhaseDurationNegative(String, i16, i16),
340
341    /// Could not get a single Balling set value from the database.
342    #[error("[{location}] Could not get a single Balling set value from the database using query: {query}")]
343    SingleBallingSetValRequestFailure {
344        location: String,
345        query: String,
346        pump_id: i64,
347
348        #[source]
349        source: MySqlError,
350    },
351
352    /// database responded with empty response.
353    #[error("[{0}] Database responded with empty response to query: {1} with pump_id={2}")]
354    SingleBallingSetValEmptyResponse(String, String, i64),
355
356    /// The database responded with multiple responses.
357    #[error("[{0}] Database responded with multiple responses to request to query: {1} with pump_id={2}")]
358    SingleBallingSetValNoSingleResponse(String, String, i64),
359
360    /// Error occurred when inserting data frame into the database.
361    #[error("[{location}] Error occurred when inserting data frame into the database using query: {query}")]
362    InsertDataFrameFailure {
363        location: String,
364        query: String,
365
366        #[source]
367        source: MySqlError,
368    },
369
370    /// The database responded with a timestamp in the future.
371    #[error("[{0}] Database responded with a timestamp in the future (delta of {1} seconds).")]
372    NegativeTimeDeltaSeconds(String, i64),
373
374    /// The database responded with a negative value ({0}) where a positive value was expected
375    #[error(
376        "[{0}] Database responded with a negative value ({1}) where a positive value was expected."
377    )]
378    NegativeInteger(String, i64),
379
380    /// Could not get heating set values from the database.
381    #[error("[{location}] Could not get heating set values from the database with query: {query}")]
382    HeatingSetValsRequestFailure {
383        location: String,
384        query: String,
385
386        #[source]
387        source: MySqlError,
388    },
389
390    /// The database returned more than one heating set value entry.
391    #[error("[{0}] The database returned more than one heating set value entry with query: {1}")]
392    HeatingSetValsNoSingleResponse(String, String),
393
394    /// Updating the heating set values failed.
395    #[error("[{location}] Updating the heating set values failed.")]
396    HeatingSetValsUpdateFailure {
397        location: String,
398
399        #[source]
400        source: Box<SqlInterfaceError>,
401    },
402
403    /// Updating the ventilation set values failed.
404    #[error("[{location}] Updating the ventilation set values failed.")]
405    VentilationSetValsUpdateFailure {
406        location: String,
407
408        #[source]
409        source: Box<SqlInterfaceError>,
410    },
411
412    /// The heating switch on temperature is higher than the heating switch-off temperature.
413    #[error("[{0}] The heating switch on temperature ({1}) is higher than the heating switch-off temperature ({2}).")]
414    HeatingSetValsInvalid(String, f32, f32),
415
416    /// Could not get ventilation set values from the database.
417    #[error(
418        "[{location}] Could not get ventilation set values from the database with query: {query}"
419    )]
420    VentilationSetValsRequestFailure {
421        location: String,
422        query: String,
423
424        #[source]
425        source: MySqlError,
426    },
427
428    /// The database returned more than one ventilation set value entry.
429    #[error(
430        "[{0}] The database returned more than one ventilation set value entry with query: {1}"
431    )]
432    VentilationSetValsNoSingleResponse(String, String),
433
434    /// The ventilation switch on temperature is lower than the ventilation switch-off temperature.
435    #[error("[{0}] The ventilation switch on temperature ({1}) is lower than the ventilation switch-off temperature ({2}).")]
436    VentilationSetValsInvalid(String, f32, f32),
437
438    /// Could not retrieve database name.
439    #[error("[{location}] Could not retrieve database name.")]
440    DatabaseNameRequestFailure {
441        location: String,
442
443        #[source]
444        source: Box<SqlInterfaceError>,
445    },
446
447    /// Could not retrieve date from the database.
448    #[error("[{location}] Could not retrieve date from the database.")]
449    DateRequestFailure { location: String, query: String },
450
451    /// Could not retrieve timestamp from the database.
452    #[error("[{location}] Could not retrieve timestamp from database using query: {query}")]
453    TimestampRequestFailure { location: String, query: String },
454
455    /// Could not retrieve optional timestamp from the database.
456    #[error("[{location}] Could not retrieve optional timestamp from database.")]
457    OptionalTimestampRequestFailure { location: String, query: String },
458
459    /// Could not ping database.
460    #[error("[{location}] Could not ping database.")]
461    DatabasePingFailure {
462        location: String,
463
464        #[source]
465        source: Box<SqlInterfaceError>,
466    },
467
468    /// Balling set value table may be inconsistent - it contains NULL values.
469    #[error("[{0}] Balling set value table may be inconsistent - it contains {1} NULL values.")]
470    DatabaseBallingSetValTableContainsNull(String, i64),
471
472    /// Balling set value table may be inconsistent - invalid answer from database to query.
473    #[error("[{0}] Balling set value table may be inconsistent - database responded with negative value(s) ({1}, {2}, {3}).")]
474    DatabaseBallingSetValTableNegativeValue(String, i64, i64, i64),
475
476    /// Balling set value table contains too many rows.
477    #[error(
478        "[{0}] Balling set value table contains too many rows ({1}). Permissible maximum is {2}."
479    )]
480    DatabaseBallingSetValTableContainsTooManyRows(String, u64, u64),
481
482    /// Balling dosing log table contains too many rows.
483    #[error(
484        "[{0}] Balling dosing log table contains too many rows ({1}). Permissible maximum is {2}."
485    )]
486    DatabaseBallingDosingLogTableContainsTooManyRows(String, u64, u64),
487
488    /// Schedule table may be inconsistent - it contains NULL values.
489    #[error("[{0}] Schedule table may be inconsistent - it contains {1} NULL values.")]
490    DatabaseScheduleTableContainsNull(String, i64),
491
492    /// Schedule table may be inconsistent - invalid answer from database to query.
493    #[error("[{0}] Schedule table may be inconsistent - database responded with negative value(s) ({1}, {2}).")]
494    DatabaseScheduleTableNegativeValue(String, i64, i64),
495
496    /// The schedule table contains too many rows.
497    #[error("[{0}] Schedule table contains too many rows ({1}). Permissible maximum is {2}.")]
498    DatabaseScheduleTableContainsTooManyRows(String, u64, u64),
499
500    /// Data table may be inconsistent - invalid answer from database to query.
501    #[error("[{location}] Could not check data table for number of rows.")]
502    DatabaseCheckDataFailure {
503        location: String,
504
505        #[source]
506        source: Box<SqlInterfaceError>,
507    },
508
509    /// Data table may be inconsistent - invalid answer from database to query.
510    #[error(
511        "[{0}] Data table may be inconsistent - database responded with negative value ({1})."
512    )]
513    DatabaseDataTableNegativeValue(String, i64),
514
515    /// The data table contains too many rows.
516    #[error("[{0}] Data table contains too many rows ({1}). Permissible maximum is {2}.")]
517    DatabaseDataTableContainsTooManyRows(String, u64, u64),
518
519    /// Heating set value table may be inconsistent - it contains NULL values.
520    #[error("[{0}] Heating set value table may be inconsistent - it contains {1} NULL values.")]
521    DatabaseHeatingSetValTableContainsNull(String, i64),
522
523    /// Heating set value table may be inconsistent - invalid answer from database to query.
524    #[error("[{0}] Heating set value table may be inconsistent - database responded with negative value(s) ({1}, {2}).")]
525    DatabaseHeatingSetValTableNegativeValue(String, i64, i64),
526
527    /// Heating set value table contains too many rows.
528    #[error(
529        "[{0}] Heating set value table contains too many rows ({1}). Permissible maximum is 1."
530    )]
531    DatabaseHeatingSetValTableContainsTooManyRows(String, u64),
532
533    /// Heating stats table may be inconsistent - it contains NULL values.
534    #[error("[{0}] Heating stats table may be inconsistent - it contains {1} NULL values.")]
535    DatabaseHeatingStatsTableContainsNull(String, i64),
536
537    /// Heating statistics table may be inconsistent - invalid answer from database to query.
538    #[error("[{0}] Heating set value table may be inconsistent - database responded with negative value(s) ({1}, {2}).")]
539    DatabaseHeatingStatsTableNegativeValue(String, i64, i64),
540
541    /// Heating stats table contains too many rows.
542    #[error(
543        "[{0}] Heating statistics table contains too many rows ({1}). Permissible maximum is {2}."
544    )]
545    DatabaseHeatingStatsTableContainsTooManyRows(String, u64, u64),
546
547    /// Ventilation set value table may be inconsistent - it contains NULL values.
548    #[error(
549        "[{0}] Ventilation set value table may be inconsistent - it contains {1} NULL values."
550    )]
551    DatabaseVentilationSetValTableContainsNull(String, i64),
552
553    /// Ventilation set value table may be inconsistent - invalid answer from database to query.
554    #[error("[{0}] Ventilation set value table may be inconsistent - database responded with negative value(s) ({1}, {2}).")]
555    DatabaseVentilationSetValTableNegativeValue(String, i64, i64),
556
557    /// Ventilation set value table contains too many rows.
558    #[error(
559        "[{0}] Ventilation set value table contains too many rows ({1}). Permissible maximum is 1."
560    )]
561    DatabaseVentilationSetValTableContainsTooManyRows(String, u64),
562
563    /// Refill table may be inconsistent - invalid answer from database to query.
564    #[error(
565        "[{0}] Refill table may be inconsistent - database responded with negative value ({1})."
566    )]
567    DatabaseRefillTableNegativeValue(String, i64),
568
569    /// Refill table contains too many rows.
570    #[error("[{0}] Refill table contains too many rows ({1}). Permissible maximum is {2}.")]
571    DatabaseRefillTableContainsTooManyRows(String, u64, u64),
572
573    /// Feed set value table may be inconsistent - it contains NULL values.
574    #[error("[{0}] Feed set value table {1} may be inconsistent - it contains {2} NULL values.")]
575    DatabaseFeedSetValTableContainsNull(String, String, i64),
576
577    /// Feed set value table contains too many rows.
578    #[error("[{0}] Feed table {1} contains too many rows ({2}). Permissible maximum is {3}.")]
579    DatabaseFeedTableContainsTooManyRows(String, String, u64, u64),
580
581    /// Could not check balling set value table for NULL values or number of rows.
582    #[error(
583        "[{location}] Could not check balling set value table (existence of NULL values and number of rows)."
584    )]
585    DatabaseCheckBallingSetValsFailure {
586        location: String,
587
588        #[source]
589        source: Box<SqlInterfaceError>,
590    },
591
592    /// Could not check schedule table for NULL values or number of rows.
593    #[error("[{location}] Could not check schedule table for NULL values or number of rows.")]
594    DatabaseCheckScheduleFailure {
595        location: String,
596
597        #[source]
598        source: Box<SqlInterfaceError>,
599    },
600
601    /// Could not check heating set value table for NULL values or number of rows.
602    #[error(
603        "[{location}] Could not check heating set value table for NULL values or number of rows."
604    )]
605    DatabaseCheckHeatingSetValsFailure {
606        location: String,
607
608        #[source]
609        source: Box<SqlInterfaceError>,
610    },
611
612    /// Could not check heating stats table for NULL values or number of rows.
613    #[error("[{location}] Could not check heating stats table for NULL values or number of rows.")]
614    DatabaseCheckHeatingStatsFailure {
615        location: String,
616
617        #[source]
618        source: Box<SqlInterfaceError>,
619    },
620
621    /// Could not check ventilation set value table for NULL values or number of rows.
622    #[error("[{location}] Could not check ventilation set value table for NULL values or number of rows.")]
623    DatabaseCheckVentilationSetValsFailure {
624        location: String,
625
626        #[source]
627        source: Box<SqlInterfaceError>,
628    },
629
630    /// Could not check refill table for number of rows.
631    #[error("[{location}] Could not check refill table for number of rows.")]
632    DatabaseCheckRefillFailure {
633        location: String,
634
635        #[source]
636        source: Box<SqlInterfaceError>,
637    },
638
639    /// Could not check feed set value table for NULL values or number of rows.
640    #[error(
641        "[{location}] Could not check feed set value table {table_name} for NULL values or number of rows."
642    )]
643    DatabaseCheckFeedSetValsFailure {
644        location: String,
645        table_name: String,
646        #[source]
647        source: Box<SqlInterfaceError>,
648    },
649
650    /// A feed set value table may be inconsistent - invalid answer from database to query.
651    #[error("[{0}] Feed set value table {1} may be inconsistent - database responded with negative value(s) ({2}, {3}).")]
652    DatabaseFeedTableNegativeValue(String, String, i64, i64),
653
654    #[cfg(test)]
655    #[error(
656        "[{location}] Error when inserting heating set values into database using query: {query}"
657    )]
658    InsertHeatingSetValuesFailure {
659        location: String,
660        query: String,
661
662        #[source]
663        source: MySqlError,
664    },
665
666    #[cfg(test)]
667    #[error("[{location}] Error when inserting ventilation set values into database using query: {query}")]
668    InsertVentilationSetValuesFailure {
669        location: String,
670        query: String,
671
672        #[source]
673        source: MySqlError,
674    },
675
676    #[cfg(test)]
677    #[error("[{0}] Error occurred when trying to truncate table {1} of database.")]
678    TruncateTableFailure(String, String),
679
680    #[cfg(test)]
681    #[error("[{0}] This is a mock error generated for testing purposes.")]
682    MockError(String),
683
684    #[cfg(test)]
685    #[error(
686        "[{0}] Error when inserting Balling pump configuration into database using query: {1}"
687    )]
688    InsertBallingPumpConfigurationFailure(String, String),
689
690    #[cfg(test)]
691    #[error("[{0}] Database responded with empty response to queries: {1} {2}")]
692    SingleDataFrameRequestEmptyResponse(String, String, String),
693
694    #[cfg(test)]
695    #[error("[{0}] Database responded with multiple responses to queries: {1} {2}")]
696    SingleDataFrameRequestNoSingleResponse(String, String, String),
697}