mirror of https://github.com/harness/drone.git
259 lines
7.8 KiB
Go
259 lines
7.8 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"
|
|
"fmt"
|
|
|
|
"github.com/harness/gitness/app/paths"
|
|
"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/guregu/null"
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
var _ store.SpacePathStore = (*SpacePathStore)(nil)
|
|
|
|
// NewSpacePathStore returns a new SpacePathStore.
|
|
func NewSpacePathStore(
|
|
db *sqlx.DB,
|
|
pathTransformation store.SpacePathTransformation,
|
|
) *SpacePathStore {
|
|
return &SpacePathStore{
|
|
db: db,
|
|
spacePathTransformation: pathTransformation,
|
|
}
|
|
}
|
|
|
|
// SpacePathStore implements a store.SpacePathStore backed by a relational database.
|
|
type SpacePathStore struct {
|
|
db *sqlx.DB
|
|
spacePathTransformation store.SpacePathTransformation
|
|
}
|
|
|
|
// spacePathSegment is an internal representation of a segment of a space path.
|
|
type spacePathSegment struct {
|
|
ID int64 `db:"space_path_id"`
|
|
// Identifier is the original identifier that was provided
|
|
Identifier string `db:"space_path_uid"`
|
|
// IdentifierUnique is a transformed version of Identifier which is used to ensure uniqueness guarantees
|
|
IdentifierUnique string `db:"space_path_uid_unique"`
|
|
// IsPrimary indicates whether the path is the primary path of the space
|
|
// IMPORTANT: to allow DB enforcement of at most one primary path per repo/space
|
|
// we have a unique index on spaceID + IsPrimary and set IsPrimary to true
|
|
// for primary paths and to nil for non-primary paths.
|
|
IsPrimary null.Bool `db:"space_path_is_primary"`
|
|
ParentID null.Int `db:"space_path_parent_id"`
|
|
SpaceID int64 `db:"space_path_space_id"`
|
|
CreatedBy int64 `db:"space_path_created_by"`
|
|
Created int64 `db:"space_path_created"`
|
|
Updated int64 `db:"space_path_updated"`
|
|
}
|
|
|
|
const (
|
|
spacePathColumns = `
|
|
space_path_uid
|
|
,space_path_uid_unique
|
|
,space_path_is_primary
|
|
,space_path_parent_id
|
|
,space_path_space_id
|
|
,space_path_created_by
|
|
,space_path_created
|
|
,space_path_updated`
|
|
|
|
spacePathSelectBase = `
|
|
SELECT` + spacePathColumns + `
|
|
FROM space_paths`
|
|
)
|
|
|
|
// InsertSegment inserts a space path segment to the table - returns the full path.
|
|
func (s *SpacePathStore) InsertSegment(ctx context.Context, segment *types.SpacePathSegment) error {
|
|
const sqlQuery = `
|
|
INSERT INTO space_paths (
|
|
space_path_uid
|
|
,space_path_uid_unique
|
|
,space_path_is_primary
|
|
,space_path_parent_id
|
|
,space_path_space_id
|
|
,space_path_created_by
|
|
,space_path_created
|
|
,space_path_updated
|
|
) values (
|
|
:space_path_uid
|
|
,:space_path_uid_unique
|
|
,:space_path_is_primary
|
|
,:space_path_parent_id
|
|
,:space_path_space_id
|
|
,:space_path_created_by
|
|
,:space_path_created
|
|
,:space_path_updated
|
|
) RETURNING space_path_id`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
query, arg, err := db.BindNamed(sqlQuery, s.mapToInternalSpacePathSegment(segment))
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "Failed to bind path segment object")
|
|
}
|
|
|
|
if err = db.QueryRowContext(ctx, query, arg...).Scan(&segment.ID); err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "Insert query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SpacePathStore) FindPrimaryBySpaceID(ctx context.Context, spaceID int64) (*types.SpacePath, error) {
|
|
sqlQuery := spacePathSelectBase + `
|
|
where space_path_space_id = $1 AND space_path_is_primary = TRUE`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
dst := new(spacePathSegment)
|
|
|
|
path := ""
|
|
nextSpaceID := null.IntFrom(spaceID)
|
|
|
|
for nextSpaceID.Valid {
|
|
err := db.GetContext(ctx, dst, sqlQuery, nextSpaceID.Int64)
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find primary segment for %d", nextSpaceID.Int64)
|
|
}
|
|
|
|
path = paths.Concatenate(dst.Identifier, path)
|
|
nextSpaceID = dst.ParentID
|
|
}
|
|
|
|
return &types.SpacePath{
|
|
SpaceID: spaceID,
|
|
Value: path,
|
|
IsPrimary: true,
|
|
}, nil
|
|
}
|
|
func (s *SpacePathStore) FindByPath(ctx context.Context, path string) (*types.SpacePath, error) {
|
|
const sqlQueryNoParent = spacePathSelectBase + ` WHERE space_path_uid_unique = $1 AND space_path_parent_id IS NULL`
|
|
const sqlQueryParent = spacePathSelectBase + ` WHERE space_path_uid_unique = $1 AND space_path_parent_id = $2`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
segment := new(spacePathSegment)
|
|
|
|
segmentIdentifiers := paths.Segments(path)
|
|
if len(segmentIdentifiers) == 0 {
|
|
return nil, fmt.Errorf("path with no segments was passed '%s'", path)
|
|
}
|
|
|
|
var err error
|
|
var parentID int64
|
|
originalPath := ""
|
|
isPrimary := true
|
|
for i, segmentIdentifier := range segmentIdentifiers {
|
|
uniqueSegmentIdentifier := s.spacePathTransformation(segmentIdentifier, i == 0)
|
|
|
|
if parentID == 0 {
|
|
err = db.GetContext(ctx, segment, sqlQueryNoParent, uniqueSegmentIdentifier)
|
|
} else {
|
|
err = db.GetContext(ctx, segment, sqlQueryParent, uniqueSegmentIdentifier, parentID)
|
|
}
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(
|
|
ctx,
|
|
err,
|
|
"Failed to find segment for '%s' in '%s'",
|
|
uniqueSegmentIdentifier,
|
|
path,
|
|
)
|
|
}
|
|
|
|
originalPath = paths.Concatenate(originalPath, segment.Identifier)
|
|
parentID = segment.SpaceID
|
|
isPrimary = isPrimary && segment.IsPrimary.ValueOrZero()
|
|
}
|
|
|
|
return &types.SpacePath{
|
|
Value: originalPath,
|
|
IsPrimary: isPrimary,
|
|
SpaceID: segment.SpaceID,
|
|
}, nil
|
|
}
|
|
|
|
// DeletePrimarySegment deletes the primary segment of the space.
|
|
func (s *SpacePathStore) DeletePrimarySegment(ctx context.Context, spaceID int64) error {
|
|
const sqlQuery = `
|
|
DELETE FROM space_paths
|
|
WHERE space_path_space_id = $1 AND space_path_is_primary = TRUE`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
if _, err := db.ExecContext(ctx, sqlQuery, spaceID); err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "the delete query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeletePathsAndDescendandPaths deletes all space paths reachable from spaceID including itself.
|
|
func (s *SpacePathStore) DeletePathsAndDescendandPaths(ctx context.Context, spaceID int64) error {
|
|
const sqlQuery = `WITH RECURSIVE DescendantPaths AS (
|
|
SELECT space_path_id, space_path_space_id, space_path_parent_id
|
|
FROM space_paths
|
|
WHERE space_path_space_id = $1
|
|
|
|
UNION
|
|
|
|
SELECT sp.space_path_id, sp.space_path_space_id, sp.space_path_parent_id
|
|
FROM space_paths sp
|
|
JOIN DescendantPaths dp ON sp.space_path_parent_id = dp.space_path_space_id
|
|
)
|
|
DELETE FROM space_paths
|
|
WHERE space_path_id IN (SELECT space_path_id FROM DescendantPaths);`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
if _, err := db.ExecContext(ctx, sqlQuery, spaceID); err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "the delete query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SpacePathStore) mapToInternalSpacePathSegment(p *types.SpacePathSegment) *spacePathSegment {
|
|
res := &spacePathSegment{
|
|
ID: p.ID,
|
|
Identifier: p.Identifier,
|
|
IdentifierUnique: s.spacePathTransformation(p.Identifier, p.ParentID == 0),
|
|
SpaceID: p.SpaceID,
|
|
Created: p.Created,
|
|
CreatedBy: p.CreatedBy,
|
|
Updated: p.Updated,
|
|
|
|
// ParentID: is set below
|
|
// IsPrimary: is set below
|
|
}
|
|
|
|
// only set IsPrimary to a value if it's true (Unique Index doesn't allow multiple false, hence keep it nil)
|
|
if p.IsPrimary {
|
|
res.IsPrimary = null.BoolFrom(true)
|
|
}
|
|
|
|
if p.ParentID > 0 {
|
|
res.ParentID = null.IntFrom(p.ParentID)
|
|
}
|
|
|
|
return res
|
|
}
|