mirror of https://github.com/harness/drone.git
225 lines
6.7 KiB
Go
225 lines
6.7 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/internal/paths"
|
|
"github.com/harness/gitness/internal/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"`
|
|
// UID is the original uid that was provided
|
|
UID string `db:"space_path_uid"`
|
|
// UIDUnique is a transformed version of UID which is used to ensure uniqueness guarantees
|
|
UIDUnique 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
|
|
)`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
|
|
query, arg, err := db.BindNamed(sqlQuery, s.mapToInternalSpacePathSegment(segment))
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(err, "Failed to bind path segment object")
|
|
}
|
|
|
|
if _, err = db.ExecContext(ctx, query, arg...); err != nil {
|
|
return database.ProcessSQLErrorf(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(err, "Failed to find primary segment for %d", nextSpaceID.Int64)
|
|
}
|
|
|
|
path = paths.Concatinate(dst.UID, 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)
|
|
|
|
segmentUIDs := paths.Segments(path)
|
|
if len(segmentUIDs) == 0 {
|
|
return nil, fmt.Errorf("path with no segments was passed '%s'", path)
|
|
}
|
|
|
|
var err error
|
|
var parentID int64
|
|
originalPath := ""
|
|
isPrimary := true
|
|
for i, segmentUID := range segmentUIDs {
|
|
uniqueSegmentUID := s.spacePathTransformation(segmentUID, i == 0)
|
|
|
|
if parentID == 0 {
|
|
err = db.GetContext(ctx, segment, sqlQueryNoParent, uniqueSegmentUID)
|
|
} else {
|
|
err = db.GetContext(ctx, segment, sqlQueryParent, uniqueSegmentUID, parentID)
|
|
}
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(err, "Failed to find segment for '%s' in '%s'", uniqueSegmentUID, path)
|
|
}
|
|
|
|
originalPath = paths.Concatinate(originalPath, segment.UID)
|
|
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(err, "the delete query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SpacePathStore) mapToInternalSpacePathSegment(p *types.SpacePathSegment) *spacePathSegment {
|
|
res := &spacePathSegment{
|
|
ID: p.ID,
|
|
UID: p.UID,
|
|
UIDUnique: s.spacePathTransformation(p.UID, 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
|
|
}
|