mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:07:22 +00:00
feat(user/v1): support composite queries (#6361)
* feat(user/v1): support composite queries * fix: added proper error handling for NotQuery * Added error when there are too many levels of nesting * Add localization keys for english * Update internal/api/grpc/user/query.go
This commit is contained in:
parent
e0a5f8661d
commit
3f22fb3a5c
@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) {
|
func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) {
|
||||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||||
queries, err := user_grpc.UserQueriesToQuery(req.Queries)
|
queries, err := user_grpc.UserQueriesToQuery(req.Queries, 0 /*start from level 0*/)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery, err error) {
|
func UserQueriesToQuery(queries []*user_pb.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
|
||||||
q := make([]query.SearchQuery, len(queries))
|
q := make([]query.SearchQuery, len(queries))
|
||||||
for i, query := range queries {
|
for i, query := range queries {
|
||||||
q[i], err = UserQueryToQuery(query)
|
q[i], err = UserQueryToQuery(query, level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -18,7 +18,11 @@ func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery,
|
|||||||
return q, nil
|
return q, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
|
func UserQueryToQuery(query *user_pb.SearchQuery, level uint8) (query.SearchQuery, error) {
|
||||||
|
if level > 20 {
|
||||||
|
// can't go deeper than 20 levels of nesting.
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.User.TooManyNestingLevels")
|
||||||
|
}
|
||||||
switch q := query.Query.(type) {
|
switch q := query.Query.(type) {
|
||||||
case *user_pb.SearchQuery_UserNameQuery:
|
case *user_pb.SearchQuery_UserNameQuery:
|
||||||
return UserNameQueryToQuery(q.UserNameQuery)
|
return UserNameQueryToQuery(q.UserNameQuery)
|
||||||
@ -42,6 +46,12 @@ func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
|
|||||||
return ResourceOwnerQueryToQuery(q.ResourceOwner)
|
return ResourceOwnerQueryToQuery(q.ResourceOwner)
|
||||||
case *user_pb.SearchQuery_InUserIdsQuery:
|
case *user_pb.SearchQuery_InUserIdsQuery:
|
||||||
return InUserIdsQueryToQuery(q.InUserIdsQuery)
|
return InUserIdsQueryToQuery(q.InUserIdsQuery)
|
||||||
|
case *user_pb.SearchQuery_OrQuery:
|
||||||
|
return OrQueryToQuery(q.OrQuery, level)
|
||||||
|
case *user_pb.SearchQuery_AndQuery:
|
||||||
|
return AndQueryToQuery(q.AndQuery, level)
|
||||||
|
case *user_pb.SearchQuery_NotQuery:
|
||||||
|
return NotQueryToQuery(q.NotQuery, level)
|
||||||
default:
|
default:
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
}
|
}
|
||||||
@ -90,3 +100,24 @@ func ResourceOwnerQueryToQuery(q *user_pb.ResourceOwnerQuery) (query.SearchQuery
|
|||||||
func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) {
|
func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) {
|
||||||
return query.NewUserInUserIdsSearchQuery(q.UserIds)
|
return query.NewUserInUserIdsSearchQuery(q.UserIds)
|
||||||
}
|
}
|
||||||
|
func OrQueryToQuery(q *user_pb.OrQuery, level uint8) (query.SearchQuery, error) {
|
||||||
|
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return query.NewUserOrSearchQuery(mappedQueries)
|
||||||
|
}
|
||||||
|
func AndQueryToQuery(q *user_pb.AndQuery, level uint8) (query.SearchQuery, error) {
|
||||||
|
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return query.NewUserAndSearchQuery(mappedQueries)
|
||||||
|
}
|
||||||
|
func NotQueryToQuery(q *user_pb.NotQuery, level uint8) (query.SearchQuery, error) {
|
||||||
|
mappedQuery, err := UserQueryToQuery(q.Query, level+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return query.NewUserNotSearchQuery(mappedQuery)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -92,27 +93,26 @@ func (q *IsNullQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
|||||||
func (q *IsNullQuery) comp() sq.Sqlizer {
|
func (q *IsNullQuery) comp() sq.Sqlizer {
|
||||||
return sq.Eq{q.Column.identifier(): nil}
|
return sq.Eq{q.Column.identifier(): nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *IsNullQuery) Col() Column {
|
func (q *IsNullQuery) Col() Column {
|
||||||
return q.Column
|
return q.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
type orQuery struct {
|
type OrQuery struct {
|
||||||
queries []SearchQuery
|
queries []SearchQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOrQuery(queries ...SearchQuery) (*orQuery, error) {
|
func NewOrQuery(queries ...SearchQuery) (*OrQuery, error) {
|
||||||
if len(queries) == 0 {
|
if len(queries) == 0 {
|
||||||
return nil, ErrMissingColumn
|
return nil, ErrMissingColumn
|
||||||
}
|
}
|
||||||
return &orQuery{queries: queries}, nil
|
return &OrQuery{queries: queries}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *orQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
func (q *OrQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
return query.Where(q.comp())
|
return query.Where(q.comp())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *orQuery) comp() sq.Sqlizer {
|
func (q *OrQuery) comp() sq.Sqlizer {
|
||||||
or := make(sq.Or, len(q.queries))
|
or := make(sq.Or, len(q.queries))
|
||||||
for i, query := range q.queries {
|
for i, query := range q.queries {
|
||||||
or[i] = query.comp()
|
or[i] = query.comp()
|
||||||
@ -120,7 +120,66 @@ func (q *orQuery) comp() sq.Sqlizer {
|
|||||||
return or
|
return or
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *orQuery) Col() Column {
|
type AndQuery struct {
|
||||||
|
queries []SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *AndQuery) Col() Column {
|
||||||
|
return Column{}
|
||||||
|
}
|
||||||
|
func NewAndQuery(queries ...SearchQuery) (*AndQuery, error) {
|
||||||
|
if len(queries) == 0 {
|
||||||
|
return nil, ErrMissingColumn
|
||||||
|
}
|
||||||
|
return &AndQuery{queries: queries}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *AndQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
return query.Where(q.comp())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *AndQuery) comp() sq.Sqlizer {
|
||||||
|
and := make(sq.And, len(q.queries))
|
||||||
|
for i, query := range q.queries {
|
||||||
|
and[i] = query.comp()
|
||||||
|
}
|
||||||
|
return and
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotQuery struct {
|
||||||
|
query SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *NotQuery) Col() Column {
|
||||||
|
return q.query.Col()
|
||||||
|
}
|
||||||
|
func NewNotQuery(query SearchQuery) (*NotQuery, error) {
|
||||||
|
if query == nil {
|
||||||
|
return nil, ErrMissingColumn
|
||||||
|
}
|
||||||
|
return &NotQuery{query: query}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *NotQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
return query.Where(q.comp())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (notQ NotQuery) ToSql() (sql string, args []interface{}, err error) {
|
||||||
|
querySql, queryArgs, queryErr := notQ.query.comp().ToSql()
|
||||||
|
// Handle the error from the query's ToSql() function.
|
||||||
|
if queryErr != nil {
|
||||||
|
return "", queryArgs, queryErr
|
||||||
|
}
|
||||||
|
// Construct the SQL statement.
|
||||||
|
sql = fmt.Sprintf("NOT (%s)", querySql)
|
||||||
|
return sql, queryArgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *NotQuery) comp() sq.Sqlizer {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *OrQuery) Col() Column {
|
||||||
return Column{}
|
return Column{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1383,6 +1383,415 @@ func TestNumberComparisonFromMethod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewOrQuery(t *testing.T) {
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
queries []SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *OrQuery
|
||||||
|
wantErr func(error) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty values",
|
||||||
|
args: args{
|
||||||
|
queries: []SearchQuery{},
|
||||||
|
},
|
||||||
|
wantErr: func(err error) bool {
|
||||||
|
return errors.Is(err, ErrMissingColumn)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct",
|
||||||
|
args: args{
|
||||||
|
queries: []SearchQuery{singleCorrectQuery},
|
||||||
|
},
|
||||||
|
want: &OrQuery{
|
||||||
|
queries: []SearchQuery{singleCorrectQuery},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewOrQuery(tt.args.queries...)
|
||||||
|
if err != nil && tt.wantErr == nil {
|
||||||
|
t.Errorf("NewOrQuery() no error expected got %v", err)
|
||||||
|
return
|
||||||
|
} else if tt.wantErr != nil && !tt.wantErr(err) {
|
||||||
|
t.Errorf("NewOrQuery() unexpeted error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NewOrQuery() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestOrQuery_comp(t *testing.T) {
|
||||||
|
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
|
||||||
|
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
|
||||||
|
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
|
||||||
|
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
|
||||||
|
orq, _ := NewOrQuery(q3, q4)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
queries []SearchQuery
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
query interface{}
|
||||||
|
isNil bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single input",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi input",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1, q2},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested inputs",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1, orq},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Or{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &OrQuery{
|
||||||
|
queries: tt.fields.queries,
|
||||||
|
}
|
||||||
|
query := s.comp()
|
||||||
|
if query == nil && tt.want.isNil {
|
||||||
|
return
|
||||||
|
} else if tt.want.isNil && query != nil {
|
||||||
|
t.Error("query should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(query, tt.want.query) {
|
||||||
|
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestNewAndQuery(t *testing.T) {
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
queries []SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *AndQuery
|
||||||
|
wantErr func(error) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty values",
|
||||||
|
args: args{
|
||||||
|
queries: []SearchQuery{},
|
||||||
|
},
|
||||||
|
wantErr: func(err error) bool {
|
||||||
|
return errors.Is(err, ErrMissingColumn)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct",
|
||||||
|
args: args{
|
||||||
|
queries: []SearchQuery{singleCorrectQuery},
|
||||||
|
},
|
||||||
|
want: &AndQuery{
|
||||||
|
queries: []SearchQuery{singleCorrectQuery},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewAndQuery(tt.args.queries...)
|
||||||
|
if err != nil && tt.wantErr == nil {
|
||||||
|
t.Errorf("NewAndQuery() no error expected got %v", err)
|
||||||
|
return
|
||||||
|
} else if tt.wantErr != nil && !tt.wantErr(err) {
|
||||||
|
t.Errorf("NewAndQuery() unexpeted error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NewAndQuery() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAndQuery_comp(t *testing.T) {
|
||||||
|
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
|
||||||
|
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
|
||||||
|
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
|
||||||
|
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
|
||||||
|
andq, _ := NewAndQuery(q3, q4)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
queries []SearchQuery
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
query interface{}
|
||||||
|
isNil bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single input",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi input",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1, q2},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested inputs",
|
||||||
|
fields: fields{
|
||||||
|
queries: []SearchQuery{q1, andq},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}, sq.And{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &AndQuery{
|
||||||
|
queries: tt.fields.queries,
|
||||||
|
}
|
||||||
|
query := s.comp()
|
||||||
|
if query == nil && tt.want.isNil {
|
||||||
|
return
|
||||||
|
} else if tt.want.isNil && query != nil {
|
||||||
|
t.Error("query should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(query, tt.want.query) {
|
||||||
|
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewNotQuery(t *testing.T) {
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
query SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *NotQuery
|
||||||
|
wantErr func(error) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty query",
|
||||||
|
args: args{
|
||||||
|
query: nil,
|
||||||
|
},
|
||||||
|
wantErr: func(err error) bool {
|
||||||
|
return errors.Is(err, ErrMissingColumn)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct",
|
||||||
|
args: args{
|
||||||
|
query: singleCorrectQuery,
|
||||||
|
},
|
||||||
|
want: &NotQuery{
|
||||||
|
query: singleCorrectQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewNotQuery(tt.args.query)
|
||||||
|
if err != nil && tt.wantErr == nil {
|
||||||
|
t.Errorf("NewNotQuery() no error expected got %v", err)
|
||||||
|
return
|
||||||
|
} else if tt.wantErr != nil && !tt.wantErr(err) {
|
||||||
|
t.Errorf("NewNotQuery() unexpeted error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NewNotQuery() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotQuery_comp(t *testing.T) {
|
||||||
|
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
query SearchQuery
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
query interface{}
|
||||||
|
isNil bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single input",
|
||||||
|
fields: fields{
|
||||||
|
query: q1,
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: &NotQuery{query: q1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &NotQuery{
|
||||||
|
query: tt.fields.query,
|
||||||
|
}
|
||||||
|
query := s.comp()
|
||||||
|
if query == nil && tt.want.isNil {
|
||||||
|
return
|
||||||
|
} else if tt.want.isNil && query != nil {
|
||||||
|
t.Error("query should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(query, tt.want.query) {
|
||||||
|
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestNotQuery_ToSql(t *testing.T) {
|
||||||
|
q, _ := NewTextQuery(testCol, "hello1", TextEquals)
|
||||||
|
type fields struct {
|
||||||
|
query SearchQuery
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single input",
|
||||||
|
fields: fields{
|
||||||
|
query: q,
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: "NOT (test_table.test_col = hello1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &NotQuery{
|
||||||
|
query: tt.fields.query,
|
||||||
|
}
|
||||||
|
queryStr, _, err := s.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("an error raised ")
|
||||||
|
}
|
||||||
|
if queryStr == tt.want.query {
|
||||||
|
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, queryStr, queryStr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAndOrQueryCombo(t *testing.T) {
|
||||||
|
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
|
||||||
|
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
|
||||||
|
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
|
||||||
|
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
|
||||||
|
andq, _ := NewAndQuery(q3, q4)
|
||||||
|
orq, _ := NewOrQuery(q1, q2, andq)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
query SearchQuery
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
query interface{}
|
||||||
|
isNil bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OR containing AND query",
|
||||||
|
fields: fields{
|
||||||
|
query: orq,
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}, sq.And{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := tt.fields.query
|
||||||
|
query := s.comp()
|
||||||
|
if query == nil && tt.want.isNil {
|
||||||
|
return
|
||||||
|
} else if tt.want.isNil && query != nil {
|
||||||
|
t.Error("query should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(query, tt.want.query) {
|
||||||
|
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewInTextQuery(t *testing.T) {
|
func TestNewInTextQuery(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
column Column
|
column Column
|
||||||
|
@ -629,6 +629,15 @@ func (r *UserSearchQueries) AppendMyResourceOwnerQuery(orgID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewUserOrSearchQuery(values []SearchQuery) (SearchQuery, error) {
|
||||||
|
return NewOrQuery(values...)
|
||||||
|
}
|
||||||
|
func NewUserAndSearchQuery(values []SearchQuery) (SearchQuery, error) {
|
||||||
|
return NewAndQuery(values...)
|
||||||
|
}
|
||||||
|
func NewUserNotSearchQuery(value SearchQuery) (SearchQuery, error) {
|
||||||
|
return NewNotQuery(value)
|
||||||
|
}
|
||||||
func NewUserInUserIdsSearchQuery(values []string) (SearchQuery, error) {
|
func NewUserInUserIdsSearchQuery(values []string) (SearchQuery, error) {
|
||||||
return NewInTextQuery(UserIDCol, values)
|
return NewInTextQuery(UserIDCol, values)
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newOrQuery(orgQuery, projectQuery)
|
return NewOrQuery(orgQuery, projectQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) {
|
func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) {
|
||||||
|
@ -53,6 +53,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Няма намерен домейн за съобщение
|
NoDomain: Няма намерен домейн за съобщение
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: Потребителят не може да бъде намерен
|
NotFound: Потребителят не може да бъде намерен
|
||||||
AlreadyExists: Вече съществува потребител
|
AlreadyExists: Вече съществува потребител
|
||||||
NotFoundOnOrg: Потребителят не може да бъде намерен в избраната организация
|
NotFoundOnOrg: Потребителят не може да бъде намерен в избраната организация
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Keine Domäne für Nachricht gefunden
|
NoDomain: Keine Domäne für Nachricht gefunden
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: Benutzer konnte nicht gefunden werden
|
NotFound: Benutzer konnte nicht gefunden werden
|
||||||
AlreadyExists: Benutzer existiert bereits
|
AlreadyExists: Benutzer existiert bereits
|
||||||
NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden
|
NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: No Domain found for message
|
NoDomain: No Domain found for message
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: User could not be found
|
NotFound: User could not be found
|
||||||
AlreadyExists: User already exists
|
AlreadyExists: User already exists
|
||||||
NotFoundOnOrg: User could not be found on chosen organization
|
NotFoundOnOrg: User could not be found on chosen organization
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: No se encontró el dominio para el mensaje
|
NoDomain: No se encontró el dominio para el mensaje
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: El usuario no pudo encontrarse
|
NotFound: El usuario no pudo encontrarse
|
||||||
AlreadyExists: El usuario ya existe
|
AlreadyExists: El usuario ya existe
|
||||||
NotFoundOnOrg: El usuario no pudo encontrarse en la organización elegida
|
NotFoundOnOrg: El usuario no pudo encontrarse en la organización elegida
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Aucun domaine trouvé pour le message
|
NoDomain: Aucun domaine trouvé pour le message
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: L'utilisateur n'a pas été trouvé
|
NotFound: L'utilisateur n'a pas été trouvé
|
||||||
AlreadyExists: L'utilisateur existe déjà
|
AlreadyExists: L'utilisateur existe déjà
|
||||||
NotFoundOnOrg: L'utilisateur n'a pas été trouvé dans l'organisation choisie
|
NotFoundOnOrg: L'utilisateur n'a pas été trouvé dans l'organisation choisie
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Nessun dominio trovato per il messaggio
|
NoDomain: Nessun dominio trovato per il messaggio
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: L'utente non è stato trovato
|
NotFound: L'utente non è stato trovato
|
||||||
AlreadyExists: L'utente già esistente
|
AlreadyExists: L'utente già esistente
|
||||||
NotFoundOnOrg: L'utente non è stato trovato nell'organizzazione scelta
|
NotFoundOnOrg: L'utente non è stato trovato nell'organizzazione scelta
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: メッセージのドメインが見つかりません
|
NoDomain: メッセージのドメインが見つかりません
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: ユーザーが見つかりません
|
NotFound: ユーザーが見つかりません
|
||||||
AlreadyExists: 既に存在するユーザーです
|
AlreadyExists: 既に存在するユーザーです
|
||||||
NotFoundOnOrg: ユーザーが選択した組織内で見つかりません
|
NotFoundOnOrg: ユーザーが選択した組織内で見つかりません
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Не е пронајден домен за пораката
|
NoDomain: Не е пронајден домен за пораката
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: Корисникот не е пронајден
|
NotFound: Корисникот не е пронајден
|
||||||
AlreadyExists: Корисникот веќе постои
|
AlreadyExists: Корисникот веќе постои
|
||||||
NotFoundOnOrg: Корисникот не е пронајден во избраната организација
|
NotFoundOnOrg: Корисникот не е пронајден во избраната организација
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Nie znaleziono domeny dla wiadomości
|
NoDomain: Nie znaleziono domeny dla wiadomości
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: Nie znaleziono użytkownika
|
NotFound: Nie znaleziono użytkownika
|
||||||
AlreadyExists: Użytkownik już istnieje
|
AlreadyExists: Użytkownik już istnieje
|
||||||
NotFoundOnOrg: Użytkownik nie został znaleziony w wybranej organizacji
|
NotFoundOnOrg: Użytkownik nie został znaleziony w wybranej organizacji
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: Nenhum domínio encontrado para a mensagem
|
NoDomain: Nenhum domínio encontrado para a mensagem
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: Usuário não pôde ser encontrado
|
NotFound: Usuário não pôde ser encontrado
|
||||||
AlreadyExists: Usuário já existe
|
AlreadyExists: Usuário já existe
|
||||||
NotFoundOnOrg: Usuário não pôde ser encontrado na organização escolhida
|
NotFoundOnOrg: Usuário não pôde ser encontrado na organização escolhida
|
||||||
|
@ -51,6 +51,7 @@ Errors:
|
|||||||
Notification:
|
Notification:
|
||||||
NoDomain: 未找到对应的域名
|
NoDomain: 未找到对应的域名
|
||||||
User:
|
User:
|
||||||
|
TooManyNestingLevels: Too many query nesting levels (Max 20).
|
||||||
NotFound: 找不到用户
|
NotFound: 找不到用户
|
||||||
AlreadyExists: 用户已存在
|
AlreadyExists: 用户已存在
|
||||||
NotFoundOnOrg: 在所选组织中找不到用户
|
NotFoundOnOrg: 在所选组织中找不到用户
|
||||||
|
@ -185,9 +185,35 @@ message SearchQuery {
|
|||||||
TypeQuery type_query = 8;
|
TypeQuery type_query = 8;
|
||||||
LoginNameQuery login_name_query = 9;
|
LoginNameQuery login_name_query = 9;
|
||||||
InUserIDQuery in_user_ids_query = 10;
|
InUserIDQuery in_user_ids_query = 10;
|
||||||
|
OrQuery or_query = 11;
|
||||||
|
AndQuery and_query = 12;
|
||||||
|
NotQuery not_query = 13;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message OrQuery {
|
||||||
|
repeated SearchQuery queries = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the sub queries to 'OR'"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
message AndQuery {
|
||||||
|
repeated SearchQuery queries = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the sub queries to 'AND'"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message NotQuery {
|
||||||
|
SearchQuery query = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the sub query to negate (NOT)"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
message InUserIDQuery {
|
message InUserIDQuery {
|
||||||
repeated string user_ids = 1 [
|
repeated string user_ids = 1 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user