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