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              ('position', 'signed_qty'), ('position', 'avg_px_open'), ('position', 'avg_px_close'))
380          AND data_type <> 'numeric'
381        ORDER BY table_name, column_name",
382    )
383    .fetch_all(pool)
384    .await?;
385
386    if stale.iter().any(|column| column.starts_with("position.")) {
387        return Err(sqlx::Error::Configuration(
388            format!("Postgres position accounting requires numeric columns, found {}; create a new cache with the current schema", stale.join(", ")).into(),
389        ));
390    }
391
392    if !stale.is_empty() {
393        return Err(sqlx::Error::Configuration(
394            format!(
395                "Postgres schema is out of date, {} should be `numeric`: {SCHEMA_MIGRATION_COMMAND}",
396                stale.join(", "),
397            )
398            .into(),
399        ));
400    }
401
402    check_trader_keys(pool).await?;
403
404    let missing: Vec<String> = sqlx::query_scalar(
405        "SELECT required.table_name || '.' || required.column_name
406        FROM (VALUES
407            ('instrument', 'info'),
408            ('account_event', 'trader_id'),
409            ('order_position_index', 'trader_id'),
410            ('order_event', 'released_price'),
411            ('order_event', 'protection_price'),
412            ('order_event', 'due_post_only'),
413            ('order_event', 'correction_id'),
414            ('order_event', 'is_reopened'),
415            ('order_event', 'info'),
416            ('order_event', 'causation_id'),
417            ('position_event', 'reconciliation'),
418            ('position_event', 'info'),
419            ('position_event', 'causation_id')
420        ) AS required(table_name, column_name)
421        WHERE NOT EXISTS (
422            SELECT 1
423            FROM information_schema.columns
424            WHERE table_schema = current_schema()
425              AND table_name = required.table_name
426              AND column_name = required.column_name
427        )
428        ORDER BY 1",
429    )
430    .fetch_all(pool)
431    .await?;
432
433    if missing.is_empty() {
434        return Ok(());
435    }
436
437    Err(sqlx::Error::Configuration(
438        format!(
439            "Postgres schema is out of date, missing cache columns {}: {SCHEMA_MIGRATION_COMMAND}",
440            missing.join(", "),
441        )
442        .into(),
443    ))
444}
445
446// Client order and position IDs are only unique per trader, so the snapshot and index keys must be
447// trader-qualified or two traders sharing one database overwrite each other's rows.
448async fn check_trader_keys(pool: &PgPool) -> Result<(), sqlx::Error> {
449    let unscoped_keys: Vec<String> = sqlx::query_scalar(
450        "SELECT constraints.table_name
451        FROM information_schema.table_constraints AS constraints
452        WHERE constraints.table_schema = current_schema()
453          AND constraints.constraint_type = 'PRIMARY KEY'
454          AND constraints.table_name IN ('order', 'position', 'order_position_index')
455          AND NOT EXISTS (
456            SELECT 1
457            FROM information_schema.key_column_usage AS keys
458            WHERE keys.constraint_schema = constraints.constraint_schema
459              AND keys.constraint_name = constraints.constraint_name
460              AND keys.column_name = 'trader_id'
461          )
462        ORDER BY 1",
463    )
464    .fetch_all(pool)
465    .await?;
466
467    if unscoped_keys.is_empty() {
468        return Ok(());
469    }
470
471    Err(sqlx::Error::Configuration(
472        format!(
473            "Postgres schema is out of date, primary keys of {} are not trader-qualified: \
474             {SCHEMA_MIGRATION_COMMAND}",
475            unscoped_keys.join(", "),
476        )
477        .into(),
478    ))
479}
480
481// Account events written before trader-scoped persistence carry no trader. Nothing establishes which
482// trader owns them, so connecting would silently omit persisted account state: refuse until every
483// such account is assigned. `nautilus database assign-account` connects directly, so it stays usable
484// while this blocks the cache.
485async fn check_account_ownership(pool: &PgPool) -> Result<(), sqlx::Error> {
486    let account_ids = DatabaseQueries::load_unassigned_account_ids(pool)
487        .await
488        .map_err(|e| {
489            sqlx::Error::Configuration(
490                format!("Failed to check account event ownership: {e}").into(),
491            )
492        })?;
493
494    if account_ids.is_empty() {
495        return Ok(());
496    }
497
498    let account_ids: Vec<String> = account_ids.iter().map(ToString::to_string).collect();
499    Err(sqlx::Error::Configuration(
500        format!(
501            "Postgres cache has account events with no trader for {}: assign each account with \
502             `nautilus database assign-account --account-id <ACCOUNT_ID> --trader-id <TRADER_ID>` \
503             before connecting",
504            account_ids.join(", "),
505        )
506        .into(),
507    ))
508}
509
510async fn handle_query(
511    maybe_msg: Option<DatabaseQuery>,
512    buffer: &mut VecDeque<DatabaseQuery>,
513    buffer_interval: Duration,
514    pool: &PgPool,
515    trader_id: &TraderId,
516) -> ControlFlow<()> {
517    let Some(msg) = maybe_msg else {
518        log::debug!("Command channel closed");
519        return ControlFlow::Break(());
520    };
521
522    if matches!(msg, DatabaseQuery::Close) {
523        if !buffer.is_empty() {
524            drain_buffer(pool, trader_id, buffer).await;
525        }
526        return ControlFlow::Break(());
527    }
528
529    buffer.push_back(msg);
530
531    if buffer_interval.is_zero() {
532        drain_buffer(pool, trader_id, buffer).await;
533    }
534
535    ControlFlow::Continue(())
536}
537
538async fn flush_buffer(
539    buffer: &mut VecDeque<DatabaseQuery>,
540    pool: &PgPool,
541    trader_id: &TraderId,
542    flush_timer: &mut Pin<&mut tokio::time::Sleep>,
543    buffer_interval: Duration,
544) {
545    if !buffer.is_empty() {
546        drain_buffer(pool, trader_id, buffer).await;
547    }
548    flush_timer.as_mut().reset(Instant::now() + buffer_interval);
549}
550
551/// Retrieves a `PostgresCacheDatabase` for `trader_id` using default connection options.
552///
553/// # Errors
554///
555/// Returns an error if connecting to the database or initializing the cache adapter fails.
556pub async fn get_pg_cache_database(trader_id: TraderId) -> anyhow::Result<PostgresCacheDatabase> {
557    let connect_options = get_postgres_connect_options(None, None, None, None, None);
558    Ok(PostgresCacheDatabase::connect(
559        Some(connect_options.host),
560        Some(connect_options.port),
561        Some(connect_options.username),
562        Some(connect_options.password),
563        Some(connect_options.database),
564        trader_id,
565    )
566    .await?)
567}
568
569#[async_trait::async_trait]
570impl CacheDatabaseAdapter for PostgresCacheDatabase {
571    fn close(&mut self) -> anyhow::Result<()> {
572        let pool = self.pool.clone();
573        let (tx, rx) = std::sync::mpsc::channel();
574
575        // The close command follows all pending writes on the FIFO channel and drains the buffer
576        if let Err(e) = self.tx.send(DatabaseQuery::Close) {
577            log::warn!("Error sending close: {e:?}");
578        }
579
580        log_task_awaiting("cache-write");
581
582        tokio::task::block_in_place(|| {
583            if let Err(e) = get_runtime().block_on(&mut self.handle) {
584                log::error!("Error awaiting task 'cache-write': {e:?}");
585            }
586        });
587
588        log::debug!("Closing connection pool");
589
590        tokio::task::block_in_place(|| {
591            get_runtime().block_on(async {
592                pool.close().await;
593
594                if let Err(e) = tx.send(()) {
595                    log::error!("Error closing pool: {e:?}");
596                }
597            });
598        });
599
600        log::debug!("Closed");
601
602        Ok(rx.recv()?)
603    }
604
605    fn flush(&mut self) -> anyhow::Result<()> {
606        let pool = self.pool.clone();
607        let trader_id = self.trader_id;
608
609        tokio::task::block_in_place(|| {
610            get_runtime().block_on(DatabaseQueries::delete_trader(&pool, &trader_id))
611        })
612        .map_err(|e| anyhow::anyhow!("Failed to flush Postgres cache for {trader_id}: {e}"))
613    }
614
615    async fn load_all(&self) -> anyhow::Result<CacheMap> {
616        let mut currencies = self.load_currencies().await?;
617        register_loaded_currencies(&mut currencies)?;
618
619        let (instruments, instrument_closes, synthetics, accounts, orders, positions) = try_join!(
620            self.load_instruments(),
621            self.load_instrument_closes(),
622            self.load_synthetics(),
623            self.load_accounts(),
624            self.load_orders(),
625            self.load_positions()
626        )
627        .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
628
629        // For now, we don't load greeks and yield curves from the database
630        // This will be implemented in the future
631        let greeks = AHashMap::new();
632        let yield_curves = AHashMap::new();
633
634        Ok(CacheMap {
635            currencies,
636            instruments,
637            instrument_closes,
638            synthetics,
639            accounts,
640            orders,
641            positions,
642            greeks,
643            yield_curves,
644        })
645    }
646
647    fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>> {
648        let pool = self.pool.clone();
649        let (tx, rx) = std::sync::mpsc::channel();
650
651        tokio::spawn(async move {
652            let result = DatabaseQueries::load(&pool).await;
653            match result {
654                Ok(items) => {
655                    let mapping = items
656                        .into_iter()
657                        .map(|(k, v)| (k, Bytes::from(v)))
658                        .collect();
659
660                    if let Err(e) = tx.send(mapping) {
661                        log::error!("Failed to send general items: {e:?}");
662                    }
663                }
664                Err(e) => {
665                    log::error!("Failed to load general items: {e:?}");
666                    if let Err(e) = tx.send(AHashMap::new()) {
667                        log::error!("Failed to send empty general items: {e:?}");
668                    }
669                }
670            }
671        });
672        Ok(rx.recv()?)
673    }
674
675    async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>> {
676        let pool = self.pool.clone();
677        let (tx, rx) = std::sync::mpsc::channel();
678
679        tokio::spawn(async move {
680            let result = DatabaseQueries::load_currencies(&pool).await;
681            match result {
682                Ok(currencies) => {
683                    let mapping = currencies
684                        .into_iter()
685                        .map(|currency| (currency.code, currency))
686                        .collect();
687
688                    if let Err(e) = tx.send(mapping) {
689                        log::error!("Failed to send currencies: {e:?}");
690                    }
691                }
692                Err(e) => {
693                    log::error!("Failed to load currencies: {e:?}");
694                    if let Err(e) = tx.send(AHashMap::new()) {
695                        log::error!("Failed to send empty currencies: {e:?}");
696                    }
697                }
698            }
699        });
700        Ok(rx.recv()?)
701    }
702
703    async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
704        let pool = self.pool.clone();
705        let (tx, rx) = std::sync::mpsc::channel();
706
707        tokio::spawn(async move {
708            let result = DatabaseQueries::load_instruments(&pool).await;
709            match result {
710                Ok(instruments) => {
711                    let mapping = instruments
712                        .into_iter()
713                        .map(|instrument| (instrument.id(), instrument))
714                        .collect();
715
716                    if let Err(e) = tx.send(mapping) {
717                        log::error!("Failed to send instruments: {e:?}");
718                    }
719                }
720                Err(e) => {
721                    log::error!("Failed to load instruments: {e:?}");
722                    if let Err(e) = tx.send(AHashMap::new()) {
723                        log::error!("Failed to send empty instruments: {e:?}");
724                    }
725                }
726            }
727        });
728        Ok(rx.recv()?)
729    }
730
731    async fn load_instrument_closes(
732        &self,
733    ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>> {
734        Ok(DatabaseQueries::load_instrument_closes(&self.pool)
735            .await?
736            .into_iter()
737            .map(|close| (close.instrument_id, close))
738            .collect())
739    }
740
741    async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
742        Ok(AHashMap::new())
743    }
744
745    async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
746        let pool = self.pool.clone();
747        let trader_id = self.trader_id;
748        let (tx, rx) = std::sync::mpsc::channel();
749
750        tokio::spawn(async move {
751            let result = DatabaseQueries::load_accounts(&pool, &trader_id).await;
752            match result {
753                Ok(accounts) => {
754                    let mapping = accounts
755                        .into_iter()
756                        .map(|account| (account.id(), account))
757                        .collect();
758
759                    if let Err(e) = tx.send(mapping) {
760                        log::error!("Failed to send accounts: {e:?}");
761                    }
762                }
763                Err(e) => {
764                    log::error!("Failed to load accounts: {e:?}");
765                    if let Err(e) = tx.send(AHashMap::new()) {
766                        log::error!("Failed to send empty accounts: {e:?}");
767                    }
768                }
769            }
770        });
771        Ok(rx.recv()?)
772    }
773
774    async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
775        let pool = self.pool.clone();
776        let trader_id = self.trader_id;
777        let (tx, rx) = std::sync::mpsc::channel();
778
779        tokio::spawn(async move {
780            let result = DatabaseQueries::load_orders(&pool, &trader_id).await;
781            match result {
782                Ok(orders) => {
783                    let mapping = orders
784                        .into_iter()
785                        .map(|order| (order.client_order_id(), order))
786                        .collect();
787
788                    if let Err(e) = tx.send(mapping) {
789                        log::error!("Failed to send orders: {e:?}");
790                    }
791                }
792                Err(e) => {
793                    log::error!("Failed to load orders: {e:?}");
794                    if let Err(e) = tx.send(AHashMap::new()) {
795                        log::error!("Failed to send empty orders: {e:?}");
796                    }
797                }
798            }
799        });
800        Ok(rx.recv()?)
801    }
802
803    async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>> {
804        let pool = self.pool.clone();
805        let trader_id = self.trader_id;
806        let (tx, rx) = std::sync::mpsc::channel();
807
808        tokio::spawn(async move {
809            let result = DatabaseQueries::load_positions(&pool, &trader_id)
810                .await
811                .map(|positions| {
812                    positions
813                        .into_iter()
814                        .map(|position| (position.id, position))
815                        .collect()
816                });
817
818            if let Err(e) = tx.send(result) {
819                log::error!("Failed to send positions: {e:?}");
820            }
821        });
822        rx.recv()?
823    }
824
825    fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
826        let pool = self.pool.clone();
827        let trader_id = self.trader_id;
828        let (tx, rx) = std::sync::mpsc::channel();
829
830        tokio::spawn(async move {
831            let result = DatabaseQueries::load_index_order_position(&pool, &trader_id).await;
832            match result {
833                Ok(index) => {
834                    if let Err(e) = tx.send(index) {
835                        log::error!("Failed to send load_index_order_position result: {e:?}");
836                    }
837                }
838                Err(e) => {
839                    log::error!("Failed to run query load_index_order_position: {e:?}");
840                    if let Err(e) = tx.send(AHashMap::new()) {
841                        log::error!("Failed to send empty load_index_order_position result: {e:?}");
842                    }
843                }
844            }
845        });
846        Ok(rx.recv()?)
847    }
848
849    fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
850        let pool = self.pool.clone();
851        let trader_id = self.trader_id;
852        let (tx, rx) = std::sync::mpsc::channel();
853
854        tokio::spawn(async move {
855            let result =
856                DatabaseQueries::load_distinct_order_event_client_ids(&pool, &trader_id).await;
857
858            match result {
859                Ok(currency) => {
860                    if let Err(e) = tx.send(currency) {
861                        log::error!("Failed to send load_index_order_client result: {e:?}");
862                    }
863                }
864                Err(e) => {
865                    log::error!("Failed to run query load_distinct_order_event_client_ids: {e:?}");
866                    if let Err(e) = tx.send(AHashMap::new()) {
867                        log::error!("Failed to send empty load_index_order_client result: {e:?}");
868                    }
869                }
870            }
871        });
872        Ok(rx.recv()?)
873    }
874
875    async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>> {
876        let pool = self.pool.clone();
877        let code = code.to_owned(); // Clone the code
878        let (tx, rx) = std::sync::mpsc::channel();
879
880        tokio::spawn(async move {
881            let result = DatabaseQueries::load_currency(&pool, &code).await;
882            match result {
883                Ok(currency) => {
884                    if let Err(e) = tx.send(currency) {
885                        log::error!("Failed to send currency {code}: {e:?}");
886                    }
887                }
888                Err(e) => {
889                    log::error!("Failed to load currency {code}: {e:?}");
890                    if let Err(e) = tx.send(None) {
891                        log::error!("Failed to send None for currency {code}: {e:?}");
892                    }
893                }
894            }
895        });
896        Ok(rx.recv()?)
897    }
898
899    async fn load_instrument(
900        &self,
901        instrument_id: &InstrumentId,
902    ) -> anyhow::Result<Option<InstrumentAny>> {
903        let pool = self.pool.clone();
904        let instrument_id = instrument_id.to_owned(); // Clone the instrument_id
905        let (tx, rx) = std::sync::mpsc::channel();
906
907        tokio::spawn(async move {
908            let result = DatabaseQueries::load_instrument(&pool, &instrument_id).await;
909            match result {
910                Ok(instrument) => {
911                    if let Err(e) = tx.send(instrument) {
912                        log::error!("Failed to send instrument {instrument_id}: {e:?}");
913                    }
914                }
915                Err(e) => {
916                    log::error!("Failed to load instrument {instrument_id}: {e:?}");
917                    if let Err(e) = tx.send(None) {
918                        log::error!("Failed to send None for instrument {instrument_id}: {e:?}");
919                    }
920                }
921            }
922        });
923        Ok(rx.recv()?)
924    }
925
926    async fn load_synthetic(
927        &self,
928        instrument_id: &InstrumentId,
929    ) -> anyhow::Result<Option<SyntheticInstrument>> {
930        anyhow::bail!(
931            "load_synthetic not implemented for PostgreSQL cache adapter: {instrument_id}"
932        )
933    }
934
935    async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>> {
936        let pool = self.pool.clone();
937        let trader_id = self.trader_id;
938        let account_id = account_id.to_owned();
939        let (tx, rx) = std::sync::mpsc::channel();
940
941        tokio::spawn(async move {
942            let result = DatabaseQueries::load_account(&pool, &account_id, &trader_id).await;
943            match result {
944                Ok(account) => {
945                    if let Err(e) = tx.send(account) {
946                        log::error!("Failed to send account {account_id}: {e:?}");
947                    }
948                }
949                Err(e) => {
950                    log::error!("Failed to load account {account_id}: {e:?}");
951                    if let Err(e) = tx.send(None) {
952                        log::error!("Failed to send None for account {account_id}: {e:?}");
953                    }
954                }
955            }
956        });
957        Ok(rx.recv()?)
958    }
959
960    async fn load_order(
961        &self,
962        client_order_id: &ClientOrderId,
963    ) -> anyhow::Result<Option<OrderAny>> {
964        let pool = self.pool.clone();
965        let trader_id = self.trader_id;
966        let client_order_id = client_order_id.to_owned();
967        let (tx, rx) = std::sync::mpsc::channel();
968
969        tokio::spawn(async move {
970            let result = DatabaseQueries::load_order(&pool, &client_order_id, &trader_id).await;
971            match result {
972                Ok(order) => {
973                    if let Err(e) = tx.send(order) {
974                        log::error!("Failed to send order {client_order_id}: {e:?}");
975                    }
976                }
977                Err(e) => {
978                    log::error!("Failed to load order {client_order_id}: {e:?}");
979                    let _ = tx.send(None);
980                }
981            }
982        });
983        Ok(rx.recv()?)
984    }
985
986    async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>> {
987        let pool = self.pool.clone();
988        let trader_id = self.trader_id;
989        let position_id = position_id.to_owned();
990        let (tx, rx) = std::sync::mpsc::channel();
991
992        tokio::spawn(async move {
993            let result = DatabaseQueries::load_position(&pool, &position_id, &trader_id).await;
994            if let Err(e) = tx.send(result) {
995                log::error!("Failed to send position {position_id}: {e:?}");
996            }
997        });
998        rx.recv()?
999    }
1000
1001    fn load_actor(&self, actor_id: &ActorId) -> anyhow::Result<AHashMap<String, Bytes>> {
1002        anyhow::bail!("load_actor not implemented for PostgreSQL cache adapter: {actor_id}")
1003    }
1004
1005    fn delete_actor(&self, _actor_id: &ActorId) -> anyhow::Result<()> {
1006        todo!()
1007    }
1008
1009    fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>> {
1010        anyhow::bail!("load_strategy not implemented for PostgreSQL cache adapter: {strategy_id}")
1011    }
1012
1013    fn delete_strategy(&self, _strategy_id: &StrategyId) -> anyhow::Result<()> {
1014        todo!()
1015    }
1016
1017    fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
1018        anyhow::bail!(
1019            "delete_order not implemented for PostgreSQL cache adapter: {client_order_id}"
1020        )
1021    }
1022
1023    fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
1024        anyhow::bail!("delete_position not implemented for PostgreSQL cache adapter: {position_id}")
1025    }
1026
1027    fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()> {
1028        anyhow::bail!(
1029            "delete_account_event not implemented for PostgreSQL cache adapter: {account_id}, {event_id}"
1030        )
1031    }
1032
1033    fn add(&self, key: String, value: Bytes) -> anyhow::Result<()> {
1034        let query = DatabaseQuery::Add(key, value.into());
1035        self.tx
1036            .send(query)
1037            .map_err(|e| anyhow::anyhow!("Failed to send query to database message handler: {e}"))
1038    }
1039
1040    fn add_currency(&self, currency: &Currency) -> anyhow::Result<()> {
1041        let query = DatabaseQuery::AddCurrency(*currency);
1042        self.tx.send(query).map_err(|e| {
1043            anyhow::anyhow!("Failed to query add_currency to database message handler: {e}")
1044        })
1045    }
1046
1047    fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()> {
1048        let query = DatabaseQuery::AddInstrument(instrument.clone());
1049        self.tx.send(query).map_err(|e| {
1050            anyhow::anyhow!("Failed to send query add_instrument to database message handler: {e}")
1051        })
1052    }
1053
1054    fn add_instrument_close(&self, close: &InstrumentClose) -> anyhow::Result<()> {
1055        self.tx
1056            .send(DatabaseQuery::AddInstrumentClose(*close))
1057            .map_err(|e| {
1058                anyhow::anyhow!(
1059                    "Failed to send query add_instrument_close to database message handler: {e}"
1060                )
1061            })
1062    }
1063
1064    fn add_synthetic(&self, _synthetic: &SyntheticInstrument) -> anyhow::Result<()> {
1065        todo!()
1066    }
1067
1068    fn add_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1069        let query = DatabaseQuery::AddAccount(account_last_event(account)?, false);
1070        self.tx.send(query).map_err(|e| {
1071            anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
1072        })
1073    }
1074
1075    fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()> {
1076        let query = DatabaseQuery::AddOrder(order_initialized_event(order), client_id);
1077        self.tx.send(query).map_err(|e| {
1078            anyhow::anyhow!("Failed to send query add_order to database message handler: {e}")
1079        })
1080    }
1081
1082    fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()> {
1083        let query = DatabaseQuery::AddOrderSnapshot(snapshot.to_owned());
1084        self.tx.send(query).map_err(|e| {
1085            anyhow::anyhow!(
1086                "Failed to send query add_order_snapshot to database message handler: {e}"
1087            )
1088        })
1089    }
1090
1091    fn add_position(&self, position: &Position) -> anyhow::Result<()> {
1092        let event = position_last_event(position)?;
1093        let query = DatabaseQuery::AddPosition(position.id, event);
1094        self.tx.send(query).map_err(|e| {
1095            anyhow::anyhow!("Failed to send query add_position to database message handler: {e}")
1096        })?;
1097
1098        if position.requires_replay_state() {
1099            self.add_position_snapshot(&PositionSnapshot::from_replay_state(position, None))?;
1100        }
1101
1102        Ok(())
1103    }
1104
1105    fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()> {
1106        let query = DatabaseQuery::AddPositionSnapshot(snapshot.to_owned());
1107        self.tx.send(query).map_err(|e| {
1108            anyhow::anyhow!(
1109                "Failed to send query add_position_snapshot to database message handler: {e}"
1110            )
1111        })
1112    }
1113
1114    fn add_order_book(&self, _order_book: &OrderBook) -> anyhow::Result<()> {
1115        todo!()
1116    }
1117
1118    fn add_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
1119        let query = DatabaseQuery::AddQuote(quote.to_owned());
1120        self.tx.send(query).map_err(|e| {
1121            anyhow::anyhow!("Failed to send query add_quote to database message handler: {e}")
1122        })
1123    }
1124
1125    fn load_quotes(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>> {
1126        let pool = self.pool.clone();
1127        let instrument_id = instrument_id.to_owned();
1128        let (tx, rx) = std::sync::mpsc::channel();
1129
1130        tokio::spawn(async move {
1131            let result = DatabaseQueries::load_quotes(&pool, &instrument_id).await;
1132            match result {
1133                Ok(quotes) => {
1134                    if let Err(e) = tx.send(quotes) {
1135                        log::error!("Failed to send quotes for instrument {instrument_id}: {e:?}");
1136                    }
1137                }
1138                Err(e) => {
1139                    log::error!("Failed to load quotes for instrument {instrument_id}: {e:?}");
1140                    if let Err(e) = tx.send(Vec::new()) {
1141                        log::error!(
1142                            "Failed to send empty quotes for instrument {instrument_id}: {e:?}"
1143                        );
1144                    }
1145                }
1146            }
1147        });
1148        Ok(rx.recv()?)
1149    }
1150
1151    fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
1152        let query = DatabaseQuery::AddTrade(trade.to_owned());
1153        self.tx.send(query).map_err(|e| {
1154            anyhow::anyhow!("Failed to send query add_trade to database message handler: {e}")
1155        })
1156    }
1157
1158    fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
1159        let pool = self.pool.clone();
1160        let instrument_id = instrument_id.to_owned();
1161        let (tx, rx) = std::sync::mpsc::channel();
1162
1163        tokio::spawn(async move {
1164            let result = DatabaseQueries::load_trades(&pool, &instrument_id).await;
1165            match result {
1166                Ok(trades) => {
1167                    if let Err(e) = tx.send(trades) {
1168                        log::error!("Failed to send trades for instrument {instrument_id}: {e:?}");
1169                    }
1170                }
1171                Err(e) => {
1172                    log::error!("Failed to load trades for instrument {instrument_id}: {e:?}");
1173                    if let Err(e) = tx.send(Vec::new()) {
1174                        log::error!(
1175                            "Failed to send empty trades for instrument {instrument_id}: {e:?}"
1176                        );
1177                    }
1178                }
1179            }
1180        });
1181        Ok(rx.recv()?)
1182    }
1183
1184    fn add_funding_rate(&self, _funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
1185        anyhow::bail!("add_funding_rate not implemented for PostgreSQL cache adapter")
1186    }
1187
1188    fn load_funding_rates(
1189        &self,
1190        _instrument_id: &InstrumentId,
1191    ) -> anyhow::Result<Vec<FundingRateUpdate>> {
1192        anyhow::bail!("load_funding_rates not implemented for PostgreSQL cache adapter")
1193    }
1194
1195    fn add_bar(&self, bar: &Bar) -> anyhow::Result<()> {
1196        let query = DatabaseQuery::AddBar(bar.to_owned());
1197        self.tx.send(query).map_err(|e| {
1198            anyhow::anyhow!("Failed to send query add_bar to database message handler: {e}")
1199        })
1200    }
1201
1202    fn load_bars(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>> {
1203        let pool = self.pool.clone();
1204        let instrument_id = instrument_id.to_owned();
1205        let (tx, rx) = std::sync::mpsc::channel();
1206
1207        tokio::spawn(async move {
1208            let result = DatabaseQueries::load_bars(&pool, &instrument_id).await;
1209            match result {
1210                Ok(bars) => {
1211                    if let Err(e) = tx.send(bars) {
1212                        log::error!("Failed to send bars for instrument {instrument_id}: {e:?}");
1213                    }
1214                }
1215                Err(e) => {
1216                    log::error!("Failed to load bars for instrument {instrument_id}: {e:?}");
1217                    if let Err(e) = tx.send(Vec::new()) {
1218                        log::error!(
1219                            "Failed to send empty bars for instrument {instrument_id}: {e:?}"
1220                        );
1221                    }
1222                }
1223            }
1224        });
1225        Ok(rx.recv()?)
1226    }
1227
1228    fn add_signal(&self, signal: &Signal) -> anyhow::Result<()> {
1229        let query = DatabaseQuery::AddSignal(signal.to_owned());
1230        self.tx.send(query).map_err(|e| {
1231            anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
1232        })
1233    }
1234
1235    fn load_signals(&self, name: &str) -> anyhow::Result<Vec<Signal>> {
1236        let pool = self.pool.clone();
1237        let name = name.to_owned();
1238        let (tx, rx) = std::sync::mpsc::channel();
1239
1240        tokio::spawn(async move {
1241            let result = DatabaseQueries::load_signals(&pool, &name).await;
1242            match result {
1243                Ok(signals) => {
1244                    if let Err(e) = tx.send(signals) {
1245                        log::error!("Failed to send signals for '{name}': {e:?}");
1246                    }
1247                }
1248                Err(e) => {
1249                    log::error!("Failed to load signals for '{name}': {e:?}");
1250                    if let Err(e) = tx.send(Vec::new()) {
1251                        log::error!("Failed to send empty signals for '{name}': {e:?}");
1252                    }
1253                }
1254            }
1255        });
1256        Ok(rx.recv()?)
1257    }
1258
1259    fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
1260        let query = DatabaseQuery::AddCustom(data.to_owned());
1261        self.tx.send(query).map_err(|e| {
1262            anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
1263        })
1264    }
1265
1266    fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
1267        let pool = self.pool.clone();
1268        let data_type = data_type.to_owned();
1269        let (tx, rx) = std::sync::mpsc::channel();
1270
1271        tokio::spawn(async move {
1272            let result = DatabaseQueries::load_custom_data(&pool, &data_type).await;
1273            match result {
1274                Ok(signals) => {
1275                    if let Err(e) = tx.send(signals) {
1276                        log::error!("Failed to send custom data for '{data_type}': {e:?}");
1277                    }
1278                }
1279                Err(e) => {
1280                    log::error!("Failed to load custom data for '{data_type}': {e:?}");
1281                    if let Err(e) = tx.send(Vec::new()) {
1282                        log::error!("Failed to send empty custom data for '{data_type}': {e:?}");
1283                    }
1284                }
1285            }
1286        });
1287        Ok(rx.recv()?)
1288    }
1289
1290    fn load_order_snapshot(
1291        &self,
1292        client_order_id: &ClientOrderId,
1293    ) -> anyhow::Result<Option<OrderSnapshot>> {
1294        let pool = self.pool.clone();
1295        let trader_id = self.trader_id;
1296        let client_order_id = client_order_id.to_owned();
1297        let (tx, rx) = std::sync::mpsc::channel();
1298
1299        tokio::spawn(async move {
1300            let result =
1301                DatabaseQueries::load_order_snapshot(&pool, &client_order_id, &trader_id).await;
1302
1303            match result {
1304                Ok(snapshot) => {
1305                    if let Err(e) = tx.send(snapshot) {
1306                        log::error!("Failed to send order snapshot {client_order_id}: {e:?}");
1307                    }
1308                }
1309                Err(e) => {
1310                    log::error!("Failed to load order snapshot {client_order_id}: {e:?}");
1311                    if let Err(e) = tx.send(None) {
1312                        log::error!(
1313                            "Failed to send None for order snapshot {client_order_id}: {e:?}"
1314                        );
1315                    }
1316                }
1317            }
1318        });
1319        Ok(rx.recv()?)
1320    }
1321
1322    fn load_position_snapshot(
1323        &self,
1324        position_id: &PositionId,
1325    ) -> anyhow::Result<Option<PositionSnapshot>> {
1326        let pool = self.pool.clone();
1327        let trader_id = self.trader_id;
1328        let position_id = position_id.to_owned();
1329        let (tx, rx) = std::sync::mpsc::channel();
1330
1331        tokio::spawn(async move {
1332            let result =
1333                DatabaseQueries::load_position_snapshot(&pool, &position_id, &trader_id).await;
1334
1335            match result {
1336                Ok(snapshot) => {
1337                    if let Err(e) = tx.send(snapshot) {
1338                        log::error!("Failed to send position snapshot {position_id}: {e:?}");
1339                    }
1340                }
1341                Err(e) => {
1342                    log::error!("Failed to load position snapshot {position_id}: {e:?}");
1343                    if let Err(e) = tx.send(None) {
1344                        log::error!(
1345                            "Failed to send None for position snapshot {position_id}: {e:?}"
1346                        );
1347                    }
1348                }
1349            }
1350        });
1351        Ok(rx.recv()?)
1352    }
1353
1354    fn index_venue_order_id(
1355        &self,
1356        _client_order_id: ClientOrderId,
1357        _venue_order_id: VenueOrderId,
1358    ) -> anyhow::Result<()> {
1359        todo!()
1360    }
1361
1362    fn index_order_position(
1363        &self,
1364        client_order_id: ClientOrderId,
1365        position_id: PositionId,
1366    ) -> anyhow::Result<()> {
1367        let query = DatabaseQuery::IndexOrderPosition(client_order_id, position_id);
1368        self.tx.send(query).map_err(|e| {
1369            anyhow::anyhow!(
1370                "Failed to send query index_order_position to database message handler: {e}"
1371            )
1372        })
1373    }
1374
1375    fn index_order_clients(&self, claims: &[(ClientOrderId, ClientId)]) -> anyhow::Result<()> {
1376        if claims.is_empty() {
1377            return Ok(());
1378        }
1379
1380        let query = DatabaseQuery::IndexOrderClients(claims.to_vec());
1381        self.tx.send(query).map_err(|e| {
1382            anyhow::anyhow!(
1383                "Failed to send query index_order_clients to database message handler: {e}"
1384            )
1385        })
1386    }
1387
1388    fn update_actor(
1389        &self,
1390        actor_id: &ActorId,
1391        _state: &AHashMap<String, Bytes>,
1392    ) -> anyhow::Result<()> {
1393        anyhow::bail!("update_actor not implemented for PostgreSQL cache adapter: {actor_id}")
1394    }
1395
1396    fn update_strategy(
1397        &self,
1398        strategy_id: &StrategyId,
1399        _state: &AHashMap<String, Bytes>,
1400    ) -> anyhow::Result<()> {
1401        anyhow::bail!("update_strategy not implemented for PostgreSQL cache adapter: {strategy_id}")
1402    }
1403
1404    fn update_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1405        let query = DatabaseQuery::AddAccount(account_last_event(account)?, true);
1406        self.tx.send(query).map_err(|e| {
1407            anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
1408        })
1409    }
1410
1411    fn update_order(&self, event: &OrderEventAny) -> anyhow::Result<()> {
1412        let query = DatabaseQuery::UpdateOrder(event.clone());
1413        self.tx.send(query).map_err(|e| {
1414            anyhow::anyhow!("Failed to send query update_order to database message handler: {e}")
1415        })
1416    }
1417
1418    fn update_position(&self, position: &Position) -> anyhow::Result<()> {
1419        let query = if position.requires_replay_state() {
1420            DatabaseQuery::AddPositionSnapshot(PositionSnapshot::from_replay_state(position, None))
1421        } else {
1422            DatabaseQuery::UpdatePosition(position_last_event(position)?)
1423        };
1424        self.tx.send(query).map_err(|e| {
1425            anyhow::anyhow!("Failed to send query update_position to database message handler: {e}")
1426        })
1427    }
1428
1429    fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()> {
1430        let snapshot = OrderSnapshot::from(order.clone());
1431        self.add_order_snapshot(&snapshot)
1432    }
1433
1434    fn snapshot_position_state(
1435        &self,
1436        position: &Position,
1437        ts_snapshot: UnixNanos,
1438        unrealized_pnl: Option<Money>,
1439    ) -> anyhow::Result<()> {
1440        let mut snapshot = if position.requires_replay_state() {
1441            PositionSnapshot::from_replay_state(position, unrealized_pnl)
1442        } else {
1443            PositionSnapshot::from(position, unrealized_pnl)
1444        };
1445        snapshot.ts_init = ts_snapshot;
1446        self.add_position_snapshot(&snapshot)
1447    }
1448
1449    fn heartbeat(&self, _timestamp: UnixNanos) -> anyhow::Result<()> {
1450        todo!()
1451    }
1452}
1453
1454fn account_last_event(account: &AccountAny) -> anyhow::Result<AccountState> {
1455    account
1456        .last_event()
1457        .ok_or_else(|| anyhow::anyhow!("Cannot persist account with no events: {}", account.id()))
1458}
1459
1460fn order_initialized_event(order: &OrderAny) -> OrderInitialized {
1461    order.init_event().clone()
1462}
1463
1464fn position_last_event(position: &Position) -> anyhow::Result<OrderFilled> {
1465    position
1466        .last_event()
1467        .ok_or_else(|| anyhow::anyhow!("Cannot persist position with no events: {}", position.id))
1468}
1469
1470#[expect(
1471    clippy::too_many_lines,
1472    reason = "database command dispatch enumerates each cache query variant explicitly"
1473)]
1474async fn drain_buffer(pool: &PgPool, trader_id: &TraderId, buffer: &mut VecDeque<DatabaseQuery>) {
1475    for cmd in buffer.drain(..) {
1476        let result: anyhow::Result<()> = match cmd {
1477            DatabaseQuery::Close => Ok(()),
1478            DatabaseQuery::Add(key, value) => DatabaseQueries::add(pool, key, value).await,
1479            DatabaseQuery::AddCurrency(currency) => {
1480                DatabaseQueries::add_currency(pool, currency).await
1481            }
1482            DatabaseQuery::AddInstrument(instrument_any) => match instrument_any {
1483                InstrumentAny::Betting(instrument) => {
1484                    DatabaseQueries::add_instrument(pool, "BETTING", Box::new(instrument)).await
1485                }
1486                InstrumentAny::BinaryOption(instrument) => {
1487                    DatabaseQueries::add_instrument(pool, "BINARY_OPTION", Box::new(instrument))
1488                        .await
1489                }
1490                InstrumentAny::CryptoFuture(instrument) => {
1491                    DatabaseQueries::add_instrument(pool, "CRYPTO_FUTURE", Box::new(instrument))
1492                        .await
1493                }
1494                InstrumentAny::CryptoFuturesSpread(instrument) => {
1495                    DatabaseQueries::add_instrument(
1496                        pool,
1497                        "CRYPTO_FUTURES_SPREAD",
1498                        Box::new(instrument),
1499                    )
1500                    .await
1501                }
1502                InstrumentAny::CryptoOption(instrument) => {
1503                    DatabaseQueries::add_instrument(pool, "CRYPTO_OPTION", Box::new(instrument))
1504                        .await
1505                }
1506                InstrumentAny::CryptoOptionSpread(instrument) => {
1507                    DatabaseQueries::add_instrument(
1508                        pool,
1509                        "CRYPTO_OPTION_SPREAD",
1510                        Box::new(instrument),
1511                    )
1512                    .await
1513                }
1514                InstrumentAny::CryptoPerpetual(instrument) => {
1515                    DatabaseQueries::add_instrument(pool, "CRYPTO_PERPETUAL", Box::new(instrument))
1516                        .await
1517                }
1518                InstrumentAny::CurrencyPair(instrument) => {
1519                    DatabaseQueries::add_instrument(pool, "CURRENCY_PAIR", Box::new(instrument))
1520                        .await
1521                }
1522                InstrumentAny::Equity(equity) => {
1523                    DatabaseQueries::add_instrument(pool, "EQUITY", Box::new(equity)).await
1524                }
1525                InstrumentAny::FuturesContract(instrument) => {
1526                    DatabaseQueries::add_instrument(pool, "FUTURES_CONTRACT", Box::new(instrument))
1527                        .await
1528                }
1529                InstrumentAny::FuturesSpread(instrument) => {
1530                    DatabaseQueries::add_instrument(pool, "FUTURES_SPREAD", Box::new(instrument))
1531                        .await
1532                }
1533                InstrumentAny::OptionContract(instrument) => {
1534                    DatabaseQueries::add_instrument(pool, "OPTION_CONTRACT", Box::new(instrument))
1535                        .await
1536                }
1537                InstrumentAny::Commodity(instrument) => {
1538                    DatabaseQueries::add_instrument(pool, "COMMODITY", Box::new(instrument)).await
1539                }
1540                InstrumentAny::IndexInstrument(instrument) => {
1541                    DatabaseQueries::add_instrument(pool, "INDEX_INSTRUMENT", Box::new(instrument))
1542                        .await
1543                }
1544                InstrumentAny::Cfd(instrument) => {
1545                    DatabaseQueries::add_instrument(pool, "CFD", Box::new(instrument)).await
1546                }
1547                InstrumentAny::OptionSpread(instrument) => {
1548                    DatabaseQueries::add_instrument(pool, "OPTION_SPREAD", Box::new(instrument))
1549                        .await
1550                }
1551                InstrumentAny::PerpetualContract(instrument) => {
1552                    DatabaseQueries::add_instrument(
1553                        pool,
1554                        "PERPETUAL_CONTRACT",
1555                        Box::new(instrument),
1556                    )
1557                    .await
1558                }
1559                InstrumentAny::TokenizedAsset(instrument) => {
1560                    DatabaseQueries::add_instrument(pool, "TOKENIZED_ASSET", Box::new(instrument))
1561                        .await
1562                }
1563            },
1564            DatabaseQuery::AddInstrumentClose(close) => {
1565                DatabaseQueries::add_instrument_close(pool, &close).await
1566            }
1567            DatabaseQuery::AddOrder(event, client_id) => {
1568                DatabaseQueries::add_order(pool, event, client_id).await
1569            }
1570            DatabaseQuery::AddOrderSnapshot(snapshot) => {
1571                DatabaseQueries::add_order_snapshot(pool, snapshot).await
1572            }
1573            DatabaseQuery::AddPosition(position_id, event) => {
1574                DatabaseQueries::add_position(pool, position_id, &event).await
1575            }
1576            DatabaseQuery::AddPositionSnapshot(snapshot) => {
1577                DatabaseQueries::add_position_snapshot(pool, snapshot).await
1578            }
1579            DatabaseQuery::AddAccount(event, updated) => {
1580                DatabaseQueries::add_account(pool, updated, event, trader_id).await
1581            }
1582            DatabaseQuery::AddSignal(signal) => DatabaseQueries::add_signal(pool, &signal).await,
1583            DatabaseQuery::AddCustom(data) => DatabaseQueries::add_custom_data(pool, &data).await,
1584            DatabaseQuery::AddQuote(quote) => DatabaseQueries::add_quote(pool, &quote).await,
1585            DatabaseQuery::AddTrade(trade) => DatabaseQueries::add_trade(pool, &trade).await,
1586            DatabaseQuery::AddBar(bar) => DatabaseQueries::add_bar(pool, &bar).await,
1587            DatabaseQuery::UpdateOrder(event) => {
1588                DatabaseQueries::add_order_event(pool, event.into_boxed(), None).await
1589            }
1590            DatabaseQuery::UpdatePosition(event) => {
1591                DatabaseQueries::update_position(pool, &event).await
1592            }
1593            DatabaseQuery::IndexOrderPosition(client_order_id, position_id) => {
1594                DatabaseQueries::index_order_position(pool, trader_id, client_order_id, position_id)
1595                    .await
1596            }
1597            DatabaseQuery::IndexOrderClients(claims) => {
1598                DatabaseQueries::index_order_clients(pool, trader_id, &claims).await
1599            }
1600        };
1601
1602        if let Err(e) = result {
1603            log::error!("Error on query: {e:?}");
1604        }
1605    }
1606}