Skip to main content

nautilus_infrastructure/sql/
cache.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{collections::VecDeque, fmt::Debug, ops::ControlFlow, pin::Pin, time::Duration};
17
18use ahash::AHashMap;
19use bytes::Bytes;
20use nautilus_common::{
21    cache::{
22        CacheConfig,
23        database::{CacheDatabaseAdapter, CacheDatabaseFactory, CacheMap},
24    },
25    live::get_runtime,
26    logging::{log_task_awaiting, log_task_started, log_task_stopped},
27    signal::Signal,
28};
29use nautilus_core::{UUID4, UnixNanos};
30use nautilus_model::{
31    accounts::AccountAny,
32    data::{Bar, CustomData, DataType, FundingRateUpdate, QuoteTick, TradeTick},
33    events::{
34        AccountState, OrderEventAny, OrderFilled, OrderInitialized, OrderSnapshot,
35        position::snapshot::PositionSnapshot,
36    },
37    identifiers::{
38        AccountId, ActorId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId,
39        TraderId, VenueOrderId,
40    },
41    instruments::{Instrument, InstrumentAny, SyntheticInstrument},
42    orderbook::OrderBook,
43    orders::{Order, OrderAny},
44    position::Position,
45    types::{Currency, Money},
46};
47use serde::{Deserialize, Serialize};
48use sqlx::{PgPool, postgres::PgConnectOptions};
49use tokio::{time::Instant, try_join};
50use ustr::Ustr;
51
52use crate::sql::{
53    pg::{connect_pg, get_postgres_connect_options},
54    queries::DatabaseQueries,
55};
56
57// Task and connection names
58const CACHE_PROCESS: &str = "cache-process";
59
60/// Configuration for a Postgres-backed cache database.
61///
62/// Missing fields are resolved from Postgres environment variables and then built-in defaults.
63#[cfg_attr(
64    feature = "python",
65    expect(
66        clippy::unsafe_derive_deserialize,
67        reason = "config deserializes plain fields; unsafe methods come from generated PyO3 integration"
68    )
69)]
70#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(default, deny_unknown_fields)]
72#[cfg_attr(
73    feature = "python",
74    pyo3::pyclass(module = "nautilus_trader.infrastructure", from_py_object)
75)]
76#[cfg_attr(
77    feature = "python",
78    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.infrastructure")
79)]
80pub struct PostgresCacheConfig {
81    /// The Postgres host address.
82    pub host: Option<String>,
83    /// The Postgres port.
84    pub port: Option<u16>,
85    /// The Postgres account username.
86    pub username: Option<String>,
87    /// The Postgres account password.
88    pub password: Option<String>,
89    /// The Postgres database name.
90    pub database: Option<String>,
91}
92
93impl Debug for PostgresCacheConfig {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        let redacted = self.password.as_ref().map(|_| "***");
96        f.debug_struct(stringify!(PostgresCacheConfig))
97            .field("host", &self.host)
98            .field("port", &self.port)
99            .field("username", &self.username)
100            .field("password", &redacted)
101            .field("database", &self.database)
102            .finish()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use rstest::rstest;
109    use serde_json::json;
110
111    use super::*;
112
113    #[rstest]
114    fn test_default_postgres_cache_config() {
115        let config = PostgresCacheConfig::default();
116
117        assert_eq!(config.host, None);
118        assert_eq!(config.port, None);
119        assert_eq!(config.username, None);
120        assert_eq!(config.password, None);
121        assert_eq!(config.database, None);
122    }
123
124    #[rstest]
125    fn test_deserialize_postgres_cache_config() {
126        let config_json = json!({
127            "host": "localhost",
128            "port": 5432,
129            "username": "user",
130            "password": "pass",
131            "database": "nautilus"
132        });
133
134        let config: PostgresCacheConfig = serde_json::from_value(config_json).unwrap();
135
136        assert_eq!(config.host, Some("localhost".to_string()));
137        assert_eq!(config.port, Some(5432));
138        assert_eq!(config.username, Some("user".to_string()));
139        assert_eq!(config.password, Some("pass".to_string()));
140        assert_eq!(config.database, Some("nautilus".to_string()));
141    }
142
143    #[rstest]
144    fn test_deserialize_postgres_cache_config_rejects_type_selector() {
145        let config_json = json!({
146            "type": "postgres",
147        });
148
149        let error = serde_json::from_value::<PostgresCacheConfig>(config_json).unwrap_err();
150
151        assert!(error.to_string().contains("unknown field `type`"));
152    }
153}
154
155#[async_trait::async_trait]
156impl CacheDatabaseFactory for PostgresCacheConfig {
157    async fn create(
158        &self,
159        _trader_id: TraderId,
160        _instance_id: UUID4,
161        _config: CacheConfig,
162    ) -> anyhow::Result<Box<dyn CacheDatabaseAdapter>> {
163        let database = PostgresCacheDatabase::connect(
164            self.host.clone(),
165            self.port,
166            self.username.clone(),
167            self.password.clone(),
168            self.database.clone(),
169        )
170        .await?;
171        Ok(Box::new(database))
172    }
173}
174
175#[derive(Debug)]
176#[cfg_attr(
177    feature = "python",
178    pyo3::pyclass(module = "nautilus_trader.infrastructure")
179)]
180pub struct PostgresCacheDatabase {
181    pub pool: PgPool,
182    tx: tokio::sync::mpsc::UnboundedSender<DatabaseQuery>,
183    handle: tokio::task::JoinHandle<()>,
184}
185
186#[allow(
187    clippy::large_enum_variant,
188    reason = "variant sizes vary with feature unification; allow stays silent when the lint does not fire"
189)]
190#[derive(Debug, Clone)]
191pub enum DatabaseQuery {
192    Close,
193    Add(String, Vec<u8>),
194    AddCurrency(Currency),
195    AddInstrument(InstrumentAny),
196    AddOrder(OrderInitialized, Option<ClientId>),
197    AddOrderSnapshot(OrderSnapshot),
198    AddPosition(PositionId, OrderFilled),
199    AddPositionSnapshot(PositionSnapshot),
200    AddAccount(AccountState, bool),
201    AddSignal(Signal),
202    AddCustom(CustomData),
203    AddQuote(QuoteTick),
204    AddTrade(TradeTick),
205    AddBar(Bar),
206    UpdateOrder(OrderEventAny),
207    UpdatePosition(OrderFilled),
208    IndexOrderPosition(ClientOrderId, PositionId),
209    IndexOrderClients(Vec<(ClientOrderId, ClientId)>),
210}
211
212impl PostgresCacheDatabase {
213    /// Connects to the Postgres cache database using the provided connection parameters.
214    ///
215    /// # Errors
216    ///
217    /// Returns an error if establishing the database connection fails.
218    ///
219    /// # Panics
220    ///
221    /// Panics if the internal Postgres pool connection attempt (`connect_pg`) unwraps on error.
222    pub async fn connect(
223        host: Option<String>,
224        port: Option<u16>,
225        username: Option<String>,
226        password: Option<String>,
227        database: Option<String>,
228    ) -> Result<Self, sqlx::Error> {
229        let pg_connect_options =
230            get_postgres_connect_options(host, port, username, password, database);
231        let pool = connect_pg(pg_connect_options.clone().into()).await.unwrap();
232        check_schema_migrated(&pool).await?;
233        let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<DatabaseQuery>();
234
235        let handle = get_runtime().spawn(async move {
236            Box::pin(Self::process_commands(
237                rx,
238                pg_connect_options.clone().into(),
239            ))
240            .await;
241        });
242        Ok(Self { pool, tx, handle })
243    }
244
245    async fn process_commands(
246        mut rx: tokio::sync::mpsc::UnboundedReceiver<DatabaseQuery>,
247        pg_connect_options: PgConnectOptions,
248    ) {
249        log_task_started(CACHE_PROCESS);
250
251        let pool = connect_pg(pg_connect_options).await.unwrap();
252
253        // Buffering
254        let mut buffer: VecDeque<DatabaseQuery> = VecDeque::new();
255
256        // TODO: expose this via configuration once tests are fixed
257        let buffer_interval = Duration::from_millis(0);
258
259        // A sleep used to trigger periodic flushing of the buffer.
260        // When `buffer_interval` is zero we skip using the timer and flush immediately
261        // after every message.
262        let flush_timer = tokio::time::sleep(buffer_interval);
263        tokio::pin!(flush_timer);
264
265        // Continue to receive and handle messages until channel is hung up
266        loop {
267            tokio::select! {
268                maybe_msg = rx.recv() => {
269                    let result = Box::pin(handle_query(
270                        maybe_msg,
271                        &mut buffer,
272                        buffer_interval,
273                        &pool,
274                    ))
275                    .await;
276
277                    if result.is_break() {
278                        break;
279                    }
280                }
281                () = &mut flush_timer, if !buffer_interval.is_zero() => {
282                    flush_buffer(&mut buffer, &pool, &mut flush_timer, buffer_interval).await;
283                }
284            }
285        }
286
287        if !buffer.is_empty() {
288            drain_buffer(&pool, &mut buffer).await;
289        }
290
291        log_task_stopped(CACHE_PROCESS);
292    }
293}
294
295// Fails fast when the connected database predates the exact-average columns.
296//
297// Both directions of the mismatch are otherwise silent: `numeric -> double precision` is an
298// implicit cast so writes truncate, and the row readers use `.ok().flatten()` so reads degrade
299// to `None`. A column absent altogether is left to the query that first touches it.
300async fn check_schema_migrated(pool: &PgPool) -> Result<(), sqlx::Error> {
301    let stale: Vec<String> = sqlx::query_scalar(
302        "SELECT table_name || '.' || column_name || ' (' || data_type || ')'
303        FROM information_schema.columns
304        WHERE table_schema = current_schema()
305          AND (table_name, column_name) IN (('order', 'avg_px'), ('order', 'slippage'))
306          AND data_type <> 'numeric'
307        ORDER BY table_name, column_name",
308    )
309    .fetch_all(pool)
310    .await?;
311
312    if stale.is_empty() {
313        return Ok(());
314    }
315
316    Err(sqlx::Error::Configuration(
317        format!(
318            "Postgres schema is out of date, {} should be `numeric`: run `nautilus database init` to migrate",
319            stale.join(", ")
320        )
321        .into(),
322    ))
323}
324
325async fn handle_query(
326    maybe_msg: Option<DatabaseQuery>,
327    buffer: &mut VecDeque<DatabaseQuery>,
328    buffer_interval: Duration,
329    pool: &PgPool,
330) -> ControlFlow<()> {
331    let Some(msg) = maybe_msg else {
332        log::debug!("Command channel closed");
333        return ControlFlow::Break(());
334    };
335
336    if matches!(msg, DatabaseQuery::Close) {
337        if !buffer.is_empty() {
338            drain_buffer(pool, buffer).await;
339        }
340        return ControlFlow::Break(());
341    }
342
343    buffer.push_back(msg);
344
345    if buffer_interval.is_zero() {
346        drain_buffer(pool, buffer).await;
347    }
348
349    ControlFlow::Continue(())
350}
351
352async fn flush_buffer(
353    buffer: &mut VecDeque<DatabaseQuery>,
354    pool: &PgPool,
355    flush_timer: &mut Pin<&mut tokio::time::Sleep>,
356    buffer_interval: Duration,
357) {
358    if !buffer.is_empty() {
359        drain_buffer(pool, buffer).await;
360    }
361    flush_timer.as_mut().reset(Instant::now() + buffer_interval);
362}
363
364/// Retrieves a `PostgresCacheDatabase` using default connection options.
365///
366/// # Errors
367///
368/// Returns an error if connecting to the database or initializing the cache adapter fails.
369pub async fn get_pg_cache_database() -> anyhow::Result<PostgresCacheDatabase> {
370    let connect_options = get_postgres_connect_options(None, None, None, None, None);
371    Ok(PostgresCacheDatabase::connect(
372        Some(connect_options.host),
373        Some(connect_options.port),
374        Some(connect_options.username),
375        Some(connect_options.password),
376        Some(connect_options.database),
377    )
378    .await?)
379}
380
381#[async_trait::async_trait]
382impl CacheDatabaseAdapter for PostgresCacheDatabase {
383    fn close(&mut self) -> anyhow::Result<()> {
384        let pool = self.pool.clone();
385        let (tx, rx) = std::sync::mpsc::channel();
386
387        // The close command follows all pending writes on the FIFO channel and drains the buffer
388        if let Err(e) = self.tx.send(DatabaseQuery::Close) {
389            log::warn!("Error sending close: {e:?}");
390        }
391
392        log_task_awaiting("cache-write");
393
394        tokio::task::block_in_place(|| {
395            if let Err(e) = get_runtime().block_on(&mut self.handle) {
396                log::error!("Error awaiting task 'cache-write': {e:?}");
397            }
398        });
399
400        log::debug!("Closing connection pool");
401
402        tokio::task::block_in_place(|| {
403            get_runtime().block_on(async {
404                pool.close().await;
405
406                if let Err(e) = tx.send(()) {
407                    log::error!("Error closing pool: {e:?}");
408                }
409            });
410        });
411
412        log::debug!("Closed");
413
414        Ok(rx.recv()?)
415    }
416
417    fn flush(&mut self) -> anyhow::Result<()> {
418        let pool = self.pool.clone();
419        let (tx, rx) = std::sync::mpsc::channel();
420
421        tokio::task::block_in_place(|| {
422            get_runtime().block_on(async {
423                if let Err(e) = DatabaseQueries::truncate(&pool).await {
424                    log::error!("Error flushing pool: {e:?}");
425                }
426
427                if let Err(e) = tx.send(()) {
428                    log::error!("Error sending flush result: {e:?}");
429                }
430            });
431        });
432
433        Ok(rx.recv()?)
434    }
435
436    async fn load_all(&self) -> anyhow::Result<CacheMap> {
437        let currencies = self.load_currencies().await?;
438        for currency in currencies.values() {
439            Currency::register(*currency, false)?;
440        }
441
442        let (instruments, synthetics, accounts, orders, positions) = try_join!(
443            self.load_instruments(),
444            self.load_synthetics(),
445            self.load_accounts(),
446            self.load_orders(),
447            self.load_positions()
448        )
449        .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
450
451        // For now, we don't load greeks and yield curves from the database
452        // This will be implemented in the future
453        let greeks = AHashMap::new();
454        let yield_curves = AHashMap::new();
455
456        Ok(CacheMap {
457            currencies,
458            instruments,
459            synthetics,
460            accounts,
461            orders,
462            positions,
463            greeks,
464            yield_curves,
465        })
466    }
467
468    fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>> {
469        let pool = self.pool.clone();
470        let (tx, rx) = std::sync::mpsc::channel();
471
472        tokio::spawn(async move {
473            let result = DatabaseQueries::load(&pool).await;
474            match result {
475                Ok(items) => {
476                    let mapping = items
477                        .into_iter()
478                        .map(|(k, v)| (k, Bytes::from(v)))
479                        .collect();
480
481                    if let Err(e) = tx.send(mapping) {
482                        log::error!("Failed to send general items: {e:?}");
483                    }
484                }
485                Err(e) => {
486                    log::error!("Failed to load general items: {e:?}");
487                    if let Err(e) = tx.send(AHashMap::new()) {
488                        log::error!("Failed to send empty general items: {e:?}");
489                    }
490                }
491            }
492        });
493        Ok(rx.recv()?)
494    }
495
496    async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>> {
497        let pool = self.pool.clone();
498        let (tx, rx) = std::sync::mpsc::channel();
499
500        tokio::spawn(async move {
501            let result = DatabaseQueries::load_currencies(&pool).await;
502            match result {
503                Ok(currencies) => {
504                    let mapping = currencies
505                        .into_iter()
506                        .map(|currency| (currency.code, currency))
507                        .collect();
508
509                    if let Err(e) = tx.send(mapping) {
510                        log::error!("Failed to send currencies: {e:?}");
511                    }
512                }
513                Err(e) => {
514                    log::error!("Failed to load currencies: {e:?}");
515                    if let Err(e) = tx.send(AHashMap::new()) {
516                        log::error!("Failed to send empty currencies: {e:?}");
517                    }
518                }
519            }
520        });
521        Ok(rx.recv()?)
522    }
523
524    async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
525        let pool = self.pool.clone();
526        let (tx, rx) = std::sync::mpsc::channel();
527
528        tokio::spawn(async move {
529            let result = DatabaseQueries::load_instruments(&pool).await;
530            match result {
531                Ok(instruments) => {
532                    let mapping = instruments
533                        .into_iter()
534                        .map(|instrument| (instrument.id(), instrument))
535                        .collect();
536
537                    if let Err(e) = tx.send(mapping) {
538                        log::error!("Failed to send instruments: {e:?}");
539                    }
540                }
541                Err(e) => {
542                    log::error!("Failed to load instruments: {e:?}");
543                    if let Err(e) = tx.send(AHashMap::new()) {
544                        log::error!("Failed to send empty instruments: {e:?}");
545                    }
546                }
547            }
548        });
549        Ok(rx.recv()?)
550    }
551
552    async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
553        Ok(AHashMap::new())
554    }
555
556    async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
557        let pool = self.pool.clone();
558        let (tx, rx) = std::sync::mpsc::channel();
559
560        tokio::spawn(async move {
561            let result = DatabaseQueries::load_accounts(&pool).await;
562            match result {
563                Ok(accounts) => {
564                    let mapping = accounts
565                        .into_iter()
566                        .map(|account| (account.id(), account))
567                        .collect();
568
569                    if let Err(e) = tx.send(mapping) {
570                        log::error!("Failed to send accounts: {e:?}");
571                    }
572                }
573                Err(e) => {
574                    log::error!("Failed to load accounts: {e:?}");
575                    if let Err(e) = tx.send(AHashMap::new()) {
576                        log::error!("Failed to send empty accounts: {e:?}");
577                    }
578                }
579            }
580        });
581        Ok(rx.recv()?)
582    }
583
584    async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
585        let pool = self.pool.clone();
586        let (tx, rx) = std::sync::mpsc::channel();
587
588        tokio::spawn(async move {
589            let result = DatabaseQueries::load_orders(&pool).await;
590            match result {
591                Ok(orders) => {
592                    let mapping = orders
593                        .into_iter()
594                        .map(|order| (order.client_order_id(), order))
595                        .collect();
596
597                    if let Err(e) = tx.send(mapping) {
598                        log::error!("Failed to send orders: {e:?}");
599                    }
600                }
601                Err(e) => {
602                    log::error!("Failed to load orders: {e:?}");
603                    if let Err(e) = tx.send(AHashMap::new()) {
604                        log::error!("Failed to send empty orders: {e:?}");
605                    }
606                }
607            }
608        });
609        Ok(rx.recv()?)
610    }
611
612    async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>> {
613        let pool = self.pool.clone();
614        let (tx, rx) = std::sync::mpsc::channel();
615
616        tokio::spawn(async move {
617            let result = DatabaseQueries::load_positions(&pool)
618                .await
619                .map(|positions| {
620                    positions
621                        .into_iter()
622                        .map(|position| (position.id, position))
623                        .collect()
624                });
625
626            if let Err(e) = tx.send(result) {
627                log::error!("Failed to send positions: {e:?}");
628            }
629        });
630        rx.recv()?
631    }
632
633    fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
634        let pool = self.pool.clone();
635        let (tx, rx) = std::sync::mpsc::channel();
636
637        tokio::spawn(async move {
638            let result = DatabaseQueries::load_index_order_position(&pool).await;
639            match result {
640                Ok(index) => {
641                    if let Err(e) = tx.send(index) {
642                        log::error!("Failed to send load_index_order_position result: {e:?}");
643                    }
644                }
645                Err(e) => {
646                    log::error!("Failed to run query load_index_order_position: {e:?}");
647                    if let Err(e) = tx.send(AHashMap::new()) {
648                        log::error!("Failed to send empty load_index_order_position result: {e:?}");
649                    }
650                }
651            }
652        });
653        Ok(rx.recv()?)
654    }
655
656    fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
657        let pool = self.pool.clone();
658        let (tx, rx) = std::sync::mpsc::channel();
659
660        tokio::spawn(async move {
661            let result = DatabaseQueries::load_distinct_order_event_client_ids(&pool).await;
662            match result {
663                Ok(currency) => {
664                    if let Err(e) = tx.send(currency) {
665                        log::error!("Failed to send load_index_order_client result: {e:?}");
666                    }
667                }
668                Err(e) => {
669                    log::error!("Failed to run query load_distinct_order_event_client_ids: {e:?}");
670                    if let Err(e) = tx.send(AHashMap::new()) {
671                        log::error!("Failed to send empty load_index_order_client result: {e:?}");
672                    }
673                }
674            }
675        });
676        Ok(rx.recv()?)
677    }
678
679    async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>> {
680        let pool = self.pool.clone();
681        let code = code.to_owned(); // Clone the code
682        let (tx, rx) = std::sync::mpsc::channel();
683
684        tokio::spawn(async move {
685            let result = DatabaseQueries::load_currency(&pool, &code).await;
686            match result {
687                Ok(currency) => {
688                    if let Err(e) = tx.send(currency) {
689                        log::error!("Failed to send currency {code}: {e:?}");
690                    }
691                }
692                Err(e) => {
693                    log::error!("Failed to load currency {code}: {e:?}");
694                    if let Err(e) = tx.send(None) {
695                        log::error!("Failed to send None for currency {code}: {e:?}");
696                    }
697                }
698            }
699        });
700        Ok(rx.recv()?)
701    }
702
703    async fn load_instrument(
704        &self,
705        instrument_id: &InstrumentId,
706    ) -> anyhow::Result<Option<InstrumentAny>> {
707        let pool = self.pool.clone();
708        let instrument_id = instrument_id.to_owned(); // Clone the instrument_id
709        let (tx, rx) = std::sync::mpsc::channel();
710
711        tokio::spawn(async move {
712            let result = DatabaseQueries::load_instrument(&pool, &instrument_id).await;
713            match result {
714                Ok(instrument) => {
715                    if let Err(e) = tx.send(instrument) {
716                        log::error!("Failed to send instrument {instrument_id}: {e:?}");
717                    }
718                }
719                Err(e) => {
720                    log::error!("Failed to load instrument {instrument_id}: {e:?}");
721                    if let Err(e) = tx.send(None) {
722                        log::error!("Failed to send None for instrument {instrument_id}: {e:?}");
723                    }
724                }
725            }
726        });
727        Ok(rx.recv()?)
728    }
729
730    async fn load_synthetic(
731        &self,
732        instrument_id: &InstrumentId,
733    ) -> anyhow::Result<Option<SyntheticInstrument>> {
734        anyhow::bail!(
735            "load_synthetic not implemented for PostgreSQL cache adapter: {instrument_id}"
736        )
737    }
738
739    async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>> {
740        let pool = self.pool.clone();
741        let account_id = account_id.to_owned();
742        let (tx, rx) = std::sync::mpsc::channel();
743
744        tokio::spawn(async move {
745            let result = DatabaseQueries::load_account(&pool, &account_id).await;
746            match result {
747                Ok(account) => {
748                    if let Err(e) = tx.send(account) {
749                        log::error!("Failed to send account {account_id}: {e:?}");
750                    }
751                }
752                Err(e) => {
753                    log::error!("Failed to load account {account_id}: {e:?}");
754                    if let Err(e) = tx.send(None) {
755                        log::error!("Failed to send None for account {account_id}: {e:?}");
756                    }
757                }
758            }
759        });
760        Ok(rx.recv()?)
761    }
762
763    async fn load_order(
764        &self,
765        client_order_id: &ClientOrderId,
766    ) -> anyhow::Result<Option<OrderAny>> {
767        let pool = self.pool.clone();
768        let client_order_id = client_order_id.to_owned();
769        let (tx, rx) = std::sync::mpsc::channel();
770
771        tokio::spawn(async move {
772            let result = DatabaseQueries::load_order(&pool, &client_order_id).await;
773            match result {
774                Ok(order) => {
775                    if let Err(e) = tx.send(order) {
776                        log::error!("Failed to send order {client_order_id}: {e:?}");
777                    }
778                }
779                Err(e) => {
780                    log::error!("Failed to load order {client_order_id}: {e:?}");
781                    let _ = tx.send(None);
782                }
783            }
784        });
785        Ok(rx.recv()?)
786    }
787
788    async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>> {
789        let pool = self.pool.clone();
790        let position_id = position_id.to_owned();
791        let (tx, rx) = std::sync::mpsc::channel();
792
793        tokio::spawn(async move {
794            let result = DatabaseQueries::load_position(&pool, &position_id).await;
795            if let Err(e) = tx.send(result) {
796                log::error!("Failed to send position {position_id}: {e:?}");
797            }
798        });
799        rx.recv()?
800    }
801
802    fn load_actor(&self, actor_id: &ActorId) -> anyhow::Result<AHashMap<String, Bytes>> {
803        anyhow::bail!("load_actor not implemented for PostgreSQL cache adapter: {actor_id}")
804    }
805
806    fn delete_actor(&self, _actor_id: &ActorId) -> anyhow::Result<()> {
807        todo!()
808    }
809
810    fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>> {
811        anyhow::bail!("load_strategy not implemented for PostgreSQL cache adapter: {strategy_id}")
812    }
813
814    fn delete_strategy(&self, _strategy_id: &StrategyId) -> anyhow::Result<()> {
815        todo!()
816    }
817
818    fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
819        anyhow::bail!(
820            "delete_order not implemented for PostgreSQL cache adapter: {client_order_id}"
821        )
822    }
823
824    fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
825        anyhow::bail!("delete_position not implemented for PostgreSQL cache adapter: {position_id}")
826    }
827
828    fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()> {
829        anyhow::bail!(
830            "delete_account_event not implemented for PostgreSQL cache adapter: {account_id}, {event_id}"
831        )
832    }
833
834    fn add(&self, key: String, value: Bytes) -> anyhow::Result<()> {
835        let query = DatabaseQuery::Add(key, value.into());
836        self.tx
837            .send(query)
838            .map_err(|e| anyhow::anyhow!("Failed to send query to database message handler: {e}"))
839    }
840
841    fn add_currency(&self, currency: &Currency) -> anyhow::Result<()> {
842        let query = DatabaseQuery::AddCurrency(*currency);
843        self.tx.send(query).map_err(|e| {
844            anyhow::anyhow!("Failed to query add_currency to database message handler: {e}")
845        })
846    }
847
848    fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()> {
849        let query = DatabaseQuery::AddInstrument(instrument.clone());
850        self.tx.send(query).map_err(|e| {
851            anyhow::anyhow!("Failed to send query add_instrument to database message handler: {e}")
852        })
853    }
854
855    fn add_synthetic(&self, _synthetic: &SyntheticInstrument) -> anyhow::Result<()> {
856        todo!()
857    }
858
859    fn add_account(&self, account: &AccountAny) -> anyhow::Result<()> {
860        let query = DatabaseQuery::AddAccount(account_last_event(account)?, false);
861        self.tx.send(query).map_err(|e| {
862            anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
863        })
864    }
865
866    fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()> {
867        let query = DatabaseQuery::AddOrder(order_initialized_event(order), client_id);
868        self.tx.send(query).map_err(|e| {
869            anyhow::anyhow!("Failed to send query add_order to database message handler: {e}")
870        })
871    }
872
873    fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()> {
874        let query = DatabaseQuery::AddOrderSnapshot(snapshot.to_owned());
875        self.tx.send(query).map_err(|e| {
876            anyhow::anyhow!(
877                "Failed to send query add_order_snapshot to database message handler: {e}"
878            )
879        })
880    }
881
882    fn add_position(&self, position: &Position) -> anyhow::Result<()> {
883        let event = position_last_event(position)?;
884        let query = DatabaseQuery::AddPosition(position.id, event);
885        self.tx.send(query).map_err(|e| {
886            anyhow::anyhow!("Failed to send query add_position to database message handler: {e}")
887        })
888    }
889
890    fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()> {
891        let query = DatabaseQuery::AddPositionSnapshot(snapshot.to_owned());
892        self.tx.send(query).map_err(|e| {
893            anyhow::anyhow!(
894                "Failed to send query add_position_snapshot to database message handler: {e}"
895            )
896        })
897    }
898
899    fn add_order_book(&self, _order_book: &OrderBook) -> anyhow::Result<()> {
900        todo!()
901    }
902
903    fn add_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
904        let query = DatabaseQuery::AddQuote(quote.to_owned());
905        self.tx.send(query).map_err(|e| {
906            anyhow::anyhow!("Failed to send query add_quote to database message handler: {e}")
907        })
908    }
909
910    fn load_quotes(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>> {
911        let pool = self.pool.clone();
912        let instrument_id = instrument_id.to_owned();
913        let (tx, rx) = std::sync::mpsc::channel();
914
915        tokio::spawn(async move {
916            let result = DatabaseQueries::load_quotes(&pool, &instrument_id).await;
917            match result {
918                Ok(quotes) => {
919                    if let Err(e) = tx.send(quotes) {
920                        log::error!("Failed to send quotes for instrument {instrument_id}: {e:?}");
921                    }
922                }
923                Err(e) => {
924                    log::error!("Failed to load quotes for instrument {instrument_id}: {e:?}");
925                    if let Err(e) = tx.send(Vec::new()) {
926                        log::error!(
927                            "Failed to send empty quotes for instrument {instrument_id}: {e:?}"
928                        );
929                    }
930                }
931            }
932        });
933        Ok(rx.recv()?)
934    }
935
936    fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
937        let query = DatabaseQuery::AddTrade(trade.to_owned());
938        self.tx.send(query).map_err(|e| {
939            anyhow::anyhow!("Failed to send query add_trade to database message handler: {e}")
940        })
941    }
942
943    fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
944        let pool = self.pool.clone();
945        let instrument_id = instrument_id.to_owned();
946        let (tx, rx) = std::sync::mpsc::channel();
947
948        tokio::spawn(async move {
949            let result = DatabaseQueries::load_trades(&pool, &instrument_id).await;
950            match result {
951                Ok(trades) => {
952                    if let Err(e) = tx.send(trades) {
953                        log::error!("Failed to send trades for instrument {instrument_id}: {e:?}");
954                    }
955                }
956                Err(e) => {
957                    log::error!("Failed to load trades for instrument {instrument_id}: {e:?}");
958                    if let Err(e) = tx.send(Vec::new()) {
959                        log::error!(
960                            "Failed to send empty trades for instrument {instrument_id}: {e:?}"
961                        );
962                    }
963                }
964            }
965        });
966        Ok(rx.recv()?)
967    }
968
969    fn add_funding_rate(&self, _funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
970        anyhow::bail!("add_funding_rate not implemented for PostgreSQL cache adapter")
971    }
972
973    fn load_funding_rates(
974        &self,
975        _instrument_id: &InstrumentId,
976    ) -> anyhow::Result<Vec<FundingRateUpdate>> {
977        anyhow::bail!("load_funding_rates not implemented for PostgreSQL cache adapter")
978    }
979
980    fn add_bar(&self, bar: &Bar) -> anyhow::Result<()> {
981        let query = DatabaseQuery::AddBar(bar.to_owned());
982        self.tx.send(query).map_err(|e| {
983            anyhow::anyhow!("Failed to send query add_bar to database message handler: {e}")
984        })
985    }
986
987    fn load_bars(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>> {
988        let pool = self.pool.clone();
989        let instrument_id = instrument_id.to_owned();
990        let (tx, rx) = std::sync::mpsc::channel();
991
992        tokio::spawn(async move {
993            let result = DatabaseQueries::load_bars(&pool, &instrument_id).await;
994            match result {
995                Ok(bars) => {
996                    if let Err(e) = tx.send(bars) {
997                        log::error!("Failed to send bars for instrument {instrument_id}: {e:?}");
998                    }
999                }
1000                Err(e) => {
1001                    log::error!("Failed to load bars for instrument {instrument_id}: {e:?}");
1002                    if let Err(e) = tx.send(Vec::new()) {
1003                        log::error!(
1004                            "Failed to send empty bars for instrument {instrument_id}: {e:?}"
1005                        );
1006                    }
1007                }
1008            }
1009        });
1010        Ok(rx.recv()?)
1011    }
1012
1013    fn add_signal(&self, signal: &Signal) -> anyhow::Result<()> {
1014        let query = DatabaseQuery::AddSignal(signal.to_owned());
1015        self.tx.send(query).map_err(|e| {
1016            anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
1017        })
1018    }
1019
1020    fn load_signals(&self, name: &str) -> anyhow::Result<Vec<Signal>> {
1021        let pool = self.pool.clone();
1022        let name = name.to_owned();
1023        let (tx, rx) = std::sync::mpsc::channel();
1024
1025        tokio::spawn(async move {
1026            let result = DatabaseQueries::load_signals(&pool, &name).await;
1027            match result {
1028                Ok(signals) => {
1029                    if let Err(e) = tx.send(signals) {
1030                        log::error!("Failed to send signals for '{name}': {e:?}");
1031                    }
1032                }
1033                Err(e) => {
1034                    log::error!("Failed to load signals for '{name}': {e:?}");
1035                    if let Err(e) = tx.send(Vec::new()) {
1036                        log::error!("Failed to send empty signals for '{name}': {e:?}");
1037                    }
1038                }
1039            }
1040        });
1041        Ok(rx.recv()?)
1042    }
1043
1044    fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
1045        let query = DatabaseQuery::AddCustom(data.to_owned());
1046        self.tx.send(query).map_err(|e| {
1047            anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
1048        })
1049    }
1050
1051    fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
1052        let pool = self.pool.clone();
1053        let data_type = data_type.to_owned();
1054        let (tx, rx) = std::sync::mpsc::channel();
1055
1056        tokio::spawn(async move {
1057            let result = DatabaseQueries::load_custom_data(&pool, &data_type).await;
1058            match result {
1059                Ok(signals) => {
1060                    if let Err(e) = tx.send(signals) {
1061                        log::error!("Failed to send custom data for '{data_type}': {e:?}");
1062                    }
1063                }
1064                Err(e) => {
1065                    log::error!("Failed to load custom data for '{data_type}': {e:?}");
1066                    if let Err(e) = tx.send(Vec::new()) {
1067                        log::error!("Failed to send empty custom data for '{data_type}': {e:?}");
1068                    }
1069                }
1070            }
1071        });
1072        Ok(rx.recv()?)
1073    }
1074
1075    fn load_order_snapshot(
1076        &self,
1077        client_order_id: &ClientOrderId,
1078    ) -> anyhow::Result<Option<OrderSnapshot>> {
1079        let pool = self.pool.clone();
1080        let client_order_id = client_order_id.to_owned();
1081        let (tx, rx) = std::sync::mpsc::channel();
1082
1083        tokio::spawn(async move {
1084            let result = DatabaseQueries::load_order_snapshot(&pool, &client_order_id).await;
1085            match result {
1086                Ok(snapshot) => {
1087                    if let Err(e) = tx.send(snapshot) {
1088                        log::error!("Failed to send order snapshot {client_order_id}: {e:?}");
1089                    }
1090                }
1091                Err(e) => {
1092                    log::error!("Failed to load order snapshot {client_order_id}: {e:?}");
1093                    if let Err(e) = tx.send(None) {
1094                        log::error!(
1095                            "Failed to send None for order snapshot {client_order_id}: {e:?}"
1096                        );
1097                    }
1098                }
1099            }
1100        });
1101        Ok(rx.recv()?)
1102    }
1103
1104    fn load_position_snapshot(
1105        &self,
1106        position_id: &PositionId,
1107    ) -> anyhow::Result<Option<PositionSnapshot>> {
1108        let pool = self.pool.clone();
1109        let position_id = position_id.to_owned();
1110        let (tx, rx) = std::sync::mpsc::channel();
1111
1112        tokio::spawn(async move {
1113            let result = DatabaseQueries::load_position_snapshot(&pool, &position_id).await;
1114            match result {
1115                Ok(snapshot) => {
1116                    if let Err(e) = tx.send(snapshot) {
1117                        log::error!("Failed to send position snapshot {position_id}: {e:?}");
1118                    }
1119                }
1120                Err(e) => {
1121                    log::error!("Failed to load position snapshot {position_id}: {e:?}");
1122                    if let Err(e) = tx.send(None) {
1123                        log::error!(
1124                            "Failed to send None for position snapshot {position_id}: {e:?}"
1125                        );
1126                    }
1127                }
1128            }
1129        });
1130        Ok(rx.recv()?)
1131    }
1132
1133    fn index_venue_order_id(
1134        &self,
1135        _client_order_id: ClientOrderId,
1136        _venue_order_id: VenueOrderId,
1137    ) -> anyhow::Result<()> {
1138        todo!()
1139    }
1140
1141    fn index_order_position(
1142        &self,
1143        client_order_id: ClientOrderId,
1144        position_id: PositionId,
1145    ) -> anyhow::Result<()> {
1146        let query = DatabaseQuery::IndexOrderPosition(client_order_id, position_id);
1147        self.tx.send(query).map_err(|e| {
1148            anyhow::anyhow!(
1149                "Failed to send query index_order_position to database message handler: {e}"
1150            )
1151        })
1152    }
1153
1154    fn index_order_clients(&self, claims: &[(ClientOrderId, ClientId)]) -> anyhow::Result<()> {
1155        if claims.is_empty() {
1156            return Ok(());
1157        }
1158
1159        let query = DatabaseQuery::IndexOrderClients(claims.to_vec());
1160        self.tx.send(query).map_err(|e| {
1161            anyhow::anyhow!(
1162                "Failed to send query index_order_clients to database message handler: {e}"
1163            )
1164        })
1165    }
1166
1167    fn update_actor(
1168        &self,
1169        actor_id: &ActorId,
1170        _state: &AHashMap<String, Bytes>,
1171    ) -> anyhow::Result<()> {
1172        anyhow::bail!("update_actor not implemented for PostgreSQL cache adapter: {actor_id}")
1173    }
1174
1175    fn update_strategy(
1176        &self,
1177        strategy_id: &StrategyId,
1178        _state: &AHashMap<String, Bytes>,
1179    ) -> anyhow::Result<()> {
1180        anyhow::bail!("update_strategy not implemented for PostgreSQL cache adapter: {strategy_id}")
1181    }
1182
1183    fn update_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1184        let query = DatabaseQuery::AddAccount(account_last_event(account)?, true);
1185        self.tx.send(query).map_err(|e| {
1186            anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
1187        })
1188    }
1189
1190    fn update_order(&self, event: &OrderEventAny) -> anyhow::Result<()> {
1191        let query = DatabaseQuery::UpdateOrder(event.clone());
1192        self.tx.send(query).map_err(|e| {
1193            anyhow::anyhow!("Failed to send query update_order to database message handler: {e}")
1194        })
1195    }
1196
1197    fn update_position(&self, position: &Position) -> anyhow::Result<()> {
1198        let query = if position.fill_voids.is_empty() {
1199            DatabaseQuery::UpdatePosition(position_last_event(position)?)
1200        } else {
1201            DatabaseQuery::AddPositionSnapshot(PositionSnapshot::from_replay_state(position, None))
1202        };
1203        self.tx.send(query).map_err(|e| {
1204            anyhow::anyhow!("Failed to send query update_position to database message handler: {e}")
1205        })
1206    }
1207
1208    fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()> {
1209        let snapshot = OrderSnapshot::from(order.clone());
1210        self.add_order_snapshot(&snapshot)
1211    }
1212
1213    fn snapshot_position_state(
1214        &self,
1215        position: &Position,
1216        ts_snapshot: UnixNanos,
1217        unrealized_pnl: Option<Money>,
1218    ) -> anyhow::Result<()> {
1219        let mut snapshot = if position.fill_voids.is_empty() {
1220            PositionSnapshot::from(position, unrealized_pnl)
1221        } else {
1222            PositionSnapshot::from_replay_state(position, unrealized_pnl)
1223        };
1224        snapshot.ts_init = ts_snapshot;
1225        self.add_position_snapshot(&snapshot)
1226    }
1227
1228    fn heartbeat(&self, _timestamp: UnixNanos) -> anyhow::Result<()> {
1229        todo!()
1230    }
1231}
1232
1233fn account_last_event(account: &AccountAny) -> anyhow::Result<AccountState> {
1234    account
1235        .last_event()
1236        .ok_or_else(|| anyhow::anyhow!("Cannot persist account with no events: {}", account.id()))
1237}
1238
1239fn order_initialized_event(order: &OrderAny) -> OrderInitialized {
1240    order.init_event().clone()
1241}
1242
1243fn position_last_event(position: &Position) -> anyhow::Result<OrderFilled> {
1244    position
1245        .last_event()
1246        .ok_or_else(|| anyhow::anyhow!("Cannot persist position with no events: {}", position.id))
1247}
1248
1249#[expect(
1250    clippy::too_many_lines,
1251    reason = "database command dispatch enumerates each cache query variant explicitly"
1252)]
1253async fn drain_buffer(pool: &PgPool, buffer: &mut VecDeque<DatabaseQuery>) {
1254    for cmd in buffer.drain(..) {
1255        let result: anyhow::Result<()> = match cmd {
1256            DatabaseQuery::Close => Ok(()),
1257            DatabaseQuery::Add(key, value) => DatabaseQueries::add(pool, key, value).await,
1258            DatabaseQuery::AddCurrency(currency) => {
1259                DatabaseQueries::add_currency(pool, currency).await
1260            }
1261            DatabaseQuery::AddInstrument(instrument_any) => match instrument_any {
1262                InstrumentAny::Betting(instrument) => {
1263                    DatabaseQueries::add_instrument(pool, "BETTING", Box::new(instrument)).await
1264                }
1265                InstrumentAny::BinaryOption(instrument) => {
1266                    DatabaseQueries::add_instrument(pool, "BINARY_OPTION", Box::new(instrument))
1267                        .await
1268                }
1269                InstrumentAny::CryptoFuture(instrument) => {
1270                    DatabaseQueries::add_instrument(pool, "CRYPTO_FUTURE", Box::new(instrument))
1271                        .await
1272                }
1273                InstrumentAny::CryptoFuturesSpread(instrument) => {
1274                    DatabaseQueries::add_instrument(
1275                        pool,
1276                        "CRYPTO_FUTURES_SPREAD",
1277                        Box::new(instrument),
1278                    )
1279                    .await
1280                }
1281                InstrumentAny::CryptoOption(instrument) => {
1282                    DatabaseQueries::add_instrument(pool, "CRYPTO_OPTION", Box::new(instrument))
1283                        .await
1284                }
1285                InstrumentAny::CryptoOptionSpread(instrument) => {
1286                    DatabaseQueries::add_instrument(
1287                        pool,
1288                        "CRYPTO_OPTION_SPREAD",
1289                        Box::new(instrument),
1290                    )
1291                    .await
1292                }
1293                InstrumentAny::CryptoPerpetual(instrument) => {
1294                    DatabaseQueries::add_instrument(pool, "CRYPTO_PERPETUAL", Box::new(instrument))
1295                        .await
1296                }
1297                InstrumentAny::CurrencyPair(instrument) => {
1298                    DatabaseQueries::add_instrument(pool, "CURRENCY_PAIR", Box::new(instrument))
1299                        .await
1300                }
1301                InstrumentAny::Equity(equity) => {
1302                    DatabaseQueries::add_instrument(pool, "EQUITY", Box::new(equity)).await
1303                }
1304                InstrumentAny::FuturesContract(instrument) => {
1305                    DatabaseQueries::add_instrument(pool, "FUTURES_CONTRACT", Box::new(instrument))
1306                        .await
1307                }
1308                InstrumentAny::FuturesSpread(instrument) => {
1309                    DatabaseQueries::add_instrument(pool, "FUTURES_SPREAD", Box::new(instrument))
1310                        .await
1311                }
1312                InstrumentAny::OptionContract(instrument) => {
1313                    DatabaseQueries::add_instrument(pool, "OPTION_CONTRACT", Box::new(instrument))
1314                        .await
1315                }
1316                InstrumentAny::Commodity(instrument) => {
1317                    DatabaseQueries::add_instrument(pool, "COMMODITY", Box::new(instrument)).await
1318                }
1319                InstrumentAny::IndexInstrument(instrument) => {
1320                    DatabaseQueries::add_instrument(pool, "INDEX_INSTRUMENT", Box::new(instrument))
1321                        .await
1322                }
1323                InstrumentAny::Cfd(instrument) => {
1324                    DatabaseQueries::add_instrument(pool, "CFD", Box::new(instrument)).await
1325                }
1326                InstrumentAny::OptionSpread(instrument) => {
1327                    DatabaseQueries::add_instrument(pool, "OPTION_SPREAD", Box::new(instrument))
1328                        .await
1329                }
1330                InstrumentAny::PerpetualContract(instrument) => {
1331                    DatabaseQueries::add_instrument(
1332                        pool,
1333                        "PERPETUAL_CONTRACT",
1334                        Box::new(instrument),
1335                    )
1336                    .await
1337                }
1338                InstrumentAny::TokenizedAsset(instrument) => {
1339                    DatabaseQueries::add_instrument(pool, "TOKENIZED_ASSET", Box::new(instrument))
1340                        .await
1341                }
1342            },
1343            DatabaseQuery::AddOrder(event, client_id) => {
1344                DatabaseQueries::add_order(pool, event, client_id).await
1345            }
1346            DatabaseQuery::AddOrderSnapshot(snapshot) => {
1347                DatabaseQueries::add_order_snapshot(pool, snapshot).await
1348            }
1349            DatabaseQuery::AddPosition(position_id, event) => {
1350                DatabaseQueries::add_position(pool, position_id, &event).await
1351            }
1352            DatabaseQuery::AddPositionSnapshot(snapshot) => {
1353                DatabaseQueries::add_position_snapshot(pool, snapshot).await
1354            }
1355            DatabaseQuery::AddAccount(event, updated) => {
1356                DatabaseQueries::add_account(pool, updated, event).await
1357            }
1358            DatabaseQuery::AddSignal(signal) => DatabaseQueries::add_signal(pool, &signal).await,
1359            DatabaseQuery::AddCustom(data) => DatabaseQueries::add_custom_data(pool, &data).await,
1360            DatabaseQuery::AddQuote(quote) => DatabaseQueries::add_quote(pool, &quote).await,
1361            DatabaseQuery::AddTrade(trade) => DatabaseQueries::add_trade(pool, &trade).await,
1362            DatabaseQuery::AddBar(bar) => DatabaseQueries::add_bar(pool, &bar).await,
1363            DatabaseQuery::UpdateOrder(event) => {
1364                DatabaseQueries::add_order_event(pool, event.into_boxed(), None).await
1365            }
1366            DatabaseQuery::UpdatePosition(event) => {
1367                DatabaseQueries::update_position(pool, &event).await
1368            }
1369            DatabaseQuery::IndexOrderPosition(client_order_id, position_id) => {
1370                DatabaseQueries::index_order_position(pool, client_order_id, position_id).await
1371            }
1372            DatabaseQuery::IndexOrderClients(claims) => {
1373                DatabaseQueries::index_order_clients(pool, &claims).await
1374            }
1375        };
1376
1377        if let Err(e) = result {
1378            log::error!("Error on query: {e:?}");
1379        }
1380    }
1381}