github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/runtime_context/repository.go (about)

     1  package runtimectx
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/pagination"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    12  
    13  	"github.com/google/uuid"
    14  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    15  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    19  	"github.com/kyma-incubator/compass/components/director/internal/model"
    20  )
    21  
    22  const runtimeContextsTable string = `public.runtime_contexts`
    23  
    24  var (
    25  	runtimeContextColumns = []string{"id", "runtime_id", "key", "value"}
    26  	updatableColumns      = []string{"key", "value"}
    27  	orderByColumns        = repo.OrderByParams{repo.NewAscOrderBy("runtime_id"), repo.NewAscOrderBy("id")}
    28  )
    29  
    30  type pgRepository struct {
    31  	existQuerier       repo.ExistQuerier
    32  	singleGetter       repo.SingleGetter
    33  	singleGetterGlobal repo.SingleGetterGlobal
    34  	deleter            repo.Deleter
    35  	pageableQuerier    repo.PageableQuerier
    36  	unionLister        repo.UnionLister
    37  	lister             repo.Lister
    38  	creator            repo.Creator
    39  	updater            repo.Updater
    40  	conv               entityConverter
    41  }
    42  
    43  //go:generate mockery --exported --name=entityConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    44  type entityConverter interface {
    45  	ToEntity(in *model.RuntimeContext) *RuntimeContext
    46  	FromEntity(entity *RuntimeContext) *model.RuntimeContext
    47  }
    48  
    49  // NewRepository missing godoc
    50  func NewRepository(conv entityConverter) *pgRepository {
    51  	return &pgRepository{
    52  		existQuerier:       repo.NewExistQuerier(runtimeContextsTable),
    53  		singleGetter:       repo.NewSingleGetter(runtimeContextsTable, runtimeContextColumns),
    54  		singleGetterGlobal: repo.NewSingleGetterGlobal(resource.RuntimeContext, runtimeContextsTable, runtimeContextColumns),
    55  		deleter:            repo.NewDeleter(runtimeContextsTable),
    56  		pageableQuerier:    repo.NewPageableQuerier(runtimeContextsTable, runtimeContextColumns),
    57  		unionLister:        repo.NewUnionLister(runtimeContextsTable, runtimeContextColumns),
    58  		lister:             repo.NewLister(runtimeContextsTable, runtimeContextColumns),
    59  		creator:            repo.NewCreator(runtimeContextsTable, runtimeContextColumns),
    60  		updater:            repo.NewUpdater(runtimeContextsTable, updatableColumns, []string{"id"}),
    61  		conv:               conv,
    62  	}
    63  }
    64  
    65  // Exists returns true if a RuntimeContext with the provided `id` exists in the database and is visible for `tenant`
    66  func (r *pgRepository) Exists(ctx context.Context, tenant, id string) (bool, error) {
    67  	return r.existQuerier.Exists(ctx, resource.RuntimeContext, tenant, repo.Conditions{repo.NewEqualCondition("id", id)})
    68  }
    69  
    70  // ExistsByRuntimeID returns true if a RuntimeContext with the provided runtime ID exists in the database and is visible for `tenant`
    71  func (r *pgRepository) ExistsByRuntimeID(ctx context.Context, tenant, rtmID string) (bool, error) {
    72  	return r.existQuerier.Exists(ctx, resource.RuntimeContext, tenant, repo.Conditions{repo.NewEqualCondition("runtime_id", rtmID)})
    73  }
    74  
    75  // Delete deletes the RuntimeContext with the provided `id` from the database if `tenant` has the appropriate access to it
    76  func (r *pgRepository) Delete(ctx context.Context, tenant string, id string) error {
    77  	return r.deleter.DeleteOne(ctx, resource.RuntimeContext, tenant, repo.Conditions{repo.NewEqualCondition("id", id)})
    78  }
    79  
    80  // GetByID retrieves the RuntimeContext with the provided `id` from the database if it exists and is visible for `tenant`
    81  func (r *pgRepository) GetByID(ctx context.Context, tenant, id string) (*model.RuntimeContext, error) {
    82  	var runtimeCtxEnt RuntimeContext
    83  	if err := r.singleGetter.Get(ctx, resource.RuntimeContext, tenant, repo.Conditions{repo.NewEqualCondition("id", id)}, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	return r.conv.FromEntity(&runtimeCtxEnt), nil
    88  }
    89  
    90  // GetByRuntimeID retrieves the RuntimeContext by provided runtimeID from the database within the given tenant
    91  func (r *pgRepository) GetByRuntimeID(ctx context.Context, tenant, runtimeID string) (*model.RuntimeContext, error) {
    92  	var runtimeCtxEnt RuntimeContext
    93  	if err := r.singleGetter.Get(ctx, resource.RuntimeContext, tenant, repo.Conditions{repo.NewEqualCondition("runtime_id", runtimeID)}, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	return r.conv.FromEntity(&runtimeCtxEnt), nil
    98  }
    99  
   100  // GetForRuntime retrieves RuntimeContext with the provided `id` associated to Runtime with id `runtimeID` from the database if it exists and is visible for `tenant`
   101  func (r *pgRepository) GetForRuntime(ctx context.Context, tenant, id, runtimeID string) (*model.RuntimeContext, error) {
   102  	var runtimeCtxEnt RuntimeContext
   103  
   104  	conditions := repo.Conditions{
   105  		repo.NewEqualCondition("id", id),
   106  		repo.NewEqualCondition("runtime_id", runtimeID),
   107  	}
   108  
   109  	if err := r.singleGetter.Get(ctx, resource.RuntimeContext, tenant, conditions, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return r.conv.FromEntity(&runtimeCtxEnt), nil
   114  }
   115  
   116  // GetByFiltersAndID retrieves RuntimeContext with the provided `id` matching the provided filters from the database if it exists and is visible for `tenant`
   117  func (r *pgRepository) GetByFiltersAndID(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (*model.RuntimeContext, error) {
   118  	tenantID, err := uuid.Parse(tenant)
   119  	if err != nil {
   120  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   121  	}
   122  
   123  	additionalConditions := repo.Conditions{repo.NewEqualCondition("id", id)}
   124  
   125  	filterSubquery, args, err := label.FilterQuery(model.RuntimeContextLabelableObject, label.IntersectSet, tenantID, filter)
   126  	if err != nil {
   127  		return nil, errors.Wrap(err, "while building filter query")
   128  	}
   129  	if filterSubquery != "" {
   130  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   131  	}
   132  
   133  	var runtimeCtxEnt RuntimeContext
   134  	if err := r.singleGetter.Get(ctx, resource.RuntimeContext, tenant, additionalConditions, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return r.conv.FromEntity(&runtimeCtxEnt), nil
   139  }
   140  
   141  // GetByFiltersGlobal retrieves RuntimeContext matching the provided filters from the database if it exists
   142  func (r *pgRepository) GetByFiltersGlobal(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.RuntimeContext, error) {
   143  	filterSubquery, args, err := label.FilterQueryGlobal(model.RuntimeContextLabelableObject, label.IntersectSet, filter)
   144  	if err != nil {
   145  		return nil, errors.Wrap(err, "while building filter query")
   146  	}
   147  
   148  	var additionalConditions repo.Conditions
   149  	if filterSubquery != "" {
   150  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   151  	}
   152  
   153  	var runtimeCtxEnt RuntimeContext
   154  	if err := r.singleGetterGlobal.GetGlobal(ctx, additionalConditions, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	return r.conv.FromEntity(&runtimeCtxEnt), nil
   159  }
   160  
   161  // GetGlobalByID retrieves the runtime context matching ID `id` globally without tenant parameter
   162  func (r *pgRepository) GetGlobalByID(ctx context.Context, id string) (*model.RuntimeContext, error) {
   163  	var runtimeCtxEnt RuntimeContext
   164  	if err := r.singleGetterGlobal.GetGlobal(ctx, repo.Conditions{repo.NewEqualCondition("id", id)}, repo.NoOrderBy, &runtimeCtxEnt); err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	return r.conv.FromEntity(&runtimeCtxEnt), nil
   169  }
   170  
   171  // RuntimeContextCollection represents collection of RuntimeContext
   172  type RuntimeContextCollection []RuntimeContext
   173  
   174  // Len returns the count of RuntimeContexts in the collection
   175  func (r RuntimeContextCollection) Len() int {
   176  	return len(r)
   177  }
   178  
   179  // List retrieves a page of RuntimeContext objects associated to Runtime with id `runtimeID` that are matching the provided filters from the database that are visible for `tenant`
   180  func (r *pgRepository) List(ctx context.Context, runtimeID string, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error) {
   181  	var runtimeCtxsCollection RuntimeContextCollection
   182  	tenantID, err := uuid.Parse(tenant)
   183  	if err != nil {
   184  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   185  	}
   186  	filterSubquery, args, err := label.FilterQuery(model.RuntimeContextLabelableObject, label.IntersectSet, tenantID, filter)
   187  	if err != nil {
   188  		return nil, errors.Wrap(err, "while building filter query")
   189  	}
   190  
   191  	conditions := repo.Conditions{
   192  		repo.NewEqualCondition("runtime_id", runtimeID),
   193  	}
   194  	if filterSubquery != "" {
   195  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   196  	}
   197  
   198  	page, totalCount, err := r.pageableQuerier.List(ctx, resource.RuntimeContext, tenant, pageSize, cursor, "id", &runtimeCtxsCollection, conditions...)
   199  
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	items := make([]*model.RuntimeContext, 0, len(runtimeCtxsCollection))
   205  
   206  	for _, runtimeCtxEnt := range runtimeCtxsCollection {
   207  		items = append(items, r.conv.FromEntity(&runtimeCtxEnt))
   208  	}
   209  	return &model.RuntimeContextPage{
   210  		Data:       items,
   211  		TotalCount: totalCount,
   212  		PageInfo:   page,
   213  	}, nil
   214  }
   215  
   216  // ListByRuntimeIDs retrieves a page of RuntimeContext objects for each runtimeID from the database that are visible for `tenantID`
   217  func (r *pgRepository) ListByRuntimeIDs(ctx context.Context, tenantID string, runtimeIDs []string, pageSize int, cursor string) ([]*model.RuntimeContextPage, error) {
   218  	var runtimeCtxsCollection RuntimeContextCollection
   219  
   220  	counts, err := r.unionLister.List(ctx, resource.RuntimeContext, tenantID, runtimeIDs, "runtime_id", pageSize, cursor, orderByColumns, &runtimeCtxsCollection)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	runtimeContextByID := map[string][]*model.RuntimeContext{}
   226  	for _, runtimeContextEntity := range runtimeCtxsCollection {
   227  		rc := r.conv.FromEntity(&runtimeContextEntity)
   228  		runtimeContextByID[runtimeContextEntity.RuntimeID] = append(runtimeContextByID[runtimeContextEntity.RuntimeID], rc)
   229  	}
   230  
   231  	offset, err := pagination.DecodeOffsetCursor(cursor)
   232  	if err != nil {
   233  		return nil, errors.Wrap(err, "while decoding page cursor")
   234  	}
   235  
   236  	runtimeContextPages := make([]*model.RuntimeContextPage, 0, len(runtimeIDs))
   237  	for _, runtimeID := range runtimeIDs {
   238  		totalCount := counts[runtimeID]
   239  		hasNextPage := false
   240  		endCursor := ""
   241  		if totalCount > offset+len(runtimeContextByID[runtimeID]) {
   242  			hasNextPage = true
   243  			endCursor = pagination.EncodeNextOffsetCursor(offset, pageSize)
   244  		}
   245  
   246  		page := &pagination.Page{
   247  			StartCursor: cursor,
   248  			EndCursor:   endCursor,
   249  			HasNextPage: hasNextPage,
   250  		}
   251  
   252  		runtimeContextPages = append(runtimeContextPages, &model.RuntimeContextPage{Data: runtimeContextByID[runtimeID], TotalCount: totalCount, PageInfo: page})
   253  	}
   254  
   255  	return runtimeContextPages, nil
   256  }
   257  
   258  // ListAll retrieves all RuntimeContext objects from the database that are visible for `tenant`
   259  func (r *pgRepository) ListAll(ctx context.Context, tenant string) ([]*model.RuntimeContext, error) {
   260  	var entities RuntimeContextCollection
   261  
   262  	if err := r.lister.List(ctx, resource.RuntimeContext, tenant, &entities); err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return r.multipleFromEntities(entities), nil
   267  }
   268  
   269  // ListAllForRuntime retrieves all RuntimeContext objects for runtime with ID `runtimeID`  from the database that are visible for `tenant`
   270  func (r *pgRepository) ListAllForRuntime(ctx context.Context, tenant, runtimeID string) ([]*model.RuntimeContext, error) {
   271  	var entities RuntimeContextCollection
   272  
   273  	if err := r.lister.List(ctx, resource.RuntimeContext, tenant, &entities, repo.NewEqualCondition("runtime_id", runtimeID)); err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	return r.multipleFromEntities(entities), nil
   278  }
   279  
   280  // ListByScenariosAndRuntimeIDs lists all runtime contexts that are in any of the given scenarios and are owned by any of the runtimes provided
   281  func (r *pgRepository) ListByScenariosAndRuntimeIDs(ctx context.Context, tenant string, scenarios []string, runtimeIDs []string) ([]*model.RuntimeContext, error) {
   282  	if len(runtimeIDs) == 0 || len(scenarios) == 0 {
   283  		return nil, nil
   284  	}
   285  	tenantUUID, err := uuid.Parse(tenant)
   286  	if err != nil {
   287  		return nil, apperrors.NewInvalidDataError("tenantID is not UUID")
   288  	}
   289  
   290  	var entities RuntimeContextCollection
   291  
   292  	// Scenarios query part
   293  	scenariosFilters := make([]*labelfilter.LabelFilter, 0, len(scenarios))
   294  	for _, scenarioValue := range scenarios {
   295  		query := fmt.Sprintf(`$[*] ? (@ == "%s")`, scenarioValue)
   296  		scenariosFilters = append(scenariosFilters, labelfilter.NewForKeyWithQuery(model.ScenariosKey, query))
   297  	}
   298  
   299  	scenariosSubquery, scenariosArgs, err := label.FilterQuery(model.RuntimeContextLabelableObject, label.UnionSet, tenantUUID, scenariosFilters)
   300  	if err != nil {
   301  		return nil, errors.Wrap(err, "while creating scenarios filter query")
   302  	}
   303  
   304  	var conditions repo.Conditions
   305  	if scenariosSubquery != "" {
   306  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", scenariosSubquery, scenariosArgs))
   307  	}
   308  	conditions = append(conditions, repo.NewInConditionForStringValues("runtime_id", runtimeIDs))
   309  
   310  	if err = r.lister.List(ctx, resource.RuntimeContext, tenant, &entities, conditions...); err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	return r.multipleFromEntities(entities), nil
   315  }
   316  
   317  // ListByScenarios lists all runtime contexts that are in any of the given scenarios
   318  func (r *pgRepository) ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.RuntimeContext, error) {
   319  	if len(scenarios) == 0 {
   320  		return nil, nil
   321  	}
   322  	tenantUUID, err := uuid.Parse(tenant)
   323  	if err != nil {
   324  		return nil, apperrors.NewInvalidDataError("tenantID is not UUID")
   325  	}
   326  
   327  	var entities RuntimeContextCollection
   328  
   329  	// Scenarios query part
   330  	scenariosFilters := make([]*labelfilter.LabelFilter, 0, len(scenarios))
   331  	for _, scenarioValue := range scenarios {
   332  		query := fmt.Sprintf(`$[*] ? (@ == "%s")`, scenarioValue)
   333  		scenariosFilters = append(scenariosFilters, labelfilter.NewForKeyWithQuery(model.ScenariosKey, query))
   334  	}
   335  
   336  	scenariosSubquery, scenariosArgs, err := label.FilterQuery(model.RuntimeContextLabelableObject, label.UnionSet, tenantUUID, scenariosFilters)
   337  	if err != nil {
   338  		return nil, errors.Wrap(err, "while creating scenarios filter query")
   339  	}
   340  
   341  	var conditions repo.Conditions
   342  	if scenariosSubquery != "" {
   343  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", scenariosSubquery, scenariosArgs))
   344  	}
   345  
   346  	if err = r.lister.List(ctx, resource.RuntimeContext, tenant, &entities, conditions...); err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	return r.multipleFromEntities(entities), nil
   351  }
   352  
   353  // ListByIDs lists all runtime contexts that are in any of the given scenarios
   354  func (r *pgRepository) ListByIDs(ctx context.Context, tenant string, ids []string) ([]*model.RuntimeContext, error) {
   355  	if len(ids) == 0 {
   356  		return nil, nil
   357  	}
   358  
   359  	var entities RuntimeContextCollection
   360  
   361  	var conditions repo.Conditions
   362  	conditions = append(conditions, repo.NewInConditionForStringValues("id", ids))
   363  
   364  	if err := r.lister.List(ctx, resource.RuntimeContext, tenant, &entities, conditions...); err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	return r.multipleFromEntities(entities), nil
   369  }
   370  
   371  // Create stores RuntimeContext entity in the database using the values from `item`
   372  func (r *pgRepository) Create(ctx context.Context, tenant string, item *model.RuntimeContext) error {
   373  	if item == nil {
   374  		return apperrors.NewInternalError("item can not be empty")
   375  	}
   376  	return r.creator.Create(ctx, resource.RuntimeContext, tenant, r.conv.ToEntity(item))
   377  }
   378  
   379  // Update updates the existing RuntimeContext entity in the database with the values from `item`
   380  func (r *pgRepository) Update(ctx context.Context, tenant string, item *model.RuntimeContext) error {
   381  	if item == nil {
   382  		return apperrors.NewInternalError("item can not be empty")
   383  	}
   384  	return r.updater.UpdateSingle(ctx, resource.RuntimeContext, tenant, r.conv.ToEntity(item))
   385  }
   386  
   387  func (r *pgRepository) multipleFromEntities(entities RuntimeContextCollection) []*model.RuntimeContext {
   388  	items := make([]*model.RuntimeContext, 0, len(entities))
   389  	for _, ent := range entities {
   390  		rtmCtx := r.conv.FromEntity(&ent)
   391  
   392  		items = append(items, rtmCtx)
   393  	}
   394  	return items
   395  }