drone/app/store/database/label_value.go

326 lines
8.4 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
const (
labelValueColumns = `
label_value_label_id
,label_value_value
,label_value_color
,label_value_created
,label_value_updated
,label_value_created_by
,label_value_updated_by`
labelValueSelectBase = `SELECT label_value_id, ` + labelValueColumns + ` FROM label_values`
)
type labelValue struct {
ID int64 `db:"label_value_id"`
LabelID int64 `db:"label_value_label_id"`
Value string `db:"label_value_value"`
Color enum.LabelColor `db:"label_value_color"`
Created int64 `db:"label_value_created"`
Updated int64 `db:"label_value_updated"`
CreatedBy int64 `db:"label_value_created_by"`
UpdatedBy int64 `db:"label_value_updated_by"`
}
type labelValueInfo struct {
ValueID null.Int `db:"label_value_id"`
LabelID null.Int `db:"label_value_label_id"`
Value null.String `db:"label_value_value"`
ValueColor null.String `db:"label_value_color"`
}
type labelValueStore struct {
db *sqlx.DB
}
func NewLabelValueStore(
db *sqlx.DB,
) store.LabelValueStore {
return &labelValueStore{
db: db,
}
}
var _ store.LabelValueStore = (*labelValueStore)(nil)
func (s *labelValueStore) Define(ctx context.Context, lblVal *types.LabelValue) error {
const sqlQuery = `
INSERT INTO label_values (` + labelValueColumns + `)` + `
values (
:label_value_label_id
,:label_value_value
,:label_value_color
,:label_value_created
,:label_value_updated
,:label_value_created_by
,:label_value_updated_by
)
RETURNING label_value_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabelValue(lblVal))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if err = db.QueryRowContext(ctx, query, args...).Scan(&lblVal.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to create label value")
}
return nil
}
func (s *labelValueStore) Update(ctx context.Context, lblVal *types.LabelValue) error {
const sqlQuery = `
UPDATE label_values SET
label_value_value = :label_value_value
,label_value_color = :label_value_color
,label_value_updated = :label_value_updated
,label_value_updated_by = :label_value_updated_by
WHERE label_value_id = :label_value_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabelValue(lblVal))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if _, err := db.ExecContext(ctx, query, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to update label value")
}
return nil
}
func (s *labelValueStore) Delete(
ctx context.Context,
labelID int64,
value string,
) error {
const sqlQuery = `
DELETE FROM label_values
WHERE label_value_label_id = $1 AND LOWER(label_value_value) = LOWER($2)`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sqlQuery, labelID, value); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
}
return nil
}
func (s *labelValueStore) DeleteMany(
ctx context.Context,
labelID int64,
values []string,
) error {
stmt := database.Builder.
Delete("label_values").
Where("label_value_label_id = ?", labelID).
Where(squirrel.Eq{"label_value_value": values})
sql, args, err := stmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sql, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
}
return nil
}
// List returns a list of label values for a specified label.
func (s *labelValueStore) List(
ctx context.Context,
labelID int64,
opts *types.ListQueryFilter,
) ([]*types.LabelValue, error) {
stmt := database.Builder.
Select(`label_value_id, ` + labelValueColumns).
From("label_values")
stmt = stmt.Where("label_value_label_id = ?", labelID)
stmt = stmt.Limit(database.Limit(opts.Size))
stmt = stmt.Offset(database.Offset(opts.Page, opts.Size))
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*labelValue
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Fail to list labels")
}
return mapSliceLabelValue(dst), nil
}
func (s *labelValueStore) ListInfosByLabelIDs(
ctx context.Context,
labelIDs []int64,
) (map[int64][]*types.LabelValueInfo, error) {
stmt := database.Builder.
Select(`
label_value_id
,label_value_label_id
,label_value_value
,label_value_color
`).
From("label_values").
Where(squirrel.Eq{"label_value_label_id": labelIDs}).
OrderBy("label_value_value")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*labelValueInfo
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Fail to list labels")
}
valueInfos := mapLabelValuInfos(dst)
labelValueMap := make(map[int64][]*types.LabelValueInfo)
for _, info := range valueInfos {
labelValueMap[*info.LabelID] = append(labelValueMap[*info.LabelID], info)
}
return labelValueMap, nil
}
func (s *labelValueStore) FindByLabelID(
ctx context.Context,
labelID int64,
value string,
) (*types.LabelValue, error) {
const sqlQuery = labelValueSelectBase + `
WHERE label_value_label_id = $1 AND LOWER(label_value_value) = LOWER($2)`
db := dbtx.GetAccessor(ctx, s.db)
var dst labelValue
if err := db.GetContext(ctx, &dst, sqlQuery, labelID, value); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabelValue(&dst), nil
}
func (s *labelValueStore) FindByID(ctx context.Context, id int64) (*types.LabelValue, error) {
const sqlQuery = labelValueSelectBase + `
WHERE label_value_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
var dst labelValue
if err := db.GetContext(ctx, &dst, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabelValue(&dst), nil
}
func mapLabelValue(lbl *labelValue) *types.LabelValue {
return &types.LabelValue{
ID: lbl.ID,
LabelID: lbl.LabelID,
Value: lbl.Value,
Color: lbl.Color,
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}
func mapSliceLabelValue(dbLabelValues []*labelValue) []*types.LabelValue {
result := make([]*types.LabelValue, len(dbLabelValues))
for i, lbl := range dbLabelValues {
result[i] = mapLabelValue(lbl)
}
return result
}
func mapInternalLabelValue(lblVal *types.LabelValue) *labelValue {
return &labelValue{
ID: lblVal.ID,
LabelID: lblVal.LabelID,
Value: lblVal.Value,
Color: lblVal.Color,
Created: lblVal.Created,
Updated: lblVal.Updated,
CreatedBy: lblVal.CreatedBy,
UpdatedBy: lblVal.UpdatedBy,
}
}
func mapLabeValuelInfo(internal *labelValueInfo) *types.LabelValueInfo {
if !internal.ValueID.Valid {
return nil
}
return &types.LabelValueInfo{
ID: internal.ValueID.Ptr(),
LabelID: internal.LabelID.Ptr(),
Value: internal.Value.Ptr(),
Color: internal.ValueColor.Ptr(),
}
}
func mapLabelValuInfos(
dbLabels []*labelValueInfo,
) []*types.LabelValueInfo {
result := make([]*types.LabelValueInfo, len(dbLabels))
for i, lbl := range dbLabels {
result[i] = mapLabeValuelInfo(lbl)
}
return result
}