{"record":{"id":"ceb4446b1f51e1e4","repo":"nautechsystems/nautilus_trader","slug":"failed-to-batch-insert-into-pool-fee-protocol-upda","errorCode":null,"errorMessage":"Failed to batch insert into pool_fee_protocol_update_event table: {e}","messagePattern":"Failed to batch insert into pool_fee_protocol_update_event table: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":1944,"sourceCode":"            ) AS t(chain_id, dex_name, pool_identifier, block, transaction_hash, transaction_index,\n                   log_index, fee_protocol0_new, fee_protocol1_new)\n            ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING\n           \",\n        )\n        .bind(&chain_ids[..])\n        .bind(&dex_names[..])\n        .bind(&pool_identifiers[..])\n        .bind(&blocks[..])\n        .bind(&transaction_hashes[..])\n        .bind(&transaction_indices[..])\n        .bind(&log_indices[..])\n        .bind(&fee_protocol0s[..])\n        .bind(&fee_protocol1s[..])\n        .execute(&self.pool)\n        .await\n        .map(|_| ())\n        .map_err(|e| {\n            anyhow::anyhow!(\"Failed to batch insert into pool_fee_protocol_update_event table: {e}\")\n        })\n    }\n\n    /// Inserts multiple pool protocol-fee withdrawal events in a single database operation using UNNEST.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the database operation fails.\n    pub async fn add_pool_fee_protocol_collect_batch(\n        &self,\n        chain_id: u32,\n        collects: &[PoolFeeProtocolCollect],\n    ) -> anyhow::Result<()> {\n        if collects.is_empty() {\n            return Ok(());\n        }\n\n        // Prepare vectors for each column","sourceCodeStart":1926,"sourceCodeEnd":1962,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L1926-L1962","documentation":"Raised when the batched UNNEST INSERT of pool fee-protocol update events into the `pool_fee_protocol_update_event` table fails at the sqlx `.execute` step. The driver error is wrapped with this message. The fee-protocol-update batch is not persisted.","triggerScenarios":"Executing the batch insert after successfully converting fee_protocol0/1 values, when Postgres rejects the statement: constraint violation (PK/unique on block/tx/log index), array-length mismatch, column type mismatch, or lost connection.","commonSituations":"Replaying the same logs twice causing unique conflicts; schema drift after migrations; pool connections killed by idle timeouts or failover during indexing runs.","solutions":["Read the `{e}` suffix for the exact sqlx/PostgreSQL error and fix that cause","Ensure arrays bound via UNNEST (identifiers, blocks, hashes, indices, fee protocols) have equal lengths","Apply pending migrations so the table schema matches the bound columns","Use ON CONFLICT DO NOTHING/UPDATE for idempotent re-insertion of replayed logs","Verify database connectivity and pool health"],"exampleFix":"// before\n.map_err(|e| {\n    anyhow::anyhow!(\"Failed to batch insert into pool_fee_protocol_update_event table: {e}\")\n})\n// after: make the write idempotent and add context\n// INSERT ... ON CONFLICT (block, transaction_hash, log_index) DO NOTHING\n.map_err(|e| anyhow::anyhow!(\"Failed to batch insert into pool_fee_protocol_update_event table (rows={}): {e}\", fee_protocol0s.len()))","handlingStrategy":"try-catch","validationCode":"fn validate_fee_protocol_update_batch(updates: &[FeeProtocolUpdate]) -> anyhow::Result<()> {\n    for u in updates {\n        anyhow::ensure!(u.fee_protocol0_new <= 255 && u.fee_protocol1_new <= 255, \"fee protocol out of range\");\n        anyhow::ensure!(!u.transaction_hash.is_empty(), \"missing tx hash\");\n    }\n    Ok(())\n}","typeGuard":"fn is_valid_update(u: &FeeProtocolUpdate) -> bool {\n    u.fee_protocol0_new <= 255 && u.fee_protocol1_new <= 255\n}","tryCatchPattern":"match insert_pool_fee_protocol_updates(&updates).await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"duplicate key\") => tracing::debug!(\"already persisted\"),\n    Err(e) => return Err(e),\n}","preventionTips":["Validate both fee-protocol values before batching","Make the insert idempotent with ON CONFLICT on (block, tx_hash, log_index)","Ensure equal-length arrays for UNNEST","Apply migrations before running the indexer"],"tags":["database","postgres","sqlx","batch-insert"],"backgroundTag":"database-write-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}