1use std::fmt::Display;
22
23use nautilus_model::defi::{
24 Block, Pool, PoolLiquidityUpdate, PoolSwap, Token, data::PoolFeeCollect,
25};
26use sqlx::{PgPool, postgres::PgPoolCopyExt};
27
28#[derive(Debug)]
30pub struct PostgresCopyHandler<'a> {
31 pool: &'a PgPool,
32}
33
34impl<'a> PostgresCopyHandler<'a> {
35 #[must_use]
37 pub const fn new(pool: &'a PgPool) -> Self {
38 Self { pool }
39 }
40
41 pub async fn copy_blocks(&self, chain_id: u32, blocks: &[Block]) -> anyhow::Result<()> {
50 if blocks.is_empty() {
51 return Ok(());
52 }
53
54 let copy_statement = "
55 COPY block (
56 chain_id, number, hash, parent_hash, miner, gas_limit, gas_used, timestamp,
57 base_fee_per_gas, blob_gas_used, excess_blob_gas,
58 l1_gas_price, l1_gas_used, l1_fee_scalar
59 ) FROM STDIN WITH (FORMAT BINARY)";
60
61 let mut copy_in = self
62 .pool
63 .copy_in_raw(copy_statement)
64 .await
65 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
66
67 self.write_copy_header(&mut copy_in).await?;
69
70 for block in blocks {
72 self.write_block_binary(&mut copy_in, chain_id, block)
73 .await?;
74 }
75
76 self.write_copy_trailer(&mut copy_in).await?;
78
79 copy_in
81 .finish()
82 .await
83 .map_err(|e| anyhow::anyhow!("Failed to finish COPY operation: {e}"))?;
84
85 Ok(())
86 }
87
88 pub async fn copy_tokens(&self, chain_id: u32, tokens: &[Token]) -> anyhow::Result<()> {
94 if tokens.is_empty() {
95 return Ok(());
96 }
97
98 let copy_statement = "
99 COPY token (
100 chain_id, address, name, symbol, decimals
101 ) FROM STDIN WITH (FORMAT BINARY)";
102
103 let mut copy_in = self
104 .pool
105 .copy_in_raw(copy_statement)
106 .await
107 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
108
109 self.write_copy_header(&mut copy_in).await?;
110 for token in tokens {
111 self.write_token_binary(&mut copy_in, chain_id, token)
112 .await?;
113 }
114 self.write_copy_trailer(&mut copy_in).await?;
115 copy_in
116 .finish()
117 .await
118 .map_err(|e| anyhow::anyhow!("Failed to finish COPY operation: {e}"))?;
119
120 Ok(())
121 }
122
123 pub async fn copy_pools(&self, chain_id: u32, pools: &[Pool]) -> anyhow::Result<()> {
129 if pools.is_empty() {
130 return Ok(());
131 }
132
133 let copy_statement = "
134 COPY pool (
135 chain_id, dex_name, address, pool_identifier, creation_block,
136 token0_chain, token0_address, token1_chain, token1_address,
137 fee, tick_spacing, initial_tick, initial_sqrt_price_x96, hook_address
138 ) FROM STDIN WITH (FORMAT BINARY)";
139
140 let mut copy_in = self
141 .pool
142 .copy_in_raw(copy_statement)
143 .await
144 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
145
146 self.write_copy_header(&mut copy_in).await?;
147 for pool in pools {
148 self.write_pool_binary(&mut copy_in, chain_id, pool).await?;
149 }
150 self.write_copy_trailer(&mut copy_in).await?;
151 copy_in
152 .finish()
153 .await
154 .map_err(|e| anyhow::anyhow!("Failed to finish COPY operation: {e}"))?;
155
156 Ok(())
157 }
158
159 pub async fn copy_pool_swaps(&self, chain_id: u32, swaps: &[PoolSwap]) -> anyhow::Result<()> {
165 if swaps.is_empty() {
166 return Ok(());
167 }
168
169 let copy_statement = "
170 COPY pool_swap_event (
171 chain_id, dex_name, pool_identifier, block, transaction_hash, transaction_index,
172 log_index, sender, recipient, sqrt_price_x96, liquidity, tick, amount0, amount1,
173 order_side, base_quantity, quote_quantity, spot_price, execution_price
174 ) FROM STDIN WITH (FORMAT BINARY)";
175
176 let mut copy_in = self
177 .pool
178 .copy_in_raw(copy_statement)
179 .await
180 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
181
182 self.write_copy_header(&mut copy_in).await?;
184
185 for swap in swaps {
187 self.write_pool_swap_binary(&mut copy_in, chain_id, swap)
188 .await?;
189 }
190
191 self.write_copy_trailer(&mut copy_in).await?;
193
194 copy_in.finish().await.map_err(|e| {
196 let mut detail = format!(
198 "COPY operation failed for pool_swap batch: chain_id={chain_id}, batch_size={}",
199 swaps.len()
200 );
201
202 if !swaps.is_empty() {
203 detail.push_str(&format!(
204 ", block_range={} to {}",
205 swaps.iter().map(|s| s.block).min().unwrap_or(0),
206 swaps.iter().map(|s| s.block).max().unwrap_or(0)
207 ));
208 }
209
210 for (i, swap) in swaps.iter().take(5).enumerate() {
211 detail.push_str(&format!(
212 "\n Swap[{i}]: tx={} log_idx={} block={} pool={}",
213 swap.transaction_hash, swap.log_index, swap.block, swap.instrument_id
214 ));
215 }
216
217 if swaps.len() > 5 {
218 detail.push_str(&format!("\n ... and {} more swaps", swaps.len() - 5));
219 }
220
221 log::error!("{detail}");
222
223 anyhow::anyhow!("Failed to finish COPY operation: {e}")
224 })?;
225
226 Ok(())
227 }
228
229 pub async fn copy_pool_liquidity_updates(
235 &self,
236 chain_id: u32,
237 updates: &[PoolLiquidityUpdate],
238 ) -> anyhow::Result<()> {
239 if updates.is_empty() {
240 return Ok(());
241 }
242
243 let copy_statement = "
244 COPY pool_liquidity_event (
245 chain_id, dex_name, pool_identifier, block, transaction_hash, transaction_index,
246 log_index, event_type, sender, owner, position_liquidity,
247 amount0, amount1, tick_lower, tick_upper
248 ) FROM STDIN WITH (FORMAT BINARY)";
249
250 let mut copy_in = self
251 .pool
252 .copy_in_raw(copy_statement)
253 .await
254 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
255
256 self.write_copy_header(&mut copy_in).await?;
258
259 for update in updates {
261 self.write_pool_liquidity_update_binary(&mut copy_in, chain_id, update)
262 .await?;
263 }
264
265 self.write_copy_trailer(&mut copy_in).await?;
267
268 copy_in.finish().await.map_err(|e| {
270 let mut detail = format!(
272 "COPY operation failed for pool_liquidity batch: chain_id={chain_id}, batch_size={}",
273 updates.len()
274 );
275
276 if !updates.is_empty() {
277 detail.push_str(&format!(
278 ", block_range={} to {}",
279 updates.iter().map(|u| u.block).min().unwrap_or(0),
280 updates.iter().map(|u| u.block).max().unwrap_or(0)
281 ));
282 }
283
284 for (i, update) in updates.iter().take(5).enumerate() {
285 detail.push_str(&format!(
286 "\n Update[{i}]: tx={} log_idx={} block={} pool={} type={}",
287 update.transaction_hash,
288 update.log_index,
289 update.block,
290 update.pool_identifier,
291 update.kind
292 ));
293 }
294
295 if updates.len() > 5 {
296 detail.push_str(&format!("\n ... and {} more updates", updates.len() - 5));
297 }
298
299 log::error!("{detail}");
300
301 anyhow::anyhow!("Failed to finish COPY operation: {e}")
302 })?;
303
304 Ok(())
305 }
306
307 async fn write_copy_header(
314 &self,
315 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
316 ) -> anyhow::Result<()> {
317 use std::io::Write;
318 let mut header = Vec::new();
319
320 header.write_all(b"PGCOPY\n\xff\r\n\0")?; header.write_all(&[0, 0, 0, 0])?; header.write_all(&[0, 0, 0, 0])?; copy_in
326 .send(header)
327 .await
328 .map_err(|e| anyhow::anyhow!("Failed to write COPY header: {e}"))?;
329 Ok(())
330 }
331
332 async fn write_block_binary(
338 &self,
339 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
340 chain_id: u32,
341 block: &Block,
342 ) -> anyhow::Result<()> {
343 use std::io::Write;
344 let mut row_data = Vec::new();
345
346 row_data.write_all(&14u16.to_be_bytes())?;
348
349 let chain_id_bytes = (chain_id as i32).to_be_bytes();
350 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
351 row_data.write_all(&chain_id_bytes)?;
352
353 let number_bytes = (block.number as i64).to_be_bytes();
354 row_data.write_all(&(number_bytes.len() as i32).to_be_bytes())?;
355 row_data.write_all(&number_bytes)?;
356
357 let hash_bytes = block.hash.as_bytes();
358 row_data.write_all(&(hash_bytes.len() as i32).to_be_bytes())?;
359 row_data.write_all(hash_bytes)?;
360
361 let parent_hash_bytes = block.parent_hash.as_bytes();
362 row_data.write_all(&(parent_hash_bytes.len() as i32).to_be_bytes())?;
363 row_data.write_all(parent_hash_bytes)?;
364
365 let miner_bytes = block.miner.to_string().as_bytes().to_vec();
366 row_data.write_all(&(miner_bytes.len() as i32).to_be_bytes())?;
367 row_data.write_all(&miner_bytes)?;
368
369 let gas_limit_bytes = (block.gas_limit as i64).to_be_bytes();
370 row_data.write_all(&(gas_limit_bytes.len() as i32).to_be_bytes())?;
371 row_data.write_all(&gas_limit_bytes)?;
372
373 let gas_used_bytes = (block.gas_used as i64).to_be_bytes();
374 row_data.write_all(&(gas_used_bytes.len() as i32).to_be_bytes())?;
375 row_data.write_all(&gas_used_bytes)?;
376
377 let timestamp_bytes = block.timestamp.to_string().as_bytes().to_vec();
378 row_data.write_all(&(timestamp_bytes.len() as i32).to_be_bytes())?;
379 row_data.write_all(×tamp_bytes)?;
380
381 if let Some(ref base_fee) = block.base_fee_per_gas {
382 let base_fee_bytes = base_fee.to_string().as_bytes().to_vec();
383 row_data.write_all(&(base_fee_bytes.len() as i32).to_be_bytes())?;
384 row_data.write_all(&base_fee_bytes)?;
385 } else {
386 row_data.write_all(&(-1i32).to_be_bytes())?; }
388
389 if let Some(ref blob_gas) = block.blob_gas_used {
390 let blob_gas_bytes = blob_gas.to_string().as_bytes().to_vec();
391 row_data.write_all(&(blob_gas_bytes.len() as i32).to_be_bytes())?;
392 row_data.write_all(&blob_gas_bytes)?;
393 } else {
394 row_data.write_all(&(-1i32).to_be_bytes())?; }
396
397 if let Some(ref excess_blob) = block.excess_blob_gas {
398 let excess_blob_bytes = excess_blob.to_string().as_bytes().to_vec();
399 row_data.write_all(&(excess_blob_bytes.len() as i32).to_be_bytes())?;
400 row_data.write_all(&excess_blob_bytes)?;
401 } else {
402 row_data.write_all(&(-1i32).to_be_bytes())?; }
404
405 if let Some(ref l1_gas_price) = block.l1_gas_price {
406 let l1_gas_price_bytes = l1_gas_price.to_string().as_bytes().to_vec();
407 row_data.write_all(&(l1_gas_price_bytes.len() as i32).to_be_bytes())?;
408 row_data.write_all(&l1_gas_price_bytes)?;
409 } else {
410 row_data.write_all(&(-1i32).to_be_bytes())?; }
412
413 if let Some(l1_gas_used) = block.l1_gas_used {
414 let l1_gas_used_bytes = (l1_gas_used as i64).to_be_bytes();
415 row_data.write_all(&(l1_gas_used_bytes.len() as i32).to_be_bytes())?;
416 row_data.write_all(&l1_gas_used_bytes)?;
417 } else {
418 row_data.write_all(&(-1i32).to_be_bytes())?; }
420
421 if let Some(l1_fee_scalar) = block.l1_fee_scalar {
422 let l1_fee_scalar_bytes = (l1_fee_scalar as i64).to_be_bytes();
423 row_data.write_all(&(l1_fee_scalar_bytes.len() as i32).to_be_bytes())?;
424 row_data.write_all(&l1_fee_scalar_bytes)?;
425 } else {
426 row_data.write_all(&(-1i32).to_be_bytes())?; }
428
429 copy_in
430 .send(row_data)
431 .await
432 .map_err(|e| anyhow::anyhow!("Failed to write block data: {e}"))?;
433 Ok(())
434 }
435
436 async fn write_pool_swap_binary(
442 &self,
443 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
444 chain_id: u32,
445 swap: &PoolSwap,
446 ) -> anyhow::Result<()> {
447 use std::io::Write;
448 let mut row_data = Vec::new();
449
450 row_data.write_all(&19u16.to_be_bytes())?;
451
452 let chain_id_bytes = (chain_id as i32).to_be_bytes();
453 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
454 row_data.write_all(&chain_id_bytes)?;
455
456 let dex_name_bytes = swap.dex.name.to_string().as_bytes().to_vec();
457 row_data.write_all(&(dex_name_bytes.len() as i32).to_be_bytes())?;
458 row_data.write_all(&dex_name_bytes)?;
459
460 let pool_identifier = swap.instrument_id.to_string();
461 let pool_identifier_bytes = pool_identifier.as_bytes();
462 row_data.write_all(&(pool_identifier_bytes.len() as i32).to_be_bytes())?;
463 row_data.write_all(pool_identifier_bytes)?;
464
465 let block_bytes = (swap.block as i64).to_be_bytes();
466 row_data.write_all(&(block_bytes.len() as i32).to_be_bytes())?;
467 row_data.write_all(&block_bytes)?;
468
469 let tx_hash_bytes = swap.transaction_hash.as_bytes();
470 row_data.write_all(&(tx_hash_bytes.len() as i32).to_be_bytes())?;
471 row_data.write_all(tx_hash_bytes)?;
472
473 let tx_index_bytes = (swap.transaction_index as i32).to_be_bytes();
474 row_data.write_all(&(tx_index_bytes.len() as i32).to_be_bytes())?;
475 row_data.write_all(&tx_index_bytes)?;
476
477 let log_index_bytes = (swap.log_index as i32).to_be_bytes();
478 row_data.write_all(&(log_index_bytes.len() as i32).to_be_bytes())?;
479 row_data.write_all(&log_index_bytes)?;
480
481 let sender_bytes = swap.sender.to_string().as_bytes().to_vec();
482 row_data.write_all(&(sender_bytes.len() as i32).to_be_bytes())?;
483 row_data.write_all(&sender_bytes)?;
484
485 let recipient_bytes = swap.recipient.to_string().as_bytes().to_vec();
486 row_data.write_all(&(recipient_bytes.len() as i32).to_be_bytes())?;
487 row_data.write_all(&recipient_bytes)?;
488
489 write_copy_numeric(&mut row_data, swap.sqrt_price_x96);
490 write_copy_numeric(&mut row_data, swap.liquidity);
491
492 let tick_bytes = swap.tick.to_be_bytes();
493 row_data.write_all(&(tick_bytes.len() as i32).to_be_bytes())?;
494 row_data.write_all(&tick_bytes)?;
495
496 write_copy_numeric(&mut row_data, swap.amount0);
497 write_copy_numeric(&mut row_data, swap.amount1);
498
499 if let Some(trade_info) = &swap.trade_info {
500 let side_bytes = trade_info.order_side.to_string().as_bytes().to_vec();
501 row_data.write_all(&(side_bytes.len() as i32).to_be_bytes())?;
502 row_data.write_all(&side_bytes)?;
503
504 let base_qty_decimal = trade_info.quantity_base.as_decimal();
505 let base_qty_str = base_qty_decimal.to_string();
506 let base_qty_bytes = base_qty_str.as_bytes();
507 row_data.write_all(&(base_qty_bytes.len() as i32).to_be_bytes())?;
508 row_data.write_all(base_qty_bytes)?;
509
510 let quote_qty_decimal = trade_info.quantity_quote.as_decimal();
511 let quote_qty_str = quote_qty_decimal.to_string();
512 let quote_qty_bytes = quote_qty_str.as_bytes();
513 row_data.write_all(&(quote_qty_bytes.len() as i32).to_be_bytes())?;
514 row_data.write_all(quote_qty_bytes)?;
515
516 let spot_price_decimal = trade_info.spot_price.as_decimal();
517 let spot_price_str = spot_price_decimal.to_string();
518 let spot_price_bytes = spot_price_str.as_bytes();
519 row_data.write_all(&(spot_price_bytes.len() as i32).to_be_bytes())?;
520 row_data.write_all(spot_price_bytes)?;
521
522 let exec_price_decimal = trade_info.execution_price.as_decimal();
523 let exec_price_str = exec_price_decimal.to_string();
524 let exec_price_bytes = exec_price_str.as_bytes();
525 row_data.write_all(&(exec_price_bytes.len() as i32).to_be_bytes())?;
526 row_data.write_all(exec_price_bytes)?;
527 } else {
528 row_data.write_all(&(-1i32).to_be_bytes())?;
529 row_data.write_all(&(-1i32).to_be_bytes())?;
530 row_data.write_all(&(-1i32).to_be_bytes())?;
531 row_data.write_all(&(-1i32).to_be_bytes())?;
532 row_data.write_all(&(-1i32).to_be_bytes())?;
533 }
534
535 copy_in
536 .send(row_data)
537 .await
538 .map_err(|e| anyhow::anyhow!("Failed to write pool swap data: {e}"))?;
539 Ok(())
540 }
541
542 async fn write_pool_liquidity_update_binary(
548 &self,
549 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
550 chain_id: u32,
551 update: &PoolLiquidityUpdate,
552 ) -> anyhow::Result<()> {
553 use std::io::Write;
554 let mut row_data = Vec::new();
555
556 row_data.write_all(&15u16.to_be_bytes())?;
557
558 let chain_id_bytes = (chain_id as i32).to_be_bytes();
559 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
560 row_data.write_all(&chain_id_bytes)?;
561
562 let dex_name_bytes = update.dex.name.to_string().as_bytes().to_vec();
563 row_data.write_all(&(dex_name_bytes.len() as i32).to_be_bytes())?;
564 row_data.write_all(&dex_name_bytes)?;
565
566 let pool_identifier = update.instrument_id.to_string();
567 let pool_identifier_bytes = pool_identifier.as_bytes();
568 row_data.write_all(&(pool_identifier_bytes.len() as i32).to_be_bytes())?;
569 row_data.write_all(pool_identifier_bytes)?;
570
571 let block_bytes = (update.block as i64).to_be_bytes();
572 row_data.write_all(&(block_bytes.len() as i32).to_be_bytes())?;
573 row_data.write_all(&block_bytes)?;
574
575 let tx_hash_bytes = update.transaction_hash.as_bytes();
576 row_data.write_all(&(tx_hash_bytes.len() as i32).to_be_bytes())?;
577 row_data.write_all(tx_hash_bytes)?;
578
579 let tx_index_bytes = (update.transaction_index as i32).to_be_bytes();
580 row_data.write_all(&(tx_index_bytes.len() as i32).to_be_bytes())?;
581 row_data.write_all(&tx_index_bytes)?;
582
583 let log_index_bytes = (update.log_index as i32).to_be_bytes();
584 row_data.write_all(&(log_index_bytes.len() as i32).to_be_bytes())?;
585 row_data.write_all(&log_index_bytes)?;
586
587 let event_type_bytes = update.kind.to_string().as_bytes().to_vec();
588 row_data.write_all(&(event_type_bytes.len() as i32).to_be_bytes())?;
589 row_data.write_all(&event_type_bytes)?;
590
591 if let Some(sender) = update.sender {
592 let sender_bytes = sender.to_string().as_bytes().to_vec();
593 row_data.write_all(&(sender_bytes.len() as i32).to_be_bytes())?;
594 row_data.write_all(&sender_bytes)?;
595 } else {
596 row_data.write_all(&(-1i32).to_be_bytes())?; }
598
599 let owner_bytes = update.owner.to_string().as_bytes().to_vec();
600 row_data.write_all(&(owner_bytes.len() as i32).to_be_bytes())?;
601 row_data.write_all(&owner_bytes)?;
602
603 write_copy_numeric(&mut row_data, update.position_liquidity);
604 write_copy_numeric(&mut row_data, update.amount0);
605 write_copy_numeric(&mut row_data, update.amount1);
606
607 let tick_lower_bytes = update.tick_lower.to_be_bytes();
608 row_data.write_all(&(tick_lower_bytes.len() as i32).to_be_bytes())?;
609 row_data.write_all(&tick_lower_bytes)?;
610
611 let tick_upper_bytes = update.tick_upper.to_be_bytes();
612 row_data.write_all(&(tick_upper_bytes.len() as i32).to_be_bytes())?;
613 row_data.write_all(&tick_upper_bytes)?;
614
615 copy_in
616 .send(row_data)
617 .await
618 .map_err(|e| anyhow::anyhow!("Failed to write pool liquidity update data: {e}"))?;
619 Ok(())
620 }
621
622 pub async fn copy_pool_collects(
628 &self,
629 chain_id: u32,
630 collects: &[PoolFeeCollect],
631 ) -> anyhow::Result<()> {
632 if collects.is_empty() {
633 return Ok(());
634 }
635
636 let copy_statement = "
637 COPY pool_collect_event (
638 chain_id, dex_name, pool_identifier, block, transaction_hash, transaction_index,
639 log_index, owner, amount0, amount1, tick_lower, tick_upper
640 ) FROM STDIN WITH (FORMAT BINARY)";
641
642 let mut copy_in = self
643 .pool
644 .copy_in_raw(copy_statement)
645 .await
646 .map_err(|e| anyhow::anyhow!("Failed to start COPY operation: {e}"))?;
647
648 self.write_copy_header(&mut copy_in).await?;
650
651 for collect in collects {
653 self.write_pool_fee_collect_binary(&mut copy_in, chain_id, collect)
654 .await?;
655 }
656
657 self.write_copy_trailer(&mut copy_in).await?;
659
660 copy_in.finish().await.map_err(|e| {
662 let mut detail = format!(
664 "COPY operation failed for temp_pool_collect batch: chain_id={chain_id}, batch_size={}",
665 collects.len()
666 );
667
668 if !collects.is_empty() {
669 detail.push_str(&format!(
670 ", block_range={} to {}",
671 collects.iter().map(|c| c.block).min().unwrap_or(0),
672 collects.iter().map(|c| c.block).max().unwrap_or(0)
673 ));
674 }
675
676 for (i, collect) in collects.iter().take(5).enumerate() {
677 detail.push_str(&format!(
678 "\n Collect[{i}]: tx={} log_idx={} block={} pool={} owner={}",
679 collect.transaction_hash,
680 collect.log_index,
681 collect.block,
682 collect.pool_identifier,
683 collect.owner
684 ));
685 }
686
687 if collects.len() > 5 {
688 detail.push_str(&format!("\n ... and {} more collects", collects.len() - 5));
689 }
690
691 log::error!("{detail}");
692
693 anyhow::anyhow!("Failed to finish COPY operation: {e}")
694 })?;
695
696 Ok(())
697 }
698
699 async fn write_pool_fee_collect_binary(
705 &self,
706 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
707 chain_id: u32,
708 collect: &PoolFeeCollect,
709 ) -> anyhow::Result<()> {
710 use std::io::Write;
711 let mut row_data = Vec::new();
712
713 row_data.write_all(&12u16.to_be_bytes())?;
714
715 let chain_id_bytes = (chain_id as i32).to_be_bytes();
716 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
717 row_data.write_all(&chain_id_bytes)?;
718
719 let dex_name_bytes = collect.dex.name.to_string().as_bytes().to_vec();
720 row_data.write_all(&(dex_name_bytes.len() as i32).to_be_bytes())?;
721 row_data.write_all(&dex_name_bytes)?;
722
723 let pool_identifier = collect.instrument_id.to_string();
724 let pool_identifier_bytes = pool_identifier.as_bytes();
725 row_data.write_all(&(pool_identifier_bytes.len() as i32).to_be_bytes())?;
726 row_data.write_all(pool_identifier_bytes)?;
727
728 let block_bytes = (collect.block as i64).to_be_bytes();
729 row_data.write_all(&(block_bytes.len() as i32).to_be_bytes())?;
730 row_data.write_all(&block_bytes)?;
731
732 let tx_hash_bytes = collect.transaction_hash.as_bytes();
733 row_data.write_all(&(tx_hash_bytes.len() as i32).to_be_bytes())?;
734 row_data.write_all(tx_hash_bytes)?;
735
736 let tx_index_bytes = (collect.transaction_index as i32).to_be_bytes();
737 row_data.write_all(&(tx_index_bytes.len() as i32).to_be_bytes())?;
738 row_data.write_all(&tx_index_bytes)?;
739
740 let log_index_bytes = (collect.log_index as i32).to_be_bytes();
741 row_data.write_all(&(log_index_bytes.len() as i32).to_be_bytes())?;
742 row_data.write_all(&log_index_bytes)?;
743
744 let owner_bytes = collect.owner.to_string().as_bytes().to_vec();
745 row_data.write_all(&(owner_bytes.len() as i32).to_be_bytes())?;
746 row_data.write_all(&owner_bytes)?;
747
748 write_copy_numeric(&mut row_data, collect.amount0);
749 write_copy_numeric(&mut row_data, collect.amount1);
750
751 let tick_lower_bytes = collect.tick_lower.to_be_bytes();
752 row_data.write_all(&(tick_lower_bytes.len() as i32).to_be_bytes())?;
753 row_data.write_all(&tick_lower_bytes)?;
754
755 let tick_upper_bytes = collect.tick_upper.to_be_bytes();
756 row_data.write_all(&(tick_upper_bytes.len() as i32).to_be_bytes())?;
757 row_data.write_all(&tick_upper_bytes)?;
758
759 copy_in
760 .send(row_data)
761 .await
762 .map_err(|e| anyhow::anyhow!("Failed to write pool fee collect data: {e}"))?;
763 Ok(())
764 }
765
766 async fn write_token_binary(
772 &self,
773 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
774 chain_id: u32,
775 token: &Token,
776 ) -> anyhow::Result<()> {
777 use std::io::Write;
778 let mut row_data = Vec::new();
779
780 row_data.write_all(&5u16.to_be_bytes())?;
781
782 let chain_id_bytes = (chain_id as i32).to_be_bytes();
783 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
784 row_data.write_all(&chain_id_bytes)?;
785
786 let address_bytes = token.address.to_string().as_bytes().to_vec();
787 row_data.write_all(&(address_bytes.len() as i32).to_be_bytes())?;
788 row_data.write_all(&address_bytes)?;
789
790 let name_bytes = token.name.as_bytes();
791 row_data.write_all(&(name_bytes.len() as i32).to_be_bytes())?;
792 row_data.write_all(name_bytes)?;
793
794 let symbol_bytes = token.symbol.as_bytes();
795 row_data.write_all(&(symbol_bytes.len() as i32).to_be_bytes())?;
796 row_data.write_all(symbol_bytes)?;
797
798 let decimals_bytes = (i32::from(token.decimals)).to_be_bytes();
799 row_data.write_all(&(decimals_bytes.len() as i32).to_be_bytes())?;
800 row_data.write_all(&decimals_bytes)?;
801
802 copy_in
803 .send(row_data)
804 .await
805 .map_err(|e| anyhow::anyhow!("Failed to write token data: {e}"))?;
806 Ok(())
807 }
808
809 async fn write_pool_binary(
815 &self,
816 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
817 chain_id: u32,
818 pool: &Pool,
819 ) -> anyhow::Result<()> {
820 use std::io::Write;
821 let mut row_data = Vec::new();
822
823 row_data.write_all(&14u16.to_be_bytes())?;
824
825 let chain_id_bytes = (chain_id as i32).to_be_bytes();
826 row_data.write_all(&(chain_id_bytes.len() as i32).to_be_bytes())?;
827 row_data.write_all(&chain_id_bytes)?;
828
829 let dex_name_bytes = pool.dex.name.to_string().as_bytes().to_vec();
830 row_data.write_all(&(dex_name_bytes.len() as i32).to_be_bytes())?;
831 row_data.write_all(&dex_name_bytes)?;
832
833 let address_bytes = pool.address.to_string().as_bytes().to_vec();
834 row_data.write_all(&(address_bytes.len() as i32).to_be_bytes())?;
835 row_data.write_all(&address_bytes)?;
836
837 let pool_identifier_bytes = pool.pool_identifier.as_str().as_bytes();
838 row_data.write_all(&(pool_identifier_bytes.len() as i32).to_be_bytes())?;
839 row_data.write_all(pool_identifier_bytes)?;
840
841 let creation_block_bytes = (pool.creation_block as i64).to_be_bytes();
842 row_data.write_all(&(creation_block_bytes.len() as i32).to_be_bytes())?;
843 row_data.write_all(&creation_block_bytes)?;
844
845 let token0_chain_bytes = (pool.token0.chain.chain_id as i32).to_be_bytes();
846 row_data.write_all(&(token0_chain_bytes.len() as i32).to_be_bytes())?;
847 row_data.write_all(&token0_chain_bytes)?;
848
849 let token0_address_bytes = pool.token0.address.to_string().as_bytes().to_vec();
850 row_data.write_all(&(token0_address_bytes.len() as i32).to_be_bytes())?;
851 row_data.write_all(&token0_address_bytes)?;
852
853 let token1_chain_bytes = (pool.token1.chain.chain_id as i32).to_be_bytes();
854 row_data.write_all(&(token1_chain_bytes.len() as i32).to_be_bytes())?;
855 row_data.write_all(&token1_chain_bytes)?;
856
857 let token1_address_bytes = pool.token1.address.to_string().as_bytes().to_vec();
858 row_data.write_all(&(token1_address_bytes.len() as i32).to_be_bytes())?;
859 row_data.write_all(&token1_address_bytes)?;
860
861 if let Some(fee) = pool.fee {
862 let fee_bytes = (fee as i32).to_be_bytes();
863 row_data.write_all(&(fee_bytes.len() as i32).to_be_bytes())?;
864 row_data.write_all(&fee_bytes)?;
865 } else {
866 row_data.write_all(&(-1i32).to_be_bytes())?; }
868
869 if let Some(tick_spacing) = pool.tick_spacing {
870 let tick_spacing_bytes = (tick_spacing as i32).to_be_bytes();
871 row_data.write_all(&(tick_spacing_bytes.len() as i32).to_be_bytes())?;
872 row_data.write_all(&tick_spacing_bytes)?;
873 } else {
874 row_data.write_all(&(-1i32).to_be_bytes())?; }
876
877 if let Some(initial_tick) = pool.initial_tick {
878 let initial_tick_bytes = initial_tick.to_be_bytes();
879 row_data.write_all(&(initial_tick_bytes.len() as i32).to_be_bytes())?;
880 row_data.write_all(&initial_tick_bytes)?;
881 } else {
882 row_data.write_all(&(-1i32).to_be_bytes())?; }
884
885 if let Some(ref initial_sqrt_price) = pool.initial_sqrt_price_x96 {
886 write_copy_numeric(&mut row_data, initial_sqrt_price);
887 } else {
888 row_data.write_all(&(-1i32).to_be_bytes())?; }
890
891 if let Some(ref hooks) = pool.hooks {
892 let hooks_bytes = hooks.to_string().as_bytes().to_vec();
893 row_data.write_all(&(hooks_bytes.len() as i32).to_be_bytes())?;
894 row_data.write_all(&hooks_bytes)?;
895 } else {
896 row_data.write_all(&(-1i32).to_be_bytes())?; }
898
899 copy_in
900 .send(row_data)
901 .await
902 .map_err(|e| anyhow::anyhow!("Failed to write pool data: {e}"))?;
903 Ok(())
904 }
905
906 async fn write_copy_trailer(
910 &self,
911 copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
912 ) -> anyhow::Result<()> {
913 let trailer = (-1i16).to_be_bytes();
915 copy_in
916 .send(trailer.to_vec())
917 .await
918 .map_err(|e| anyhow::anyhow!("Failed to write COPY trailer: {e}"))?;
919 Ok(())
920 }
921}
922
923fn write_copy_numeric(row: &mut Vec<u8>, value: impl Display) {
924 let value = value.to_string();
925 row.extend_from_slice(&(value.len() as i32).to_be_bytes());
926 row.extend_from_slice(value.as_bytes());
927}