github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/repo/list_union.go (about) 1 package repo 2 3 import ( 4 "context" 5 "strings" 6 7 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 8 9 "github.com/kyma-incubator/compass/components/director/pkg/pagination" 10 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 11 "github.com/kyma-incubator/compass/components/director/pkg/resource" 12 "github.com/pkg/errors" 13 ) 14 15 // UnionLister is an interface for listing tenant scoped entities with either externally managed tenant accesses (m2m table or view) or embedded tenant in them. 16 // It lists entities based on multiple parent queries. For each parent a separate list with a separate tenant isolation subquery is created and the end result is a union of all the results. 17 type UnionLister interface { 18 // List stores the result into dest and returns the total count of tuples for each id from ids 19 List(ctx context.Context, resourceType resource.Type, tenant string, ids []string, idsColumn string, pageSize int, cursor string, orderBy OrderByParams, dest Collection, additionalConditions ...Condition) (map[string]int, error) 20 } 21 22 // UnionListerGlobal is an interface for listing global entities. 23 // It lists entities based on multiple parent queries. For each parent a separate list with a separate tenant isolation subquery is created and the end result is a union of all the results. 24 type UnionListerGlobal interface { 25 ListGlobal(ctx context.Context, ids []string, idsColumn string, pageSize int, cursor string, orderBy OrderByParams, dest Collection, additionalConditions ...Condition) (map[string]int, error) 26 SetSelectedColumns(selectedColumns []string) 27 Clone() *unionLister 28 } 29 30 type unionLister struct { 31 tableName string 32 selectedColumns string 33 tenantColumn *string 34 resourceType resource.Type 35 } 36 37 // NewUnionListerWithEmbeddedTenant is a constructor for UnionLister about entities with tenant embedded in them. 38 func NewUnionListerWithEmbeddedTenant(tableName string, tenantColumn string, selectedColumns []string) UnionLister { 39 return &unionLister{ 40 tableName: tableName, 41 selectedColumns: strings.Join(selectedColumns, ", "), 42 tenantColumn: &tenantColumn, 43 } 44 } 45 46 // NewUnionLister is a constructor for UnionLister about entities with externally managed tenant accesses (m2m table or view) 47 func NewUnionLister(tableName string, selectedColumns []string) UnionLister { 48 return &unionLister{ 49 tableName: tableName, 50 selectedColumns: strings.Join(selectedColumns, ", "), 51 } 52 } 53 54 // NewUnionListerGlobal is a constructor for UnionListerGlobal about global entities. 55 func NewUnionListerGlobal(resourceType resource.Type, tableName string, selectedColumns []string) UnionListerGlobal { 56 return &unionLister{ 57 tableName: tableName, 58 selectedColumns: strings.Join(selectedColumns, ", "), 59 resourceType: resourceType, 60 } 61 } 62 63 // SetSelectedColumns sets the selected columns for the lister 64 func (l *unionLister) SetSelectedColumns(selectedColumns []string) { 65 l.selectedColumns = strings.Join(selectedColumns, ", ") 66 } 67 68 // Clone returns a copy of the lister 69 func (l *unionLister) Clone() *unionLister { 70 var clonedLister unionLister 71 72 clonedLister.resourceType = l.resourceType 73 clonedLister.tableName = l.tableName 74 clonedLister.selectedColumns = l.selectedColumns 75 clonedLister.tenantColumn = l.tenantColumn 76 77 return &clonedLister 78 } 79 80 // List lists tenant scoped entities based on multiple parent queries. For each parent a separate list with a separate tenant isolation subquery is created and the end result is a union of all the results. 81 // If the tenantColumn is configured the isolation is based on equal condition on tenantColumn. 82 // If the tenantColumn is not configured an entity with externally managed tenant accesses in m2m table / view is assumed. 83 func (l *unionLister) List(ctx context.Context, resourceType resource.Type, tenant string, ids []string, idscolumn string, pageSize int, cursor string, orderBy OrderByParams, dest Collection, additionalConditions ...Condition) (map[string]int, error) { 84 if tenant == "" { 85 return nil, apperrors.NewTenantRequiredError() 86 } 87 88 if l.tenantColumn != nil { 89 additionalConditions = append(Conditions{NewEqualCondition(*l.tenantColumn, tenant)}, additionalConditions...) 90 return l.list(ctx, resourceType, pageSize, cursor, orderBy, ids, idscolumn, dest, additionalConditions...) 91 } 92 93 tenantIsolation, err := NewTenantIsolationCondition(resourceType, tenant, false) 94 if err != nil { 95 return nil, err 96 } 97 98 additionalConditions = append(additionalConditions, tenantIsolation) 99 100 return l.list(ctx, resourceType, pageSize, cursor, orderBy, ids, idscolumn, dest, additionalConditions...) 101 } 102 103 // ListGlobal lists global entities without tenant isolation. 104 func (l *unionLister) ListGlobal(ctx context.Context, ids []string, idscolumn string, pageSize int, cursor string, orderBy OrderByParams, dest Collection, additionalConditions ...Condition) (map[string]int, error) { 105 return l.list(ctx, l.resourceType, pageSize, cursor, orderBy, ids, idscolumn, dest, additionalConditions...) 106 } 107 108 type queryStruct struct { 109 args []interface{} 110 statement string 111 } 112 113 func (l *unionLister) list(ctx context.Context, resourceType resource.Type, pageSize int, cursor string, orderBy OrderByParams, ids []string, idsColumn string, dest Collection, conditions ...Condition) (map[string]int, error) { 114 persist, err := persistence.FromCtx(ctx) 115 if err != nil { 116 return nil, err 117 } 118 119 offset, err := pagination.DecodeOffsetCursor(cursor) 120 if err != nil { 121 return nil, errors.Wrap(err, "while decoding page cursor") 122 } 123 124 queries, err := l.buildQueries(ids, idsColumn, conditions, orderBy, pageSize, offset) 125 if err != nil { 126 return nil, err 127 } 128 129 stmts := make([]string, 0, len(queries)) 130 for _, q := range queries { 131 stmts = append(stmts, q.statement) 132 } 133 134 args := make([]interface{}, 0, len(queries)) 135 for _, q := range queries { 136 args = append(args, q.args...) 137 } 138 139 query := buildUnionQuery(stmts) 140 141 err = persist.SelectContext(ctx, dest, query, args...) 142 if err != nil { 143 return nil, persistence.MapSQLError(ctx, err, resourceType, resource.List, "while fetching list page of objects from '%s' table", l.tableName) 144 } 145 146 totalCount, err := l.getTotalCount(ctx, resourceType, persist, idsColumn, []string{idsColumn}, OrderByParams{NewAscOrderBy(idsColumn)}, conditions) 147 if err != nil { 148 return nil, err 149 } 150 151 return totalCount, nil 152 } 153 154 func (l *unionLister) buildQueries(ids []string, idsColumn string, conditions []Condition, orderBy OrderByParams, limit int, offset int) ([]queryStruct, error) { 155 queries := make([]queryStruct, 0, len(ids)) 156 for _, id := range ids { 157 query, args, err := buildSelectQueryWithLimitAndOffset(l.tableName, l.selectedColumns, append(conditions, NewEqualCondition(idsColumn, id)), orderBy, limit, offset, false) 158 if err != nil { 159 return nil, errors.Wrap(err, "while building list query") 160 } 161 162 queries = append(queries, queryStruct{ 163 args: args, 164 statement: query, 165 }) 166 } 167 return queries, nil 168 } 169 170 type idToCount struct { 171 ID string `db:"id"` 172 Count int `db:"total_count"` 173 } 174 175 func (l *unionLister) getTotalCount(ctx context.Context, resourceType resource.Type, persist persistence.PersistenceOp, idsColumn string, groupBy GroupByParams, orderBy OrderByParams, conditions Conditions) (map[string]int, error) { 176 query, args, err := buildCountQuery(l.tableName, idsColumn, conditions, groupBy, orderBy, true) 177 if err != nil { 178 return nil, err 179 } 180 181 var counts []idToCount 182 err = persist.SelectContext(ctx, &counts, query, args...) 183 if err != nil { 184 return nil, persistence.MapSQLError(ctx, err, resourceType, resource.List, "while counting objects from '%s' table", l.tableName) 185 } 186 187 totalCount := make(map[string]int) 188 for _, c := range counts { 189 totalCount[c.ID] = c.Count 190 } 191 192 return totalCount, nil 193 }