SeaQL/sea-orm · error
item is a full model
Error message
item is a full model
What it means
In establish_links (used when diffing existing links for has_many/many-to-many relations), the code takes each leftover model and calls leftover.get_primary_key_value().expect("item is a full model"). This expect documents the API contract: get_primary_key_value only yields Some for a fully-loaded Model (not a partial ActiveModel), so the panic means a non-full model reached a code path that requires a complete row with all primary key fields populated.
Source
Thrown at sea-orm-sync/src/entity/active_model.rs:1284
let via_key = get_key_from_active_model(&right.from_col, &via)?;
if !leftover.iter().any(|t| t.1 == via_key) {
// if not already exist, save for insert
via_models.push(via);
}
if delete_leftover {
all_keys.insert(via_key);
}
}
if delete_leftover {
let mut to_delete = Vec::new();
let mut to_delete_am = Vec::new();
for (leftover, key) in leftover {
if !all_keys.contains(&key) {
to_delete.push(
leftover
.get_primary_key_value()
.expect("item is a full model"),
);
to_delete_am.push(leftover);
}
}
if !to_delete.is_empty() {
// run before_delete hooks
for am in to_delete_am.clone() {
am.before_delete(db)?;
}
if db.support_returning() {
let deleted = J::delete_many()
.filter_by_value_tuples(&to_delete, db.get_database_backend())
.exec_with_returning(db)?;
// run after_delete hooks with the returned value if possible
for am in deleted {
let am = am.into_active_model();
let _ = am.after_delete(db)?;
}View on GitHub (pinned to e29bcd1b41)
Solutions
- Ensure every model passed to establish_links is a fully loaded Model (from Entity::find()) or ActiveModel::from(model) with primary keys set.
- Load the related rows from the database first (find().one/all) instead of constructing ActiveModels manually, then pass them to the link API.
- If using partial models, set the primary key fields explicitly (ActiveValue::Set(pk)) before calling the API.
- Handle the case at the type level: prefer APIs accepting Model over ActiveModel when you cannot guarantee completeness.
Example fix
// before: partial ActiveModel without primary key reaches establish_links
let am = post::ActiveModel { title: Set("hi".into()), ..Default::default() };
establish_links(am, ...);
// after: load the full model first
let full = post::Entity::find_by_id(post_id).one(db).await?.unwrap();
establish_links(full.into_active_model(), ...); Defensive patterns
Strategy: type-guard
Validate before calling
// Only pass fully-loaded models into link-establishing APIs
fn assert_full_model<M: ModelTrait>(m: &M) {
let pk: Vec<_> = M::PrimaryKey::iter().map(|c| m.get(&c)).collect();
assert!(pk.iter().all(|v| !matches!(v, ActiveValue::NotSet)),
"model passed to establish_links has unset primary key fields");
} Type guard
fn has_primary_key_set<M: ModelTrait>(m: &M) -> bool {
M::PrimaryKey::iter().all(|c| !matches!(m.get(&c), ActiveValue::NotSet))
} Try / catch
// expect() panics are not catchable via Result; prefer loading full models:
match Entity::find_by_id(id).one(db).await? {
Some(full_model) => establish_links(full_model.into_active_model(), ...),
None => return Err(DbErr::RecordNotFound(format!("id {} not found", id))),
} Prevention
- Load models with Entity::find() before passing them to relation APIs instead of hand-building ActiveModels
- Never call establish_links with ActiveModel::default() or partial updates
- Set primary keys explicitly (ActiveValue::Set) when constructing ActiveModels manually
- Avoid SelectColumns/partial selects for models destined for link-diff APIs
When it happens
Trigger: Calling establish_links (or higher-level APIs that diff and prune links, e.g. replacing a relation's item set) while supplying ActiveModel values that are not full Models — e.g. partial updates with unset primary key fields, or NotSet primary keys on models passed into the link-replacement API.
Common situations: Passing ActiveModel::default() or partially-built models (from UpdateMany/Unchanged of incomplete rows) into relation-establishing helpers; using models loaded with select-only subsets of columns so primary key fields are absent; constructing ActiveModels by hand and calling save/link APIs that expect full models.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Failed to set value for {:?}: {e:?}
- Failed to set value for {:?}: {e:?}
- Cannot unwrap ActiveValue::NotSet
- Cannot borrow ActiveValue::NotSet
- called `BelongsTo::unwrap()` on an `Unloaded` value
AI-assisted analysis of SeaQL/sea-orm@e29bcd1b41 (2026-09-10).
Data as JSON: /api/errors/6fc51e434060bfda.
Report an issue: GitHub.