diff --git a/internal/dao/slonik/ce/postgres/models.go b/internal/dao/slonik/ce/postgres/models.go index d50ca7ad..f8389b70 100644 --- a/internal/dao/slonik/ce/postgres/models.go +++ b/internal/dao/slonik/ce/postgres/models.go @@ -12,9 +12,9 @@ type PTag struct { UserID int64 Tag string QuoteNum int64 - CreatedOn int32 - ModifiedOn int32 - DeletedOn int32 + CreatedOn int64 + ModifiedOn int64 + DeletedOn int64 // 是否删除 IsDel bool } diff --git a/internal/dao/slonik/ce/postgres/querier.go b/internal/dao/slonik/ce/postgres/querier.go index 7ba9ef07..73bdb8a0 100644 --- a/internal/dao/slonik/ce/postgres/querier.go +++ b/internal/dao/slonik/ce/postgres/querier.go @@ -6,21 +6,16 @@ package dbr import ( "context" - - "github.com/jackc/pgx/v5/pgtype" ) type Querier interface { DecrTagsById(ctx context.Context, arg *DecrTagsByIdParams) error HotTags(ctx context.Context, arg *HotTagsParams) ([]*HotTagsRow, error) - IncrTagsById(ctx context.Context, arg *IncrTagsByIdParams) error - InsertTags(ctx context.Context, arg *InsertTagsParams) (*InsertTagsRow, error) + IncrTags(ctx context.Context, arg *IncrTagsParams) ([]*IncrTagsRow, error) + InsertTags(ctx context.Context, arg *InsertTagsParams) (int64, error) NewestTags(ctx context.Context, arg *NewestTagsParams) ([]*NewestTagsRow, error) - TagsByIdA(ctx context.Context, ids pgtype.Array[int64]) ([]int64, error) - TagsByIdB(ctx context.Context, ids pgtype.Array[int64]) ([]*TagsByIdBRow, error) TagsByKeywordA(ctx context.Context) ([]*TagsByKeywordARow, error) TagsByKeywordB(ctx context.Context, tag string) ([]*TagsByKeywordBRow, error) - TagsByName(ctx context.Context, tags pgtype.Array[string]) ([]*TagsByNameRow, error) } var _ Querier = (*Queries)(nil) diff --git a/internal/dao/slonik/ce/postgres/query/topic.sql b/internal/dao/slonik/ce/postgres/query/topic.sql index febcdeb6..96de9bb6 100644 --- a/internal/dao/slonik/ce/postgres/query/topic.sql +++ b/internal/dao/slonik/ce/postgres/query/topic.sql @@ -29,30 +29,26 @@ OFFSET 0 LIMIT 6; -- name: InsertTags :one INSERT INTO p_tag (user_id, tag, created_on, modified_on, quote_num) VALUES ($1, $2, $3, $3, 1) -RETURNING id, user_id, tag, quote_num; - --- name: TagsByIdA :many -SELECT id -FROM p_tag -WHERE id = ANY(@ids::bigserial[]) AND is_del = false AND quote_num >= 0; - --- name: TagsByIdB :many -SELECT id, user_id, tag, quote_num -FROM p_tag -WHERE id = ANY(@ids::bigserial[]); +RETURNING id; -- name: DecrTagsById :exec UPDATE p_tag SET quote_num = quote_num-1, modified_on=$1 -WHERE id = ANY(@ids::bigserial[]); +WHERE id IN ( + SELECT id + FROM p_tag + WHERE id = ANY(@ids::bigserial[]) AND is_del = false AND quote_num >= 1 +); --- name: TagsByName :many -SELECT id, user_id, tag, quote_num -FROM p_tag -WHERE tag = ANY(@tags::varchar[]) AND is_del = false AND quote_num >= 0; - --- name: IncrTagsById :exec +-- name: IncrTags :many UPDATE p_tag -SET quote_num = quote_num+1, modified_on = $1 -WHERE id = ANY(@ids::bigserial[]); +SET quote_num = quote_num+1, + modified_on = $1, + id_del = false +WHERE id IN ( + SELECT id + FROM p_tag + WHERE tag = ANY(@tags::varchar[]) +) +RETURNING id, user_id, tag, quote_num; diff --git a/internal/dao/slonik/ce/postgres/schema/0001_initialize_schema.up.sql b/internal/dao/slonik/ce/postgres/schema/0001_initialize_schema.up.sql index aa7900ac..546b5874 100644 --- a/internal/dao/slonik/ce/postgres/schema/0001_initialize_schema.up.sql +++ b/internal/dao/slonik/ce/postgres/schema/0001_initialize_schema.up.sql @@ -3,9 +3,9 @@ CREATE TABLE p_tag ( user_id bigserial NOT NULL DEFAULT 0, tag varchar(255) NOT NULL, quote_num bigint NOT NULL DEFAULT 0, - created_on int NOT NULL DEFAULT 0, - modified_on int NOT NULL DEFAULT 0, - deleted_on int NOT NULL DEFAULT 0, + created_on bigint NOT NULL DEFAULT 0, + modified_on bigint NOT NULL DEFAULT 0, + deleted_on bigint NOT NULL DEFAULT 0, is_del boolean NOT NULL DEFAULT false, UNIQUE (tag) ); diff --git a/internal/dao/slonik/ce/postgres/topic.sql.go b/internal/dao/slonik/ce/postgres/topic.sql.go index b3c726c0..63027dd9 100644 --- a/internal/dao/slonik/ce/postgres/topic.sql.go +++ b/internal/dao/slonik/ce/postgres/topic.sql.go @@ -15,11 +15,15 @@ const decrTagsById = `-- name: DecrTagsById :exec UPDATE p_tag SET quote_num = quote_num-1, modified_on=$1 -WHERE id = ANY($2::bigserial[]) +WHERE id IN ( + SELECT id + FROM p_tag + WHERE id = ANY($2::bigserial[]) AND is_del = false AND quote_num >= 1 +) ` type DecrTagsByIdParams struct { - ModifiedOn int32 + ModifiedOn int64 Ids pgtype.Array[int64] } @@ -73,82 +77,40 @@ func (q *Queries) HotTags(ctx context.Context, arg *HotTagsParams) ([]*HotTagsRo return items, nil } -const incrTagsById = `-- name: IncrTagsById :exec +const incrTags = `-- name: IncrTags :many UPDATE p_tag -SET quote_num = quote_num+1, modified_on = $1 -WHERE id = ANY($2::bigserial[]) -` - -type IncrTagsByIdParams struct { - ModifiedOn int32 - Ids pgtype.Array[int64] -} - -func (q *Queries) IncrTagsById(ctx context.Context, arg *IncrTagsByIdParams) error { - _, err := q.db.Exec(ctx, incrTagsById, arg.ModifiedOn, arg.Ids) - return err -} - -const insertTags = `-- name: InsertTags :one -INSERT INTO p_tag (user_id, tag, created_on, modified_on, quote_num) -VALUES ($1, $2, $3, $3, 1) +SET quote_num = quote_num+1, + modified_on = $1, + id_del = false +WHERE id IN ( + SELECT id + FROM p_tag + WHERE tag = ANY($2::varchar[]) +) RETURNING id, user_id, tag, quote_num ` -type InsertTagsParams struct { - UserID int64 - Tag string - CreatedOn int32 +type IncrTagsParams struct { + ModifiedOn int64 + Tags pgtype.Array[string] } -type InsertTagsRow struct { +type IncrTagsRow struct { ID int64 UserID int64 Tag string QuoteNum int64 } -func (q *Queries) InsertTags(ctx context.Context, arg *InsertTagsParams) (*InsertTagsRow, error) { - row := q.db.QueryRow(ctx, insertTags, arg.UserID, arg.Tag, arg.CreatedOn) - var i InsertTagsRow - err := row.Scan( - &i.ID, - &i.UserID, - &i.Tag, - &i.QuoteNum, - ) - return &i, err -} - -const newestTags = `-- name: NewestTags :many -SELECT id, user_id, tag, quote_num -FROM p_tag -WHERE is_del = false AND quote_num > 0 -ORDER BY id DESC -OFFSET $1 LIMIT $2 -` - -type NewestTagsParams struct { - Offset int32 - Limit int32 -} - -type NewestTagsRow struct { - ID int64 - UserID int64 - Tag string - QuoteNum int64 -} - -func (q *Queries) NewestTags(ctx context.Context, arg *NewestTagsParams) ([]*NewestTagsRow, error) { - rows, err := q.db.Query(ctx, newestTags, arg.Offset, arg.Limit) +func (q *Queries) IncrTags(ctx context.Context, arg *IncrTagsParams) ([]*IncrTagsRow, error) { + rows, err := q.db.Query(ctx, incrTags, arg.ModifiedOn, arg.Tags) if err != nil { return nil, err } defer rows.Close() - var items []*NewestTagsRow + var items []*IncrTagsRow for rows.Next() { - var i NewestTagsRow + var i IncrTagsRow if err := rows.Scan( &i.ID, &i.UserID, @@ -165,54 +127,54 @@ func (q *Queries) NewestTags(ctx context.Context, arg *NewestTagsParams) ([]*New return items, nil } -const tagsByIdA = `-- name: TagsByIdA :many -SELECT id -FROM p_tag -WHERE id = ANY($1::bigserial[]) AND is_del = false AND quote_num >= 0 +const insertTags = `-- name: InsertTags :one +INSERT INTO p_tag (user_id, tag, created_on, modified_on, quote_num) +VALUES ($1, $2, $3, $3, 1) +RETURNING id ` -func (q *Queries) TagsByIdA(ctx context.Context, ids pgtype.Array[int64]) ([]int64, error) { - rows, err := q.db.Query(ctx, tagsByIdA, ids) - if err != nil { - return nil, err - } - defer rows.Close() - var items []int64 - for rows.Next() { - var id int64 - if err := rows.Scan(&id); err != nil { - return nil, err - } - items = append(items, id) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil +type InsertTagsParams struct { + UserID int64 + Tag string + CreatedOn int64 } -const tagsByIdB = `-- name: TagsByIdB :many +func (q *Queries) InsertTags(ctx context.Context, arg *InsertTagsParams) (int64, error) { + row := q.db.QueryRow(ctx, insertTags, arg.UserID, arg.Tag, arg.CreatedOn) + var id int64 + err := row.Scan(&id) + return id, err +} + +const newestTags = `-- name: NewestTags :many SELECT id, user_id, tag, quote_num FROM p_tag -WHERE id = ANY($1::bigserial[]) +WHERE is_del = false AND quote_num > 0 +ORDER BY id DESC +OFFSET $1 LIMIT $2 ` -type TagsByIdBRow struct { +type NewestTagsParams struct { + Offset int32 + Limit int32 +} + +type NewestTagsRow struct { ID int64 UserID int64 Tag string QuoteNum int64 } -func (q *Queries) TagsByIdB(ctx context.Context, ids pgtype.Array[int64]) ([]*TagsByIdBRow, error) { - rows, err := q.db.Query(ctx, tagsByIdB, ids) +func (q *Queries) NewestTags(ctx context.Context, arg *NewestTagsParams) ([]*NewestTagsRow, error) { + rows, err := q.db.Query(ctx, newestTags, arg.Offset, arg.Limit) if err != nil { return nil, err } defer rows.Close() - var items []*TagsByIdBRow + var items []*NewestTagsRow for rows.Next() { - var i TagsByIdBRow + var i NewestTagsRow if err := rows.Scan( &i.ID, &i.UserID, @@ -308,41 +270,3 @@ func (q *Queries) TagsByKeywordB(ctx context.Context, tag string) ([]*TagsByKeyw } return items, nil } - -const tagsByName = `-- name: TagsByName :many -SELECT id, user_id, tag, quote_num -FROM p_tag -WHERE tag = ANY($1::varchar[]) AND is_del = false AND quote_num >= 0 -` - -type TagsByNameRow struct { - ID int64 - UserID int64 - Tag string - QuoteNum int64 -} - -func (q *Queries) TagsByName(ctx context.Context, tags pgtype.Array[string]) ([]*TagsByNameRow, error) { - rows, err := q.db.Query(ctx, tagsByName, tags) - if err != nil { - return nil, err - } - defer rows.Close() - var items []*TagsByNameRow - for rows.Next() { - var i TagsByNameRow - if err := rows.Scan( - &i.ID, - &i.UserID, - &i.Tag, - &i.QuoteNum, - ); err != nil { - return nil, err - } - items = append(items, &i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/internal/dao/slonik/topics.go b/internal/dao/slonik/topics.go index 28249f91..1e149b38 100644 --- a/internal/dao/slonik/topics.go +++ b/internal/dao/slonik/topics.go @@ -7,11 +7,12 @@ package slonik import ( "context" "strings" + "time" "github.com/jackc/pgx/v5" "github.com/rocboss/paopao-ce/internal/core" dbr "github.com/rocboss/paopao-ce/internal/dao/slonik/ce/postgres" - "github.com/rocboss/paopao-ce/pkg/debug" + "github.com/rocboss/paopao-ce/pkg/types" ) var ( @@ -22,24 +23,62 @@ type topicServant struct { *pgxServant } -func (s *topicServant) UpsertTags(userId int64, tags []string) ([]*core.Tag, error) { - // TODO - return nil, debug.ErrNotImplemented +// UpsertTags update/insert tags info. +// Assume tags slice is distinct elements. +func (s *topicServant) UpsertTags(userId int64, tags []string) (res []*core.Tag, err error) { + err = s.with(func(c context.Context, q dbr.Querier) error { + now := time.Now().Unix() + upTags, err := q.IncrTags(c, &dbr.IncrTagsParams{ + Tags: types.PgxArray(tags), + ModifiedOn: now, + }) + if err != nil { + return err + } + if len(upTags) > 0 { + for _, t := range upTags { + for i := 0; i < len(tags); { + if tags[i] == t.Tag { + latestIdx := len(tags) - 1 + tags[i] = tags[latestIdx] + tags = tags[:latestIdx] + break + } + } + res = append(res, &core.Tag{ + ID: t.ID, + UserID: t.UserID, + Tag: t.Tag, + QuoteNum: t.QuoteNum, + }) + } + } + for _, tag := range tags { + id, err := q.InsertTags(c, &dbr.InsertTagsParams{ + UserID: userId, + Tag: tag, + CreatedOn: now, + }) + if err != nil { + return err + } + res = append(res, &core.Tag{ + ID: id, + UserID: userId, + Tag: tag, + QuoteNum: 1, + }) + } + return nil + }) + return } func (s *topicServant) DecrTagsById(ids []int64) error { - // return s.with(func(c context.Context, q dbr.Querier) error { - // tagIds, err := q.TagsByIdA(c, pgtype.Array[int64](ids)) - // if err != nil { - // return err - // } - // err := q.DecrTagsById(c, &dbr.DecrTagsByIdParams{ModifiedOn: time.Now().Unix(), Ids: tagIds}) - // if err != nil { - // return err - // } - // return nil - // }) - return debug.ErrNotImplemented + return s.q.DecrTagsById(context.Background(), &dbr.DecrTagsByIdParams{ + Ids: types.PgxArray(ids), + ModifiedOn: time.Now().Unix(), + }) } func (s *topicServant) GetTags(category core.TagCategory, offset int, limit int) (res []*core.Tag, _ error) { diff --git a/pkg/types/types.go b/pkg/types/types.go index 830c56c6..32da1431 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -4,8 +4,21 @@ package types +import ( + "github.com/jackc/pgx/v5/pgtype" +) + // Empty empty alias type type Empty = struct{} // Fn empty argument func alias type type Fn = func() + +// PgxArray returns an object usable by pg drivers for passing a []T slice +// into a database as type T[]. +func PgxArray[T any](elements []T) pgtype.Array[T] { + return pgtype.Array[T]{ + Elements: elements, + Dims: []pgtype.ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + } +}