phacility/phabricator · error · Exception
Rows passed to "loadAllFromArray(...)" include two or more r
Error message
Rows passed to "loadAllFromArray(...)" include two or more rows with the same ID ("%s"). Rows must have unique IDs. An underlying query may be missing a GROUP BY. What it means
loadAllFromArray() hydrates rows into objects keyed by primary key; if two rows in the input carry the same ID, it throws, because a single-table SELECT can never produce that. The message names the usual root cause: the underlying query joins a one-to-many table and is missing a GROUP BY, so one logical row fans out into several. The check protects the ID-keyed result map from silent overwrites.
Source
Thrown at src/infrastructure/storage/lisk/LiskDAO.php:668
* This is a lot messier than @{method:loadAllWhere}, but more flexible.
*
* @param list List of property dictionaries.
* @return dict List of constructed objects, keyed on ID.
*
* @task load
*/
public function loadAllFromArray(array $rows) {
$result = array();
$id_key = $this->getIDKey();
foreach ($rows as $row) {
$obj = clone $this;
if ($id_key && isset($row[$id_key])) {
$row_id = $row[$id_key];
if (isset($result[$row_id])) {
throw new Exception(
pht(
'Rows passed to "loadAllFromArray(...)" include two or more '.
'rows with the same ID ("%s"). Rows must have unique IDs. '.
'An underlying query may be missing a GROUP BY.',
$row_id));
}
$result[$row_id] = $obj->loadFromArray($row);
} else {
$result[] = $obj->loadFromArray($row);
}
}
return $result;
}
/* -( Examining Objects )-------------------------------------------------- */View on GitHub (pinned to 5720a38cfe)
Solutions
- Add GROUP BY on the primary key (or select DISTINCT on the ID) to the underlying query so each object appears once
- If fan-out is intentional, aggregate the joined values (GROUP_CONCAT, MAX) into columns instead of emitting repeated rows
- Do not call loadAllFromArray() with synthetic rows containing duplicate IDs — hydrate them differently
- Prefer two queries (objects, then related rows) when aggregation distorts the data
Example fix
-- before: join fans out one object into many rows SELECT o.*, e.dst FROM `object` o JOIN `edge` e ON e.src = o.id; -- => two rows with the same o.id => exception on hydration -- after: collapse to one row per object SELECT o.*, GROUP_CONCAT(e.dst) AS dsts FROM `object` o JOIN `edge` e ON e.src = o.id GROUP BY o.id;
Defensive patterns
Strategy: validation
Validate before calling
// Before hydrating joined rows, assert the IDs are unique (i.e., the
// query really is grouped):
$ids = ipull($rows, 'id');
if (count($ids) !== count(array_unique($ids))) {
throw new Exception('Joined query fans out; add GROUP BY on the primary key.');
} Prevention
- Always GROUP BY the primary key (or SELECT DISTINCT ids) in custom joined loaders feeding loadAllFromArray()
- Aggregate the joined side (GROUP_CONCAT/MAX) instead of emitting repeated left-side rows
- When in doubt, run two queries — objects first, related rows second — instead of one fanned-out join
- Add a test that hydrates the custom loader's output so fan-out regressions fail in CI, not production
When it happens
Trigger: A custom loader doing loadAllFromArray() on rows from a query with JOIN against a one-to-many table without GROUP BY/DISTINCT; UNION queries that fail to dedupe; hand-built row arrays passed directly to this method with repeated IDs.
Common situations: Extending LiskDAO with joined loaders (e.g. object + edge/attribute in one query); optimizing N+1 queries by joining and forgetting that the right side repeats the left; ad-hoc aggregation code feeding the DAO hydration API.
Related errors
- More than one result from %s!
- Unable to reload object that hasn't been loaded!
- 1062
- 1146, 1049, 1054
- No storage namespace configured!
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/4bf102973911e116.
Report an issue: GitHub.