doctrine/orm · error · LengthException
Unexpected empty result for database query.
Error message
Unexpected empty result for database query.
What it means
JoinedSubclassPersister (entity with joined-table inheritance) re-SELECTs version/generated columns after a write, joining the base and parent tables, filtered by the flattened identifier. fetchNumeric() returning false means the just-written row is no longer visible, so the invariant is broken and a LengthException is thrown mid-flush.
Source
Thrown at src/Persisters/Entity/JoinedSubclassPersister.php:562
$identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);
foreach ($identifier as $i => $idValue) {
$identifier[$i] = $baseTableAlias . '.' . $idValue;
}
$sql = 'SELECT ' . implode(', ', $columnNames)
. ' FROM ' . $tableName . ' ' . $baseTableAlias
. $joinSql
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);
$values = $this->conn->fetchNumeric(
$sql,
array_values($flatId),
$this->extractIdentifierTypes($id, $versionedClass),
);
if ($values === false) {
throw new LengthException('Unexpected empty result for database query.');
}
$values = array_combine(array_keys($columnNames), $values);
if (! $values) {
throw new LengthException('Unexpected number of database columns.');
}
return $values;
}
private function getJoinSql(string $baseTableAlias): string
{
$joinSql = '';
$identifierColumn = $this->class->getIdentifierColumnNames();
// INNER JOIN parent tables
foreach ($this->class->parentClasses as $parentClassName) {View on GitHub (pinned to d9b9ff7301)
Solutions
- Inspect triggers and row-level security on ALL tables of the inheritance hierarchy
- Guarantee write and re-select share one connection and transaction
- Check the discriminator/identifier mapping so the WHERE matches the actual row
- If a concurrent delete is expected, serialize the operations (lock the row with a pessimistic lock or reorder application logic)
Defensive patterns
Strategy: validation
Validate before calling
// Ensure the write and re-select share the connection; refuse flushes on read-only/replica connections
if (! $em->getConnection()->isTransactionActive() && $usesReplicaRouting) { /* pin to primary before flush */ } Try / catch
try { $em->flush(); } catch (LengthException $e) { if ($e->getMessage() === 'Unexpected empty result for database query.') { /* check triggers/RLS on all hierarchy tables */ } throw $e; } Prevention
- Review triggers and RLS on every table of a joined hierarchy
- Pin flushes to the primary connection
- Use pessimistic locking when concurrent deletes are possible
When it happens
Trigger: flush() on a joined-inheritance entity with #[Version] or generated columns where the row disappears between write and re-select: AFTER INSERT/UPDATE triggers, concurrent deletes, row-level security, or connection routing that sends the re-select to a lagging replica.
Common situations: Audit/soft-delete triggers on child tables in inheritance hierarchies; tenant isolation policies; replica-routing connection wrappers; integration tests with READ UNCOMMITTED or nested transaction quirks.
Related errors
- Unexpected empty result for database query.
- Attempting to change readonly property %s::$%s.
- Unexpected number of database columns.
- Commit failed
- Cannot call recomputeSingleEntityChangeSet before computeCha
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/af9e0c4120e48a5d.
Report an issue: GitHub.