nautechsystems/nautilus_trader · error · anyhow::Error

Failed to batch insert into pool_liquidity_event table: {e}

Error message

Failed to batch insert into pool_liquidity_event table: {e}

What it means

UNNEST-based batch insert of liquidity (mint/burn) events into `pool_liquidity_event` failed; wrapped with this message. Thrown when the bulk liquidity-event statement fails to execute against Postgres.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:1133

        .bind(&chain_ids[..])
        .bind(&dex_names[..])
        .bind(&pool_identifiers[..])
        .bind(&blocks[..])
        .bind(&transaction_hashes[..])
        .bind(&transaction_indices[..])
        .bind(&log_indices[..])
        .bind(&event_types[..])
        .bind(&senders[..])
        .bind(&owners[..])
        .bind(&position_liquidities[..])
        .bind(&amount0s[..])
        .bind(&amount1s[..])
        .bind(&tick_lowers[..])
        .bind(&tick_uppers[..])
        .execute(&self.pool)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to batch insert into pool_liquidity_event table: {e}"))
    }

    /// Adds or updates a token record in the database.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn add_token(&self, token: &Token) -> anyhow::Result<()> {
        sqlx::query(
            "
            INSERT INTO token (
                chain_id, address, name, symbol, decimals
            ) VALUES ($1, $2, $3, $4, $5)
            ON CONFLICT (chain_id, address)
            DO UPDATE
            SET
                name = $3,
                symbol = $4,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the wrapped `{e}` for the specific constraint, cast, or FK failure
  2. Insert dependent block/pool rows before the liquidity events
  3. Deduplicate or use ON CONFLICT to tolerate replays
  4. Verify equal slice lengths for all bound arrays and that ticks fit the column's integer range

Example fix

// before
assert!(amount0s.len() == amount1s.len());
// after
assert!(amount0s.len() == amount1s.len() && amount0s.len() == tick_lowers.len() && tick_lowers.len() == tick_uppers.len());
Defensive patterns

Strategy: validation

Validate before calling

let n = events.len();
anyhow::ensure!(amount0s.len() == n && amount1s.len() == n && tick_lowers.len() == n && tick_uppers.len() == n, "liquidity batch arrays misaligned");
anyhow::ensure!(tick_lowers.iter().zip(tick_uppers).all(|(l, u)| l <= &i32::MAX as _ && u <= &i32::MAX as _), "tick out of i32 range");

Type guard

fn liquidity_batch_valid(events: &[LiquidityEvent]) -> bool {
    events.iter().all(|e| e.tick_lower <= e.tick_upper && e.amount1 >= 0)
}

Try / catch

match db.insert_liquidity_events_batch(&events).await {
    Err(e) if e.to_string().contains("duplicate key") => tracing::debug!("liquidity events already persisted"),
    Err(e) => return Err(e.context("liquidity event batch insert failed"),),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Calling the batch liquidity insert with arrays of differing lengths (amount1s/tick_lowers/tick_uppers vs. other bound arrays), tick values out of the column's range, FK violation when the pool/block rows don't exist, or Postgres connectivity/permission failure during execute.

Common situations: Liquidity events persisted before their parent blocks in a crash-recovery path; replayed events tripping unique constraints; tick values from exotic pools exceeding INT column bounds; un-migrated fresh database.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/e5c6ede113eaa522. Report an issue: GitHub.