package history import ( "context" "database/sql" "time" "reanimator/internal/idgen" ) type FailureProjectionInput struct { Source string ExternalID string PartID string MachineID *string FailureType string FailureTime time.Time Details *string } func (s *Service) UpsertFailureProjectionWithTx(ctx context.Context, tx *sql.Tx, in FailureProjectionInput) error { if tx == nil { return ErrConflict } var existingID string err := tx.QueryRowContext(ctx, ` SELECT id FROM failure_events WHERE source = ? AND part_id = ? AND ((machine_id IS NULL AND ? IS NULL) OR machine_id = ?) AND failure_type = ? AND failure_time = ? AND COALESCE(details, '') = COALESCE(?, '') LIMIT 1`, in.Source, in.PartID, in.MachineID, in.MachineID, in.FailureType, in.FailureTime.UTC(), in.Details, ).Scan(&existingID) if err == nil { return nil } if err != sql.ErrNoRows { return err } id, err := s.generateID(ctx, tx, idgen.FailureEvent) if err != nil { return err } _, err = tx.ExecContext(ctx, ` INSERT INTO failure_events (id, source, external_id, part_id, machine_id, failure_type, failure_time, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE part_id = VALUES(part_id), machine_id = VALUES(machine_id), failure_type = VALUES(failure_type), failure_time = VALUES(failure_time), details = VALUES(details)`, id, in.Source, in.ExternalID, in.PartID, in.MachineID, in.FailureType, in.FailureTime.UTC(), in.Details, ) return err }