nautechsystems/nautilus_trader · error

Failed to insert into order table: {e}

Error message

Failed to insert into order table: {e}

What it means

Raised by `add_order_snapshot` on the second step of its transaction: the INSERT into the `"order"` table fails. At this point the trader-table insert already succeeded, but since the transaction is never committed it is rolled back, so no partial data is persisted. The sqlx error is wrapped in `anyhow` with this message.

Source

Thrown at crates/infrastructure/src/sql/queries.rs:451

            .bind(snapshot.contingency_type.map_or_else(
                || "NO_CONTINGENCY".to_string(),
                |value| value.to_string(),
            ))
            .bind(snapshot.order_list_id.map(|x| x.to_string()))
            .bind(snapshot.linked_order_ids.map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()))
            .bind(snapshot.parent_order_id.map(|x| x.to_string()))
            .bind(snapshot.exec_algorithm_id.map(|x| x.to_string()))
            .bind(snapshot.exec_algorithm_params.map(|x| serde_json::to_value(x).unwrap()))
            .bind(snapshot.exec_spawn_id.map(|x| x.to_string()))
            .bind(snapshot.tags.map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()))
            .bind(snapshot.init_id.to_string())
            .bind(snapshot.ts_init.to_string())
            .bind(snapshot.ts_last.to_string())
            .bind(snapshot.activation_price.map(|x| x.to_string()))
            .execute(&mut *transaction)
            .await
            .map(|_| ())
            .map_err(|e| anyhow::anyhow!("Failed to insert into order table: {e}"))?;

        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit transaction: {e}"))
    }

    /// Loads an `OrderSnapshot` entry by client order ID via the provided `pool`.
    ///
    /// # Errors
    ///
    /// Returns an error if the SQL SELECT or deserialization fails.
    pub async fn load_order_snapshot(
        pool: &PgPool,
        client_order_id: &ClientOrderId,
    ) -> anyhow::Result<Option<OrderSnapshot>> {
        sqlx::query_as::<_, OrderSnapshotRow>(r#"SELECT * FROM "order" WHERE client_order_id = $1"#)
            .bind(client_order_id.to_string())

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped `{e}` to identify the exact constraint or type error.
  2. Ensure migrations have created the `"order"` table with the expected schema.
  3. Avoid duplicate writes: check `load_order_snapshot` first or add ON CONFLICT handling for client_order_id.
  4. Verify optional fields (price, trigger_price, venue_order_id) match column nullability and types.

Example fix

// before: blindly inserting duplicates
add_order_snapshot(&pool, &snapshot).await?;

// after: skip if already persisted
if load_order_snapshot(&pool, snapshot.client_order_id).await?.is_none() {
    add_order_snapshot(&pool, &snapshot).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Avoid the most common cause: duplicate client_order_id
let dup = sqlx::query_scalar::<_, i64>(
    "SELECT COUNT(*) FROM \"order\" WHERE client_order_id = $1")
    .bind(snapshot.client_order_id.to_string())
    .fetch_one(pool).await? > 0;
if dup { return Ok(()); }

Try / catch

match add_order_snapshot(&pool, &snapshot).await {
    Ok(()) => (),
    Err(e) if format!("{e:#}").contains("duplicate key") => {
        tracing::warn!("snapshot already exists for {}", snapshot.client_order_id);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `add_order_snapshot` when: the `"order"` table is missing; a required column is NULL (e.g. venue_order_id, position_id constraints); a value exceeds a column's length/precision; a bound value's string form fails to parse into the column type; or a unique constraint on client_order_id is violated by a duplicate snapshot.

Common situations: Writing the same order snapshot twice during replay recovery; migrations not applied; enum-like values (order_type, order_side) written as strings not matching the DB enum/CHECK constraints; schema changed after a crate upgrade so bind count/types mismatch.

Related errors


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