nautechsystems/nautilus_trader · error · anyhow::Error

Failed to insert into pool table: {e}

Error message

Failed to insert into pool table: {e}

What it means

Database write error in the blockchain cache's pool insert: the SQLx upsert of a liquidity pool row (addresses, tokens, fee, tick spacing, initial state) into PostgreSQL failed; the driver error is embedded in the message.

Source

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

        )
        .bind(pool.chain.chain_id as i32)
        .bind(pool.address.to_string())
        .bind(pool.pool_identifier.as_ref())
        .bind(pool.dex.name.to_string())
        .bind(pool.creation_block as i64)
        .bind(pool.token0.chain.chain_id as i32)
        .bind(pool.token0.address.to_string())
        .bind(pool.token1.chain.chain_id as i32)
        .bind(pool.token1.address.to_string())
        .bind(pool.fee.map(|fee| fee as i32))
        .bind(pool.tick_spacing.map(|tick_spacing| tick_spacing as i32))
        .bind(pool.initial_tick)
        .bind(pool.initial_sqrt_price_x96.as_ref().map(|p| p.to_string()))
        .bind(pool.hooks.as_ref().map(|h| h.to_string()))
        .execute(&self.pool)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to insert into pool table: {e}"))
    }

    /// Inserts multiple pools in a single database operation using UNNEST for optimal performance.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn add_pools_batch(&self, pools: &[Pool]) -> anyhow::Result<()> {
        if pools.is_empty() {
            return Ok(());
        }

        // Prepare vectors for each column
        let len = pools.len();
        let mut addresses: Vec<String> = Vec::with_capacity(len);
        let mut pool_identifiers: Vec<String> = Vec::with_capacity(len);
        let mut dex_names: Vec<String> = Vec::with_capacity(len);
        let mut creation_blocks: Vec<i64> = Vec::with_capacity(len);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped `{e}` for the exact constraint (FK to dex, NOT NULL, cast)
  2. Insert the parent DEX record before pools that reference it
  3. Confirm the schema includes initial_tick/initial_sqrt_price_x96/hooks columns (run migrations) — old schemas cause 'column does not exist'
  4. Validate initial_sqrt_price_x96 renders as a plain decimal/hex string the column accepts before binding

Example fix

// before
db.insert_pool(&pool).await?; // FK failure if dex missing
// after
db.insert_dex(&dex).await?;
db.insert_pool(&pool).await?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(db_dex_exists(pool, &pool_record.dex).await?, "referencing dex not in DB; insert it first");
if let Some(p) = &pool_record.initial_sqrt_price_x96 {
    anyhow::ensure!(!p.to_string().is_empty(), "empty sqrt price");
}

Type guard

fn pool_record_valid(p: &PoolRecord) -> bool {
    !p.address.is_empty() && p.initial_tick.map_or(true, |t| t >= i32::MIN as i64 && t <= i32::MAX as i64)
}

Try / catch

match db.insert_pool(&pool_record).await {
    Err(e) if e.to_string().contains("violates foreign key") => {
        db.insert_dex(&parent_dex).await?;
        db.insert_pool(&pool_record).await?;
    }
    Err(e) => return Err(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Calling the pool insert with a record whose `initial_sqrt_price_x96` string is malformed for the column's cast, `hooks`/`initial_tick` conflicting with column constraints, foreign-key failure when the pool's dex doesn't exist in `dex`, or connection/pool failure during execute.

Common situations: Inserting a pool whose DEX wasn't registered first (FK violation); V4-style pools with hooks written to a schema predating the hooks column; sqrt_price serialized with scientific notation that the TEXT/numeric column rejects; empty database missing migrations.

Related errors


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