feat(db): add contextual_likes table (M3 session-context capture)

Table was referenced in migration 0005's comment but never created —
this slice fills the gap. Soft-delete via deleted_at column; hot-path
partial index on active rows; GIN index on session_vector for
M3 sub-plan #3's similarity queries.
This commit is contained in:
2026-04-27 11:13:31 -04:00
parent 38beadf22e
commit 92ef53c04d
5 changed files with 105 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: contextual_likes.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const insertContextualLike = `-- name: InsertContextualLike :exec
INSERT INTO contextual_likes (user_id, track_id, session_vector, session_id)
VALUES ($1, $2, $3, $4)
`
type InsertContextualLikeParams struct {
UserID pgtype.UUID
TrackID pgtype.UUID
SessionVector []byte
SessionID pgtype.UUID
}
func (q *Queries) InsertContextualLike(ctx context.Context, arg InsertContextualLikeParams) error {
_, err := q.db.Exec(ctx, insertContextualLike,
arg.UserID,
arg.TrackID,
arg.SessionVector,
arg.SessionID,
)
return err
}
const softDeleteContextualLikesForUserTrack = `-- name: SoftDeleteContextualLikesForUserTrack :exec
UPDATE contextual_likes
SET deleted_at = now()
WHERE user_id = $1 AND track_id = $2 AND deleted_at IS NULL
`
type SoftDeleteContextualLikesForUserTrackParams struct {
UserID pgtype.UUID
TrackID pgtype.UUID
}
// Marks all currently-active rows for (user, track) as deleted. Idempotent —
// already-deleted rows aren't re-touched.
func (q *Queries) SoftDeleteContextualLikesForUserTrack(ctx context.Context, arg SoftDeleteContextualLikesForUserTrackParams) error {
_, err := q.db.Exec(ctx, softDeleteContextualLikesForUserTrack, arg.UserID, arg.TrackID)
return err
}
+10
View File
@@ -29,6 +29,16 @@ type Artist struct {
UpdatedAt pgtype.Timestamptz
}
type ContextualLike struct {
ID pgtype.UUID
UserID pgtype.UUID
TrackID pgtype.UUID
LikedAt pgtype.Timestamptz
DeletedAt pgtype.Timestamptz
SessionVector []byte
SessionID pgtype.UUID
}
type GeneralLike struct {
UserID pgtype.UUID
TrackID pgtype.UUID
@@ -0,0 +1 @@
DROP TABLE IF EXISTS contextual_likes;
@@ -0,0 +1,32 @@
-- contextual_likes captures a per-session-context snapshot at the time of
-- each like. The recommendation engine in M3 sub-plan #3 uses these rows
-- to compute contextual_match_score per (current session, candidate track).
--
-- Multiple rows per (user, track) are allowed and expected — each row
-- represents a like in a specific session context. Soft-deleted rows
-- remain in the table (deleted_at populated) but are filtered out of the
-- engine's queries via the partial index. Re-liking a previously-unliked
-- track adds a NEW row (does not undelete the old one).
--
-- Note: the M2 events migration (0005) commented that contextual_likes
-- "ships nullable now" but the actual CREATE TABLE was missed. This slice
-- ships the table for the first time.
CREATE TABLE contextual_likes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
track_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
liked_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
session_vector jsonb,
session_id uuid REFERENCES play_sessions(id) ON DELETE SET NULL
);
-- Hot path for both the engine's lookups and the soft-delete UPDATE.
CREATE INDEX contextual_likes_active_idx
ON contextual_likes (user_id, track_id)
WHERE deleted_at IS NULL;
-- Vector similarity queries from M3 sub-plan #3 will use this.
CREATE INDEX contextual_likes_vector_idx
ON contextual_likes USING gin (session_vector);
+10
View File
@@ -0,0 +1,10 @@
-- name: InsertContextualLike :exec
INSERT INTO contextual_likes (user_id, track_id, session_vector, session_id)
VALUES ($1, $2, $3, $4);
-- name: SoftDeleteContextualLikesForUserTrack :exec
-- Marks all currently-active rows for (user, track) as deleted. Idempotent —
-- already-deleted rows aren't re-touched.
UPDATE contextual_likes
SET deleted_at = now()
WHERE user_id = $1 AND track_id = $2 AND deleted_at IS NULL;