55 lines
2.1 KiB
PHP
55 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs\Concerns;
|
|
|
|
use App\Models\DeviceState;
|
|
use Illuminate\Database\UniqueConstraintViolationException;
|
|
|
|
/**
|
|
* Monotonic, race-safe upsert of a single entity's state. Shared by every ingest path (H4) so
|
|
* the ordering guarantee is written once: concurrent Horizon workers can finish out of order,
|
|
* and an older message must never overwrite a newer one. The guard lives in the WHERE clause,
|
|
* so it holds without row locks.
|
|
*/
|
|
trait AppliesDeviceState
|
|
{
|
|
/**
|
|
* Returns true when this message's state was applied, false when it was stale
|
|
* (an equal-or-newer state is already stored).
|
|
*
|
|
* @param array<string,mixed> $state
|
|
*/
|
|
protected function applyState(int $entityId, array $state, int $observedAt): bool
|
|
{
|
|
$encoded = json_encode($state);
|
|
|
|
// Overwrite an existing row only when this message is newer.
|
|
$updated = DeviceState::query()
|
|
->where('entity_id', $entityId)
|
|
->where(fn ($q) => $q->whereNull('observed_at')->orWhere('observed_at', '<', $observedAt))
|
|
->update(['state' => $encoded, 'observed_at' => $observedAt, 'updated_at' => now()]);
|
|
|
|
if ($updated > 0) {
|
|
return true;
|
|
}
|
|
|
|
// No row yet → insert (unique(entity_id) guards against a concurrent insert).
|
|
if (! DeviceState::where('entity_id', $entityId)->exists()) {
|
|
try {
|
|
DeviceState::create(['entity_id' => $entityId, 'state' => $state, 'observed_at' => $observedAt]);
|
|
|
|
return true;
|
|
} catch (UniqueConstraintViolationException) {
|
|
// Lost the insert race — fall through and retry the conditional update.
|
|
return DeviceState::query()
|
|
->where('entity_id', $entityId)
|
|
->where(fn ($q) => $q->whereNull('observed_at')->orWhere('observed_at', '<', $observedAt))
|
|
->update(['state' => $encoded, 'observed_at' => $observedAt, 'updated_at' => now()]) > 0;
|
|
}
|
|
}
|
|
|
|
// Existing row is equal-or-newer → this message is stale.
|
|
return false;
|
|
}
|
|
}
|