nautechsystems/nautilus_trader · error · anyhow::Error

Failed to insert into dex table: {e}

Error message

Failed to insert into dex table: {e}

What it means

Database write error in the blockchain cache's dex insert: the SQLx upsert of a DEX row (chain_id, name, factory_address, creation_block) into PostgreSQL failed; the underlying driver error is embedded in the message.

Source

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

            "
            INSERT INTO dex (
                chain_id, name, factory_address, creation_block
            ) VALUES ($1, $2, $3, $4)
            ON CONFLICT (chain_id, name)
            DO UPDATE
            SET
                factory_address = $3,
                creation_block = $4
        ",
        )
        .bind(dex.chain.chain_id as i32)
        .bind(dex.name.to_string())
        .bind(dex.factory.to_string())
        .bind(dex.factory_creation_block as i64)
        .execute(&self.pool)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to insert into dex table: {e}"))
    }

    /// Adds or updates a liquidity pool/pair record in the database.
    ///
    /// # Errors
    ///
    /// Returns an error if the database operation fails.
    pub async fn add_pool(&self, pool: &Pool) -> anyhow::Result<()> {
        sqlx::query(
            "
            INSERT INTO pool (
                chain_id, address, pool_identifier, dex_name, creation_block,
                token0_chain, token0_address,
                token1_chain, token1_address,
                fee, tick_spacing, initial_tick, initial_sqrt_price_x96, hook_address
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
            ON CONFLICT (chain_id, dex_name, pool_identifier)
            DO UPDATE

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped `{e}` to identify the failing column/constraint
  2. Validate the DEX record before insert: non-empty valid address strings for name/factory, factory_creation_block within i64 range
  3. Confirm migrations created the `dex` table and the DB role has INSERT/UPDATE rights
  4. Check the ON CONFLICT target matches the table's actual unique index if upserts fail with 'no unique constraint' errors

Example fix

// before
if dex.name.is_empty() { /* still inserted */ }
db.insert_dex(&dex).await?;
// after
anyhow::ensure!(!dex.name.is_empty() && is_valid_address(&dex.factory), "invalid dex record");
db.insert_dex(&dex).await?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(!dex.name.is_empty(), "dex name empty");
anyhow::ensure!(is_valid_address(&dex.factory), "invalid factory address");
anyhow::ensure!(dex.factory_creation_block <= i64::MAX as u64, "creation block out of range");

Type guard

fn dex_valid(dex: &Dex) -> bool {
    !dex.name.is_empty()
        && dex.factory.len() == 42 && dex.factory.starts_with("0x")
        && dex.factory_creation_block <= i64::MAX as u64
}

Try / catch

if let Err(e) = db.insert_dex(&dex).await {
    if e.to_string().contains("duplicate key") { tracing::debug!("dex already present"); }
    else { return Err(e.context(format!("inserting dex {}", dex.name))); }
}

Prevention

When it happens

Trigger: Calling the DEX insert when `dex.name`, `dex.factory`, or `factory_creation_block` violate column constraints (NULL in NOT NULL column, factory address not valid TEXT/too long), a cast failure binding address types, or Postgres connectivity loss during execute.

Common situations: Factory address produced from mis-decoded log data (invalid hex/empty string); fresh DB without the dex table; concurrent workers upserting the same factory with conflicting ON CONFLICT targets; DB role lacking INSERT privilege.

Related errors


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