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