Fix export of records with a string primary key #276

Closed
pxlrbt wants to merge 1 commit from fix/uuid-primary-key-export into main
pxlrbt commented 2026-08-15 11:05:56 +00:00 (Migrated from github.com)

Closes #189.

Cause

getQuery() picked the query strategy from the model's declared $keyType:

$model->getKeyType() === 'string'
    ? $query->whereIn($this->modelKeyName, $this->recordIds)
    : $query->whereIntegerInRaw($this->modelKeyName, $this->recordIds)

whereIntegerInRaw() casts every value with (int) and inlines it as a raw literal instead of a binding (Builder.php:1458). So for a model with $primaryKey = 'uuid' that never declared $keyType = 'string', getKeyType() returns the default 'int', every UUID is cast to 0, and you get exactly the SQL from the issue:

select * from "posts" where "posts"."uuid" in (0)
ERROR: operator does not exist: text = integer

On MySQL there is no error at all — it just silently exports zero rows, which is the nastier half of this bug.

Laravel's HasUuids sets $keyType for you, which is why this only bites hand-rolled string keys.

Fix

Decide from the actual key values rather than the declared type. The raw-integer fast path (worth keeping — it avoids thousands of bindings on large "select all" exports) is only taken when every selected key really is an integer; anything else falls back to whereIn() with bindings.

Verified

Against a real SQLite-backed model with $primaryKey = 'uuid' and no $keyType:

  • the UUID export now finds its record (was 0 rows / PG error) and no longer emits in (0)
  • integer keys still compile to in (1, 2, 3) — fast path intact
  • numeric strings (Livewire round-trips ids as strings) also keep the fast path
  • one non-numeric id among integers disables the raw path for the whole set
Closes #189. ### Cause `getQuery()` picked the query strategy from the model's *declared* `$keyType`: ```php $model->getKeyType() === 'string' ? $query->whereIn($this->modelKeyName, $this->recordIds) : $query->whereIntegerInRaw($this->modelKeyName, $this->recordIds) ``` `whereIntegerInRaw()` casts every value with `(int)` and inlines it as a raw literal instead of a binding (`Builder.php:1458`). So for a model with `$primaryKey = 'uuid'` that never declared `$keyType = 'string'`, `getKeyType()` returns the default `'int'`, every UUID is cast to `0`, and you get exactly the SQL from the issue: ``` select * from "posts" where "posts"."uuid" in (0) ERROR: operator does not exist: text = integer ``` On MySQL there is no error at all — it just silently exports zero rows, which is the nastier half of this bug. Laravel's `HasUuids` sets `$keyType` for you, which is why this only bites hand-rolled string keys. ### Fix Decide from the actual key values rather than the declared type. The raw-integer fast path (worth keeping — it avoids thousands of bindings on large "select all" exports) is only taken when every selected key really is an integer; anything else falls back to `whereIn()` with bindings. ### Verified Against a real SQLite-backed model with `$primaryKey = 'uuid'` and no `$keyType`: - the UUID export now finds its record (was 0 rows / PG error) and no longer emits `in (0)` - integer keys still compile to `in (1, 2, 3)` — fast path intact - numeric strings (Livewire round-trips ids as strings) also keep the fast path - one non-numeric id among integers disables the raw path for the whole set
pxlrbt commented 2026-08-15 11:07:17 +00:00 (Migrated from github.com)

Closing this. Laravel core's own whereKey() picks whereIntegerInRaw() off $keyType the exact same way, so this package was following the framework convention — the model in #189 is simply missing protected $keyType = 'string'; and is equally broken in core (Model::find([$uuid]) produces the same SQL). Not worth diverging from Laravel here.

Closing this. Laravel core's own `whereKey()` picks `whereIntegerInRaw()` off `$keyType` the exact same way, so this package was following the framework convention — the model in #189 is simply missing `protected $keyType = 'string';` and is equally broken in core (`Model::find([$uuid])` produces the same SQL). Not worth diverging from Laravel here.

Pull request closed

Sign in to join this conversation.
No description provided.