mirror of https://github.com/harness/drone.git
465 lines
12 KiB
Go
465 lines
12 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 (
|
|
labelColumns = `
|
|
label_space_id
|
|
,label_repo_id
|
|
,label_scope
|
|
,label_key
|
|
,label_description
|
|
,label_type
|
|
,label_color
|
|
,label_created
|
|
,label_updated
|
|
,label_created_by
|
|
,label_updated_by`
|
|
|
|
labelSelectBase = `SELECT label_id, ` + labelColumns + ` FROM labels`
|
|
)
|
|
|
|
type label struct {
|
|
ID int64 `db:"label_id"`
|
|
SpaceID null.Int `db:"label_space_id"`
|
|
RepoID null.Int `db:"label_repo_id"`
|
|
Scope int64 `db:"label_scope"`
|
|
Key string `db:"label_key"`
|
|
Description string `db:"label_description"`
|
|
Type enum.LabelType `db:"label_type"`
|
|
Color enum.LabelColor `db:"label_color"`
|
|
ValueCount int64 `db:"label_value_count"`
|
|
Created int64 `db:"label_created"`
|
|
Updated int64 `db:"label_updated"`
|
|
CreatedBy int64 `db:"label_created_by"`
|
|
UpdatedBy int64 `db:"label_updated_by"`
|
|
}
|
|
|
|
type labelInfo struct {
|
|
LabelID int64 `db:"label_id"`
|
|
SpaceID null.Int `db:"label_space_id"`
|
|
RepoID null.Int `db:"label_repo_id"`
|
|
Scope int64 `db:"label_scope"`
|
|
Key string `db:"label_key"`
|
|
Type enum.LabelType `db:"label_type"`
|
|
LabelColor enum.LabelColor `db:"label_color"`
|
|
}
|
|
|
|
type labelStore struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
func NewLabelStore(
|
|
db *sqlx.DB,
|
|
) store.LabelStore {
|
|
return &labelStore{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
var _ store.LabelStore = (*labelStore)(nil)
|
|
|
|
func (s *labelStore) Define(ctx context.Context, lbl *types.Label) error {
|
|
const sqlQuery = `
|
|
INSERT INTO labels (` + labelColumns + `)` + `
|
|
values (
|
|
:label_space_id
|
|
,:label_repo_id
|
|
,:label_scope
|
|
,:label_key
|
|
,:label_description
|
|
,:label_type
|
|
,:label_color
|
|
,:label_created
|
|
,:label_updated
|
|
,:label_created_by
|
|
,:label_updated_by
|
|
)
|
|
RETURNING label_id`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
query, args, err := db.BindNamed(sqlQuery, mapInternalLabel(lbl))
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
|
|
}
|
|
|
|
if err = db.QueryRowContext(ctx, query, args...).Scan(&lbl.ID); err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "Failed to create label")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *labelStore) Update(ctx context.Context, lbl *types.Label) error {
|
|
const sqlQuery = `
|
|
UPDATE labels SET
|
|
label_key = :label_key
|
|
,label_description = :label_description
|
|
,label_type = :label_type
|
|
,label_color = :label_color
|
|
,label_updated = :label_updated
|
|
,label_updated_by = :label_updated_by
|
|
WHERE label_id = :label_id`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
query, args, err := db.BindNamed(sqlQuery, mapInternalLabel(lbl))
|
|
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")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *labelStore) IncrementValueCount(
|
|
ctx context.Context,
|
|
labelID int64,
|
|
increment int,
|
|
) (int64, error) {
|
|
const sqlQuery = `
|
|
UPDATE labels
|
|
SET label_value_count = label_value_count + $1
|
|
WHERE label_id = $2
|
|
RETURNING label_value_count
|
|
`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
var valueCount int64
|
|
if err := db.QueryRowContext(ctx, sqlQuery, increment, labelID).Scan(&valueCount); err != nil {
|
|
return 0, database.ProcessSQLErrorf(ctx, err, "Failed to increment label_value_count")
|
|
}
|
|
|
|
return valueCount, nil
|
|
}
|
|
|
|
func (s *labelStore) Find(
|
|
ctx context.Context,
|
|
spaceID, repoID *int64,
|
|
key string,
|
|
) (*types.Label, error) {
|
|
const sqlQuery = labelSelectBase + `
|
|
WHERE (label_space_id = $1 OR label_repo_id = $2) AND LOWER(label_key) = LOWER($3)`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
var dst label
|
|
if err := db.GetContext(ctx, &dst, sqlQuery, spaceID, repoID, key); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
|
|
}
|
|
|
|
return mapLabel(&dst), nil
|
|
}
|
|
|
|
func (s *labelStore) FindByID(ctx context.Context, id int64) (*types.Label, error) {
|
|
const sqlQuery = labelSelectBase + `
|
|
WHERE label_id = $1`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
var dst label
|
|
if err := db.GetContext(ctx, &dst, sqlQuery, id); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
|
|
}
|
|
|
|
return mapLabel(&dst), nil
|
|
}
|
|
|
|
func (s *labelStore) Delete(ctx context.Context, spaceID, repoID *int64, name string) error {
|
|
const sqlQuery = `
|
|
DELETE FROM labels
|
|
WHERE (label_space_id = $1 OR label_repo_id = $2) AND LOWER(label_key) = LOWER($3)`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
if _, err := db.ExecContext(ctx, sqlQuery, spaceID, repoID, name); err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// List returns a list of pull requests for a repo or space.
|
|
func (s *labelStore) List(
|
|
ctx context.Context,
|
|
spaceID, repoID *int64,
|
|
filter *types.LabelFilter,
|
|
) ([]*types.Label, error) {
|
|
stmt := database.Builder.
|
|
Select(`label_id, ` + labelColumns + `, label_value_count`).
|
|
From("labels").
|
|
OrderBy("label_key")
|
|
|
|
stmt = stmt.Where("(label_space_id = ? OR label_repo_id = ?)", spaceID, repoID)
|
|
stmt = stmt.Limit(database.Limit(filter.Size))
|
|
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
|
|
if filter.Query != "" {
|
|
stmt = stmt.Where(
|
|
"LOWER(label_key) LIKE '%' || LOWER(?) || '%'", filter.Query)
|
|
}
|
|
|
|
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 []*label
|
|
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels")
|
|
}
|
|
|
|
return mapSliceLabel(dst), nil
|
|
}
|
|
|
|
func (s *labelStore) ListInScopes(
|
|
ctx context.Context,
|
|
repoID int64,
|
|
spaceIDs []int64,
|
|
filter *types.LabelFilter,
|
|
) ([]*types.Label, error) {
|
|
stmt := database.Builder.
|
|
Select(`label_id, ` + labelColumns + `, label_value_count`).
|
|
From("labels")
|
|
|
|
stmt = stmt.Where(squirrel.Or{
|
|
squirrel.Eq{"label_space_id": spaceIDs},
|
|
squirrel.Eq{"label_repo_id": repoID},
|
|
}).
|
|
OrderBy("label_key").
|
|
OrderBy("label_scope")
|
|
|
|
stmt = stmt.Limit(database.Limit(filter.Size))
|
|
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
|
|
if filter.Query != "" {
|
|
stmt = stmt.Where(
|
|
"LOWER(label_key) LIKE '%' || LOWER(?) || '%'", filter.Query)
|
|
}
|
|
|
|
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 []*label
|
|
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels in hierarchy")
|
|
}
|
|
|
|
return mapSliceLabel(dst), nil
|
|
}
|
|
|
|
func (s *labelStore) ListInfosInScopes(
|
|
ctx context.Context,
|
|
repoID int64,
|
|
spaceIDs []int64,
|
|
filter *types.AssignableLabelFilter,
|
|
) ([]*types.LabelInfo, error) {
|
|
stmt := database.Builder.
|
|
Select(`
|
|
label_id
|
|
,label_space_id
|
|
,label_repo_id
|
|
,label_scope
|
|
,label_key
|
|
,label_type
|
|
,label_color`).
|
|
From("labels").
|
|
Where(squirrel.Or{
|
|
squirrel.Eq{"label_space_id": spaceIDs},
|
|
squirrel.Eq{"label_repo_id": repoID},
|
|
}).
|
|
OrderBy("label_key").
|
|
OrderBy("label_scope")
|
|
|
|
stmt = stmt.Limit(database.Limit(filter.Size))
|
|
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
|
|
if filter.Query != "" {
|
|
stmt = stmt.Where(
|
|
"LOWER(label_key) LIKE '%' || LOWER(?) || '%'", filter.Query)
|
|
}
|
|
|
|
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 []*labelInfo
|
|
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels")
|
|
}
|
|
|
|
return mapLabelInfos(dst), nil
|
|
}
|
|
|
|
func (s *labelStore) CountInSpace(
|
|
ctx context.Context,
|
|
spaceID int64,
|
|
filter *types.LabelFilter,
|
|
) (int64, error) {
|
|
const sqlQuery = `SELECT COUNT(*) FROM labels WHERE label_space_id = $1`
|
|
|
|
return s.count(ctx, sqlQuery, spaceID, filter)
|
|
}
|
|
|
|
func (s *labelStore) CountInRepo(
|
|
ctx context.Context,
|
|
repoID int64,
|
|
filter *types.LabelFilter,
|
|
) (int64, error) {
|
|
const sqlQuery = `SELECT COUNT(*) FROM labels WHERE label_repo_id = $1`
|
|
|
|
return s.count(ctx, sqlQuery, repoID, filter)
|
|
}
|
|
|
|
func (s labelStore) count(
|
|
ctx context.Context,
|
|
sqlQuery string,
|
|
scopeID int64,
|
|
filter *types.LabelFilter,
|
|
) (int64, error) {
|
|
sqlQuery += `
|
|
AND LOWER(label_key) LIKE '%' || LOWER($2) || '%'`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
var count int64
|
|
if err := db.QueryRowContext(ctx, sqlQuery, scopeID, filter.Query).Scan(&count); err != nil {
|
|
return 0, database.ProcessSQLErrorf(ctx, err, "Failed to count labels")
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (s *labelStore) CountInScopes(
|
|
ctx context.Context,
|
|
repoID int64,
|
|
spaceIDs []int64,
|
|
filter *types.LabelFilter,
|
|
) (int64, error) {
|
|
stmt := database.Builder.Select("COUNT(*)").
|
|
From("labels").
|
|
Where(squirrel.Or{
|
|
squirrel.Eq{"label_space_id": spaceIDs},
|
|
squirrel.Eq{"label_repo_id": repoID},
|
|
}).
|
|
Where("LOWER(label_key) LIKE '%' || LOWER(?) || '%'", filter.Query)
|
|
|
|
sql, args, err := stmt.ToSql()
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "Failed to convert query to sql")
|
|
}
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
var count int64
|
|
if err = db.QueryRowContext(ctx, sql, args...).Scan(&count); err != nil {
|
|
return 0, database.ProcessSQLErrorf(ctx, err, "Failed to count labels in scopes")
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func mapLabel(lbl *label) *types.Label {
|
|
return &types.Label{
|
|
ID: lbl.ID,
|
|
SpaceID: lbl.SpaceID.Ptr(),
|
|
RepoID: lbl.RepoID.Ptr(),
|
|
Scope: lbl.Scope,
|
|
Key: lbl.Key,
|
|
Type: lbl.Type,
|
|
Description: lbl.Description,
|
|
ValueCount: lbl.ValueCount,
|
|
Color: lbl.Color,
|
|
Created: lbl.Created,
|
|
Updated: lbl.Updated,
|
|
CreatedBy: lbl.CreatedBy,
|
|
UpdatedBy: lbl.UpdatedBy,
|
|
}
|
|
}
|
|
|
|
func mapSliceLabel(dbLabels []*label) []*types.Label {
|
|
result := make([]*types.Label, len(dbLabels))
|
|
|
|
for i, lbl := range dbLabels {
|
|
result[i] = mapLabel(lbl)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func mapInternalLabel(lbl *types.Label) *label {
|
|
return &label{
|
|
ID: lbl.ID,
|
|
SpaceID: null.IntFromPtr(lbl.SpaceID),
|
|
RepoID: null.IntFromPtr(lbl.RepoID),
|
|
Scope: lbl.Scope,
|
|
Key: lbl.Key,
|
|
Description: lbl.Description,
|
|
Type: lbl.Type,
|
|
Color: lbl.Color,
|
|
Created: lbl.Created,
|
|
Updated: lbl.Updated,
|
|
CreatedBy: lbl.CreatedBy,
|
|
UpdatedBy: lbl.UpdatedBy,
|
|
}
|
|
}
|
|
|
|
func mapLabelInfo(internal *labelInfo) *types.LabelInfo {
|
|
return &types.LabelInfo{
|
|
ID: internal.LabelID,
|
|
RepoID: internal.RepoID.Ptr(),
|
|
SpaceID: internal.SpaceID.Ptr(),
|
|
Scope: internal.Scope,
|
|
Key: internal.Key,
|
|
Type: internal.Type,
|
|
Color: internal.LabelColor,
|
|
}
|
|
}
|
|
|
|
func mapLabelInfos(
|
|
dbLabels []*labelInfo,
|
|
) []*types.LabelInfo {
|
|
result := make([]*types.LabelInfo, len(dbLabels))
|
|
|
|
for i, lbl := range dbLabels {
|
|
result[i] = mapLabelInfo(lbl)
|
|
}
|
|
|
|
return result
|
|
}
|