{"record":{"id":"b53f0b83f505ffa8","repo":"doctrine/orm","slug":"unexpected-empty-result-for-database-query","errorCode":null,"errorMessage":"Unexpected empty result for database query.","messagePattern":"Unexpected empty result for database query\\.","errorType":"exception","errorClass":"LengthException","httpStatus":null,"severity":"error","filePath":"src/Persisters/Entity/BasicEntityPersister.php","lineNumber":331,"sourceCode":"\n        $tableName  = $this->quoteStrategy->getTableName($versionedClass, $this->platform);\n        $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);\n\n        // FIXME: Order with composite keys might not be correct\n        $sql = 'SELECT ' . implode(', ', $columnNames)\n            . ' FROM ' . $tableName\n            . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';\n\n        $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);\n\n        $values = $this->conn->fetchNumeric(\n            $sql,\n            array_values($flatId),\n            $this->extractIdentifierTypes($id, $versionedClass),\n        );\n\n        if ($values === false) {\n            throw new LengthException('Unexpected empty result for database query.');\n        }\n\n        $values = array_combine(array_keys($columnNames), $values);\n\n        if (! $values) {\n            throw new LengthException('Unexpected number of database columns.');\n        }\n\n        return $values;\n    }\n\n    /**\n     * @param mixed[] $id\n     *\n     * @return list<ParameterType|int|string>\n     * @phpstan-return list<ParameterType::*|ArrayParameterType::*|string>\n     */\n    final protected function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Persisters/Entity/BasicEntityPersister.php#L313-L349","documentation":"After an INSERT/UPDATE flush, BasicEntityPersister::assignDefaultVersionAndUpsertableValues() re-SELECTs version and generated (insertable:false/updatable:false) columns for the written row by its identifier (fetchVersionAndNotUpsertableValues). fetchNumeric() returning false means the row the UnitOfWork just wrote cannot be read back, which should be impossible in a consistent transaction, so it throws LengthException to surface the inconsistency.","triggerScenarios":"flush() on an entity with #[Version] or generated columns where the row is gone at re-select time: an AFTER INSERT/UPDATE trigger deleting or moving the row, a concurrent delete committed between the write and the re-select, row-level security/tenant filters hiding the row, or a read/write-split connection routing the SELECT to a replica with lag.","commonSituations":"MySQL/Postgres triggers; multi-tenant RLS policies; Doctrine with a connection wrapper that sends reads to a read replica mid-flush; identifiers mangled by a custom DBAL type so the WHERE clause selects nothing.","solutions":["Check for database triggers or row-level security on the table that remove/hide the row after the write","Ensure the flush's write AND re-select run on the same connection and inside the same transaction (no read/write splitting mid-flush)","Verify identifier mapping (custom DBAL types on the id column can make the WHERE clause not match)","If another process legitimately deletes during flush, reorder the operations (perform the delete after flush, or detach the entity)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Guard the environment, not the call: assert the row is visible on the same connection before flush-heavy paths\n$row = $conn->fetchOne('SELECT 1 FROM ' . $meta->getTableName() . ' WHERE ' . $meta->getSingleIdentifierColumnName() . ' = ?', [$id]);\nif ($row === false) { /* do not proceed with generated-column/versioned writes */ }","typeGuard":null,"tryCatchPattern":"try { $em->flush(); } catch (LengthException $e) { if ($e->getMessage() === 'Unexpected empty result for database query.') { /* inspect triggers / replica routing, do not blind-retry */ } throw $e; }","preventionTips":["Audit AFTER INSERT/UPDATE triggers on tables with versioned/generated columns","Keep the flush on one connection and transaction (no replica routing mid-flush)","Test entities with #[Version] and generated columns against the real DBMS in CI"],"tags":["flush","optimistic-lock","generated-columns","database-consistency"],"backgroundTag":"row-vanished-after-write","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}