1use 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
59const CACHE_PROCESS: &str = "cache-process";
61const SCHEMA_MIGRATION_COMMAND: &str = "run `nautilus database init` to migrate";
62
63#[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 pub host: Option<String>,
86 pub port: Option<u16>,
88 pub username: Option<String>,
90 pub password: Option<String>,
92 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 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 #[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 let mut buffer: VecDeque<DatabaseQuery> = VecDeque::new();
297
298 let buffer_interval = Duration::from_millis(0);
300
301 let flush_timer = tokio::time::sleep(buffer_interval);
305 tokio::pin!(flush_timer);
306
307 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
345async 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
446async 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
481async 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
551pub 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 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 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(); 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(); 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, "e).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}