Bug: Queued/chunked export can return values from joined table when using joinRelationship() #281

Open
opened 2026-09-04 07:33:00 +00:00 by mc-fu · 0 comments
mc-fu commented 2026-09-04 07:33:00 +00:00 (Migrated from github.com)

Bug: Queued/chunked export can return values from joined table when using joinRelationship()

Description

When exporting a Filament table using pxlrbt/filament-excel with a joined relationship, columns with the same name in the parent and joined table can contain values from the wrong table after a chunk boundary.

For example, both employee_records and employees have a column named notes.

The table display itself is always correct. The problem only occurs during the export.

The issue occurs when the export is processed in multiple chunks. With our test data, a chunk size of 2 or 10 reliably reproduces the problem.

A chunk size of 1000 does not reproduce the issue with our current dataset because we have fewer than 1000 records, so there is no second chunk. We therefore expect the issue to occur with a chunk size of 1000 as soon as the dataset contains more than 1000 records.

Versions

  • Filament: 3.x
  • pxlrbt/filament-excel: 2.5.0
  • maatwebsite/excel: 3.1.70
  • anourvalar/eloquent-serialize: 1.3.11
  • kirschbaum-development/eloquent-power-joins: 4.3.3

Example

The Eloquent query uses joinRelationship():

EmployeeRecord::query()
    ->joinRelationship('employee')

The generated SQL contains the expected parent-table selection:

select `employee_records`.*
from `employee_records`
inner join `employees`
    on `employee_records`.`employee_id` = `employees`.`id`

The important part is that only the parent table is selected:

select `employee_records`.*

Observed behavior

Suppose:

employee_records.notes = "Primary assignment"
employees.notes = "Badge replaced on July 22"

The exported value is initially correct:

Primary assignment

After a chunk boundary, however, the exported value can become:

Badge replaced on July 22

This happens in CSV/PDF exports as well, so this does not appear to be specific to PhpSpreadsheet/XLSX formatting.

The actual Eloquent model already contains the wrong value at the mapping stage:

$record->getAttributes()['notes']

contains the value from employees.notes instead of employee_records.notes.

Important finding

The relevant code in eloquent-power-joins is located in:

vendor/kirschbaum-development/eloquent-power-joins/src/Mixins/JoinRelationship.php

Inside joinRelationship(), the package registers a beforeQuery callback:

$defaultSelect = sprintf('%s.*', $mainTableOrAlias);

$this->getQuery()->beforeQuery(function ($queryBuilder) use ($defaultSelect) {
    if (is_null($queryBuilder->columns) || $queryBuilder->columns === ['*']) {
        $queryBuilder->columns = [$defaultSelect];
    }
});

In this example this results in:

$queryBuilder->columns = ['employee_records.*'];

The export query itself is created in:

vendor/pxlrbt/filament-excel/src/Exports/ExcelExport.php

Specifically, ExcelExport::query() returns the query used by the export.

The following behavior is reproducible.

Without manually applying the callbacks

In ExcelExport.php:

$query = $this->getQuery();

return $query;

and then running the export with:

->withChunkSize(2)

results in the wrong notes value after a chunk boundary.

Manually applying the callbacks

Changing ExcelExport::query() to:

$query = $this->getQuery();

$query->getQuery()->applyBeforeQueryCallbacks();

return $query;

makes the export consistently correct.

This is reproducible with the same data and the same chunk size.

Additional observations

Calling:

$query->toSql();

also fixes the problem.

This is consistent with Laravel's Query\Builder::toSql() implementation, which calls:

$this->applyBeforeQueryCallbacks();

before compiling the SQL.

Calling compileSelect() directly does not reproduce the fix.

Reading bindings alone does not reproduce the fix.

Adding a delay (usleep) does not reproduce the fix.

We also tested the JoinsHelper::clearCacheBeforeQuery() callback separately. Removing that callback did not make the problem disappear. The relevant behavior appears to be the execution of the beforeQuery callbacks, in particular the callback which establishes the explicit parent-table selection.

Temporary workaround

Currently, this fixes the problem:

public function query()
{
    $query = $this->getQuery();

    $query->getQuery()->applyBeforeQueryCallbacks();

    if ($this->isQueued()) {
        $this->livewire = null;
    }

    return $query;
}

However, modifying the package's ExcelExport::query() method like this is obviously not ideal, and I would prefer a proper fix.

Expected behavior

A queued/chunked export should return the same Eloquent model attributes as a normal query, including when:

  • joinRelationship() is used
  • the parent and joined table contain columns with identical names
  • the export is processed in multiple chunks

In particular, employee_records.notes should never be replaced by employees.notes.

Actual behavior

After processing a chunk boundary, the model can contain the value of the identically named column from the joined table.

Possible cause

My current suspicion is that the query is cloned and/or serialized for the queued/chunked export before the beforeQuery callbacks registered by eloquent-power-joins have been applied.

As a result, the intended:

select `employee_records`.*

selection is not necessarily established before the query is executed for a subsequent chunk.

Manually calling:

$query->getQuery()->applyBeforeQueryCallbacks();

before returning the query makes the issue disappear.

Thanks!

