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 }