mirror of
https://github.com/goharbor/harbor
synced 2025-04-16 05:44:00 +00:00

* bump beego upgrade beego version from v1.10.12 to v2.0.5 1, beego v2 vserver/web refactor 2, beego v2 context refactor 3, beego v2 session refactor 4, beego v2 cache refactor 5, beego v2 orm refactor Signed-off-by: MinerYang <yminer@vmware.com>
217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
// Copyright Project Harbor Authors
|
|
//
|
|
// 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 orm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
|
|
"github.com/goharbor/harbor/src/lib/q"
|
|
)
|
|
|
|
// QuerySetter generates the query setter according to the provided model and query.
|
|
// e.g.
|
|
//
|
|
// type Foo struct{
|
|
// Field1 string `orm:"-"` // can not filter/sort
|
|
// Field2 string `orm:"column(customized_field2)"` // support filter by "Field2", "customized_field2"
|
|
// Field3 string `sort:"false"` // cannot be sorted
|
|
// Field4 string `sort:"default:desc"` // the default field/order(asc/desc) to sort if no sorting specified in query.
|
|
// Field5 string `filter:"false"` // cannot be filtered
|
|
// }
|
|
//
|
|
// // support filter by "Field6", "field6"
|
|
//
|
|
// func (f *Foo) FilterByField6(ctx context.Context, qs orm.QuerySetter, key string, value interface{}) orm.QuerySetter {
|
|
// ...
|
|
// return qs
|
|
// }
|
|
//
|
|
// Defining the method "GetDefaultSorts() []*q.Sort" for the model whose default sorting contains more than one fields
|
|
//
|
|
// type Bar struct{
|
|
// Field1 string
|
|
// Field2 string
|
|
// }
|
|
//
|
|
// // Sort by "Field1" desc, "Field2"
|
|
//
|
|
// func (b *Bar) GetDefaultSorts() []*q.Sort {
|
|
// return []*q.Sort{
|
|
// {
|
|
// Key: "Field1",
|
|
// DESC: true,
|
|
// },
|
|
// {
|
|
// Key: "Field2",
|
|
// DESC: false,
|
|
// },
|
|
// }
|
|
// }
|
|
func QuerySetter(ctx context.Context, model interface{}, query *q.Query) (orm.QuerySeter, error) {
|
|
t := reflect.TypeOf(model)
|
|
if t.Kind() != reflect.Ptr {
|
|
return nil, fmt.Errorf("<orm.QuerySetter> cannot use non-ptr model struct `%s`", getFullName(t.Elem()))
|
|
}
|
|
ormer, err := FromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
qs := ormer.QueryTable(model)
|
|
if query == nil {
|
|
return qs, nil
|
|
}
|
|
|
|
metadata := parseModel(model)
|
|
// set filters
|
|
qs = setFilters(ctx, qs, query, metadata)
|
|
|
|
// sorting
|
|
qs = setSorts(qs, query, metadata)
|
|
|
|
// pagination
|
|
if query.PageSize > 0 {
|
|
qs = qs.Limit(query.PageSize)
|
|
if query.PageNumber > 0 {
|
|
qs = qs.Offset(query.PageSize * (query.PageNumber - 1))
|
|
}
|
|
}
|
|
|
|
return qs, nil
|
|
}
|
|
|
|
// PaginationOnRawSQL append page information to the raw sql
|
|
// It should be called after the order by
|
|
// e.g.
|
|
// select a, b, c from mytable order by a limit ? offset ?
|
|
// it appends the " limit ? offset ? " to sql,
|
|
// and appends the limit value and offset value to the params of this query
|
|
func PaginationOnRawSQL(query *q.Query, sql string, params []interface{}) (string, []interface{}) {
|
|
if query != nil && query.PageSize > 0 {
|
|
sql += ` limit ?`
|
|
params = append(params, query.PageSize)
|
|
|
|
if query.PageNumber > 0 {
|
|
sql += ` offset ?`
|
|
params = append(params, (query.PageNumber-1)*query.PageSize)
|
|
}
|
|
}
|
|
return sql, params
|
|
}
|
|
|
|
// QuerySetterForCount creates the query setter used for count with the sort and pagination information ignored
|
|
func QuerySetterForCount(ctx context.Context, model interface{}, query *q.Query, ignoredCols ...string) (orm.QuerySeter, error) {
|
|
query = q.MustClone(query)
|
|
query.Sorts = nil
|
|
query.PageSize = 0
|
|
query.PageNumber = 0
|
|
return QuerySetter(ctx, model, query)
|
|
}
|
|
|
|
// set filters according to the query
|
|
func setFilters(ctx context.Context, qs orm.QuerySeter, query *q.Query, meta *metadata) orm.QuerySeter {
|
|
for key, value := range query.Keywords {
|
|
// The "strings.SplitN()" here is a workaround for the incorrect usage of query which should be avoided
|
|
// e.g. use the query with the knowledge of underlying ORM implementation, the "OrList" should be used instead:
|
|
// https://github.com/goharbor/harbor/blob/v2.2.0/src/controller/project/controller.go#L348
|
|
k := strings.SplitN(key, orm.ExprSep, 2)[0]
|
|
mk, filterable := meta.Filterable(k)
|
|
if !filterable {
|
|
// This is a workaround for the unsuitable usage of query, the keyword format for field and method should be consistent
|
|
// e.g. "ArtifactDigest" or the snake case format "artifact_digest" should be used instead:
|
|
// https://github.com/goharbor/harbor/blob/v2.2.0/src/controller/blob/controller.go#L233
|
|
mk, filterable = meta.Filterable(snakeCase(k))
|
|
if !filterable {
|
|
continue
|
|
}
|
|
}
|
|
// filter function defined, use it directly
|
|
if mk.FilterFunc != nil {
|
|
qs = mk.FilterFunc(ctx, qs, key, value)
|
|
continue
|
|
}
|
|
// fuzzy match
|
|
if f, ok := value.(*q.FuzzyMatchValue); ok {
|
|
qs = qs.Filter(key+"__icontains", Escape(f.Value))
|
|
continue
|
|
}
|
|
// range
|
|
if r, ok := value.(*q.Range); ok {
|
|
if r.Min != nil {
|
|
qs = qs.Filter(key+"__gte", r.Min)
|
|
}
|
|
if r.Max != nil {
|
|
qs = qs.Filter(key+"__lte", r.Max)
|
|
}
|
|
continue
|
|
}
|
|
// or list
|
|
if ol, ok := value.(*q.OrList); ok {
|
|
if ol == nil || len(ol.Values) == 0 {
|
|
qs = qs.Filter(key+"__in", nil)
|
|
} else {
|
|
qs = qs.Filter(key+"__in", ol.Values...)
|
|
}
|
|
continue
|
|
}
|
|
// and list
|
|
if _, ok := value.(*q.AndList); ok {
|
|
// do nothing as and list needs to be handled by the logic of DAO
|
|
continue
|
|
}
|
|
// exact match
|
|
qs = qs.Filter(key, value)
|
|
}
|
|
return qs
|
|
}
|
|
|
|
// set sorts according to the query
|
|
func setSorts(qs orm.QuerySeter, query *q.Query, meta *metadata) orm.QuerySeter {
|
|
var sortings []string
|
|
for _, sort := range query.Sorts {
|
|
if !meta.Sortable(sort.Key) {
|
|
continue
|
|
}
|
|
sorting := sort.Key
|
|
if sort.DESC {
|
|
sorting = fmt.Sprintf("-%s", sorting)
|
|
}
|
|
sortings = append(sortings, sorting)
|
|
}
|
|
// if no sorts are specified, apply the default sort setting if exists
|
|
if len(sortings) == 0 {
|
|
for _, ds := range meta.DefaultSorts {
|
|
sorting := ds.Key
|
|
if ds.DESC {
|
|
sorting = fmt.Sprintf("-%s", sorting)
|
|
}
|
|
sortings = append(sortings, sorting)
|
|
}
|
|
}
|
|
if len(sortings) > 0 {
|
|
qs = qs.OrderBy(sortings...)
|
|
}
|
|
return qs
|
|
}
|
|
|
|
// get reflect.Type name with package path.
|
|
func getFullName(typ reflect.Type) string {
|
|
return typ.PkgPath() + "." + typ.Name()
|
|
}
|