## Bug: Queued/chunked export can return values from joined table when using `joinRelationship()` ### Description When exporting a Filament table using `pxlrbt/filament-excel` with a joined relationship, columns with the same name in the parent and joined table can contain values from the wrong table after a chunk boundary. For example, both `employee_records` and `employees` have a column named `notes`. The table display itself is always correct. The problem only occurs during the export. The issue occurs when the export is processed in multiple chunks. With our test data, a chunk size of `2` or `10` reliably reproduces the problem. A chunk size of `1000` does not reproduce the issue with our current dataset because we have fewer than 1000 records, so there is no second chunk. We therefore expect the issue to occur with a chunk size of `1000` as soon as the dataset contains more than 1000 records. ### Versions * Filament: 3.x * `pxlrbt/filament-excel`: `2.5.0` * `maatwebsite/excel`: `3.1.70` * `anourvalar/eloquent-serialize`: `1.3.11` * `kirschbaum-development/eloquent-power-joins`: `4.3.3` ### Example The Eloquent query uses `joinRelationship()`: ```php EmployeeRecord::query() ->joinRelationship('employee') ``` The generated SQL contains the expected parent-table selection: ```sql select `employee_records`.* from `employee_records` inner join `employees` on `employee_records`.`employee_id` = `employees`.`id` ``` The important part is that only the parent table is selected: ```sql select `employee_records`.* ``` ### Observed behavior Suppose: ```text employee_records.notes = "Primary assignment" employees.notes = "Badge replaced on July 22" ``` The exported value is initially correct: ```text Primary assignment ``` After a chunk boundary, however, the exported value can become: ```text Badge replaced on July 22 ``` This happens in CSV/PDF exports as well, so this does not appear to be specific to PhpSpreadsheet/XLSX formatting. The actual Eloquent model already contains the wrong value at the mapping stage: ```php $record->getAttributes()['notes'] ``` contains the value from `employees.notes` instead of `employee_records.notes`. ### Important finding The relevant code in `eloquent-power-joins` is located in: ```text vendor/kirschbaum-development/eloquent-power-joins/src/Mixins/JoinRelationship.php ``` Inside `joinRelationship()`, the package registers a `beforeQuery` callback: ```php $defaultSelect = sprintf('%s.*', $mainTableOrAlias); $this->getQuery()->beforeQuery(function ($queryBuilder) use ($defaultSelect) { if (is_null($queryBuilder->columns) || $queryBuilder->columns === ['*']) { $queryBuilder->columns = [$defaultSelect]; } }); ``` In this example this results in: ```php $queryBuilder->columns = ['employee_records.*']; ``` The export query itself is created in: ```text vendor/pxlrbt/filament-excel/src/Exports/ExcelExport.php ``` Specifically, `ExcelExport::query()` returns the query used by the export. The following behavior is reproducible. #### Without manually applying the callbacks In `ExcelExport.php`: ```php $query = $this->getQuery(); return $query; ``` and then running the export with: ```php ->withChunkSize(2) ``` results in the wrong `notes` value after a chunk boundary. #### Manually applying the callbacks Changing `ExcelExport::query()` to: ```php $query = $this->getQuery(); $query->getQuery()->applyBeforeQueryCallbacks(); return $query; ``` makes the export consistently correct. This is reproducible with the same data and the same chunk size. ### Additional observations Calling: ```php $query->toSql(); ``` also fixes the problem. This is consistent with Laravel's `Query\Builder::toSql()` implementation, which calls: ```php $this->applyBeforeQueryCallbacks(); ``` before compiling the SQL. Calling `compileSelect()` directly does not reproduce the fix. Reading bindings alone does not reproduce the fix. Adding a delay (`usleep`) does not reproduce the fix. We also tested the `JoinsHelper::clearCacheBeforeQuery()` callback separately. Removing that callback did not make the problem disappear. The relevant behavior appears to be the execution of the `beforeQuery` callbacks, in particular the callback which establishes the explicit parent-table selection. ### Temporary workaround Currently, this fixes the problem: ```php public function query() { $query = $this->getQuery(); $query->getQuery()->applyBeforeQueryCallbacks(); if ($this->isQueued()) { $this->livewire = null; } return $query; } ``` However, modifying the package's `ExcelExport::query()` method like this is obviously not ideal, and I would prefer a proper fix. ### Expected behavior A queued/chunked export should return the same Eloquent model attributes as a normal query, including when: * `joinRelationship()` is used * the parent and joined table contain columns with identical names * the export is processed in multiple chunks In particular, `employee_records.notes` should never be replaced by `employees.notes`. ### Actual behavior After processing a chunk boundary, the model can contain the value of the identically named column from the joined table. ### Possible cause My current suspicion is that the query is cloned and/or serialized for the queued/chunked export before the `beforeQuery` callbacks registered by `eloquent-power-joins` have been applied. As a result, the intended: ```sql select `employee_records`.* ``` selection is not necessarily established before the query is executed for a subsequent chunk. Manually calling: ```php $query->getQuery()->applyBeforeQueryCallbacks(); ``` before returning the query makes the issue disappear. Thanks!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pxlrbt/filament-excel#281
No description provided.