Describe the bug
Several core relationships are plain hasMany/belongsToMany with no ORDER BY. Per the SQL standard, row order is then undefined. MySQL/InnoDB happens to return clustered primary-key order, so this has gone unnoticed — but PostgreSQL (listed as officially supported, 9.4+) returns heap/tuple order, which changes after an UPDATE. The result is user-visible instability when running on PostgreSQL.
Affected relationships:
Cart::lines() — after Cart::updateLine() (which runs refresh()->recalculate()), lines reload in a different order. This is more than cosmetic: GenerateFingerprint reduces $cart->lines in iteration order, so an unstable order can produce a different cart fingerprint for the same logical cart after an unrelated update, affecting draft-order matching.
Order::lines() — invoice/order-history display sequence (and the derived physicalLines/digitalLines/shippingLines/productLines) becomes nondeterministic.
ProductVariant::values() — getOption() joins these values into the variant option label (e.g. "Red, Large"), which is snapshotted onto order lines in CreateOrderLines. The persisted label can therefore differ across engines and shift after updates on PostgreSQL.
To reproduce (PostgreSQL)
- Create a cart with ≥2 lines; call
updateLine() on an earlier line; observe $cart->lines ordering (and $cart->fingerprint()) change.
- Create a variant with multiple option values; observe
getOption() ordering vary.
Expected behavior
Line and option-value order is stable and deterministic across database engines.
Environment
- Lunar: 1.x
- Database: PostgreSQL 9.4+ (officially supported)
- PHP: 8.x
An accompanying PR with failing tests + fixes is attached.
Describe the bug
Several core relationships are plain
hasMany/belongsToManywith noORDER BY. Per the SQL standard, row order is then undefined. MySQL/InnoDB happens to return clustered primary-key order, so this has gone unnoticed — but PostgreSQL (listed as officially supported, 9.4+) returns heap/tuple order, which changes after anUPDATE. The result is user-visible instability when running on PostgreSQL.Affected relationships:
Cart::lines()— afterCart::updateLine()(which runsrefresh()->recalculate()), lines reload in a different order. This is more than cosmetic:GenerateFingerprintreduces$cart->linesin iteration order, so an unstable order can produce a different cart fingerprint for the same logical cart after an unrelated update, affecting draft-order matching.Order::lines()— invoice/order-history display sequence (and the derivedphysicalLines/digitalLines/shippingLines/productLines) becomes nondeterministic.ProductVariant::values()—getOption()joins these values into the variant option label (e.g."Red, Large"), which is snapshotted onto order lines inCreateOrderLines. The persisted label can therefore differ across engines and shift after updates on PostgreSQL.To reproduce (PostgreSQL)
updateLine()on an earlier line; observe$cart->linesordering (and$cart->fingerprint()) change.getOption()ordering vary.Expected behavior
Line and option-value order is stable and deterministic across database engines.
Environment
An accompanying PR with failing tests + fixes is attached.