From 288bb1c375efe1b91155ba67edb13bea8338b93c Mon Sep 17 00:00:00 2001 From: dbastrikin Date: Sat, 21 Feb 2026 23:24:42 +0200 Subject: [PATCH] fix: compute expires_at in SQL instead of generated column TIMESTAMPTZ + INTERVAL is STABLE (depends on timezone), not IMMUTABLE, so PostgreSQL rejects it in GENERATED ALWAYS AS STORED columns. Fix: remove generated column and compute expires_at inline in each query as (added_at + storage_days * INTERVAL '1 day'). Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/product/repository.go | 5 ++++- backend/migrations/006_create_products.sql | 9 ++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/internal/product/repository.go b/backend/internal/product/repository.go index 6d0ed99..52264f2 100644 --- a/backend/internal/product/repository.go +++ b/backend/internal/product/repository.go @@ -23,7 +23,10 @@ func NewRepository(pool *pgxpool.Pool) *Repository { return &Repository{pool: pool} } -const selectCols = `id, user_id, mapping_id, name, quantity, unit, category, storage_days, added_at, expires_at` +// expires_at is computed in SQL because TIMESTAMPTZ + INTERVAL is STABLE (not IMMUTABLE), +// which prevents it from being used as a stored generated column. +const selectCols = `id, user_id, mapping_id, name, quantity, unit, category, storage_days, added_at, + (added_at + storage_days * INTERVAL '1 day') AS expires_at` // List returns all products for a user, sorted by expires_at ASC. func (r *Repository) List(ctx context.Context, userID string) ([]*Product, error) { diff --git a/backend/migrations/006_create_products.sql b/backend/migrations/006_create_products.sql index c22b5e7..c586508 100644 --- a/backend/migrations/006_create_products.sql +++ b/backend/migrations/006_create_products.sql @@ -8,13 +8,12 @@ CREATE TABLE products ( unit TEXT NOT NULL DEFAULT 'pcs', category TEXT, storage_days INT NOT NULL DEFAULT 7, - added_at TIMESTAMPTZ NOT NULL DEFAULT now(), - expires_at TIMESTAMPTZ GENERATED ALWAYS AS - (added_at + (storage_days || ' days')::INTERVAL) STORED + added_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -CREATE INDEX idx_products_user_id ON products(user_id); -CREATE INDEX idx_products_expires_at ON products(user_id, expires_at); +-- expires_at is computed as (added_at + storage_days * INTERVAL '1 day') in queries. +-- A stored generated column cannot be used because timestamptz + interval is STABLE, not IMMUTABLE. +CREATE INDEX idx_products_user_id ON products(user_id); -- +goose Down DROP TABLE products;