- Ya
- 2 Septemba 2026, 05:34 UTC
- Mwandishi
- Kamo
- Ahadi ya
- 9f2e060
The search index row's key is assigned rather than generated, so handing a built entity to save() was a merge: Spring Data SELECTed the whole row — body text included — before every UPDATE. That runs once per message for every message a folder listing touches, so each indexed message cost two round trips where one would do. Letting the entity drive the write was wrong in a second way. Its builder sets the envelope and nothing else, so the merge carried nulls for createdAt, updatedAt and labelIds: Hibernate dropped the two generated timestamps and logged HHH000502 about it on every flush, and label_ids was overwritten. Naming the columns says which ones a re-index actually owns. created_at now keeps the value it got when the message was first seen, label_ids is left alone, and search_vector stays with the statement that computes it. Arrays are bound as JDBC arrays rather than written into the SQL, because neither alternative survives contact with this stack: Postgres will not cast a text value to text[], and Hibernate 6.2 is the version that resolves an array's element class to java.lang.Class and dies in BasicCollectionJavaType.unwrap — the reason the entity's own array columns are bare String[]. Connection .createArrayOf goes to the driver and sidesteps both. The statement also carries no "::" anywhere, which the native-query parser would eat. Verified against the live table inside a transaction that was rolled back: the conflict branch left created_at at its original value, moved updated_at, and wrote the new envelope; the insert branch created the row; nothing persisted. Noticed while here and NOT addressed: label_ids is set on no row in production, so the label: search operator that reads it can never match. It is a join table, message_labels, that actually records labels.