Add IS_SUBSTRING operator for use in API resource filtering. (#645)

* Add IS_SUBSTRING operator for use in API resource filtering.

This should allow substring matches on fields like names and labels and
so on.

Also bump the version of Mastermind/squirrel so we get the new 'like'
operator for use when building the SQL query.

Additionally, I also had to fix the generate_api.sh script which had a
bug (it modified the wrong file permissions before), and add a dummy
service to generate Swagger definitions for the Filter itself (this was
a hack in the previous Makefile that I lost when we moved to Bazel).

* Add comments for DummyFilterService

* Add more comments

* change errors returned

* fix import
This commit is contained in:
Ajay Gopinathan 2019-01-08 18:16:12 -08:00 committed by Kubernetes Prow Robot
parent 8c6dfd14b6
commit 5a9e3ff14b
9 changed files with 296 additions and 57 deletions

View File

@ -472,7 +472,7 @@ go_repository(
go_repository(
name = "com_github_masterminds_squirrel",
commit = "a6b93000bd21",
commit = "fa735ea14f09",
importpath = "github.com/Masterminds/squirrel",
)

View File

@ -38,6 +38,10 @@ message Predicate {
// Checks if the value is a member of a given array, which should be one of
// |int_values|, |long_values| or |string_values|.
IN = 8;
// Checks if the value contains |string_value| as a substring match. Only
// applies to |string_value|.
IS_SUBSTRING = 9;
}
Op op = 1;
@ -115,4 +119,10 @@ message Filter {
repeated Predicate predicates = 1;
}
// This dummy service is required so that grpc-gateway will generate Swagger
// definitions for the Filter message. Otherwise, it does not get generated
// since Filter itself is not used in any of the RPC calls - only a serialized
// encoded version of it is used.
service DummyFilterService {
rpc GetFilter(Filter) returns (Filter) {}
}

View File

@ -34,12 +34,13 @@ bazel build @com_github_go_swagger//cmd/swagger
# Build .pb.go and .gw.pb.go files from the proto sources.
bazel build //backend/api:api_generated_go_sources
set -x
# Copy the generated files into the source tree and add license.
for f in $GENERATED_GO_PROTO_FILES; do
name=$(basename ${f})
cp $f go_client/${name}
chmod 766 $f
${AUTOGEN_CMD} -i --no-tlc -c "Google LLC" -l apache go_client/${name}
target=go_client/$(basename ${f})
cp $f $target
chmod 766 $target
${AUTOGEN_CMD} -i --no-tlc -c "Google LLC" -l apache $target
done
# Generate and copy back into source tree .swagger.json files.

View File

@ -23,6 +23,11 @@ import math "math"
import timestamp "github.com/golang/protobuf/ptypes/timestamp"
import _ "google.golang.org/genproto/googleapis/api/annotations"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
@ -45,6 +50,7 @@ const (
Predicate_LESS_THAN Predicate_Op = 6
Predicate_LESS_THAN_EQUALS Predicate_Op = 7
Predicate_IN Predicate_Op = 8
Predicate_IS_SUBSTRING Predicate_Op = 9
)
var Predicate_Op_name = map[int32]string{
@ -56,6 +62,7 @@ var Predicate_Op_name = map[int32]string{
6: "LESS_THAN",
7: "LESS_THAN_EQUALS",
8: "IN",
9: "IS_SUBSTRING",
}
var Predicate_Op_value = map[string]int32{
"UNKNOWN": 0,
@ -66,13 +73,14 @@ var Predicate_Op_value = map[string]int32{
"LESS_THAN": 6,
"LESS_THAN_EQUALS": 7,
"IN": 8,
"IS_SUBSTRING": 9,
}
func (x Predicate_Op) String() string {
return proto.EnumName(Predicate_Op_name, int32(x))
}
func (Predicate_Op) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{0, 0}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{0, 0}
}
type Predicate struct {
@ -96,7 +104,7 @@ func (m *Predicate) Reset() { *m = Predicate{} }
func (m *Predicate) String() string { return proto.CompactTextString(m) }
func (*Predicate) ProtoMessage() {}
func (*Predicate) Descriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{0}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{0}
}
func (m *Predicate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Predicate.Unmarshal(m, b)
@ -398,7 +406,7 @@ func (m *IntValues) Reset() { *m = IntValues{} }
func (m *IntValues) String() string { return proto.CompactTextString(m) }
func (*IntValues) ProtoMessage() {}
func (*IntValues) Descriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{1}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{1}
}
func (m *IntValues) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IntValues.Unmarshal(m, b)
@ -436,7 +444,7 @@ func (m *StringValues) Reset() { *m = StringValues{} }
func (m *StringValues) String() string { return proto.CompactTextString(m) }
func (*StringValues) ProtoMessage() {}
func (*StringValues) Descriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{2}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{2}
}
func (m *StringValues) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StringValues.Unmarshal(m, b)
@ -474,7 +482,7 @@ func (m *LongValues) Reset() { *m = LongValues{} }
func (m *LongValues) String() string { return proto.CompactTextString(m) }
func (*LongValues) ProtoMessage() {}
func (*LongValues) Descriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{3}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{3}
}
func (m *LongValues) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LongValues.Unmarshal(m, b)
@ -512,7 +520,7 @@ func (m *Filter) Reset() { *m = Filter{} }
func (m *Filter) String() string { return proto.CompactTextString(m) }
func (*Filter) ProtoMessage() {}
func (*Filter) Descriptor() ([]byte, []int) {
return fileDescriptor_filter_acfa53e1ea44e70d, []int{4}
return fileDescriptor_filter_836b9ddef08e4ec1, []int{4}
}
func (m *Filter) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Filter.Unmarshal(m, b)
@ -548,38 +556,113 @@ func init() {
proto.RegisterEnum("api.Predicate_Op", Predicate_Op_name, Predicate_Op_value)
}
func init() { proto.RegisterFile("backend/api/filter.proto", fileDescriptor_filter_acfa53e1ea44e70d) }
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
var fileDescriptor_filter_acfa53e1ea44e70d = []byte{
// 469 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x92, 0xd1, 0x6e, 0xd3, 0x30,
0x14, 0x86, 0x9b, 0x78, 0x49, 0x9b, 0xd3, 0xae, 0x0b, 0x06, 0x41, 0x54, 0x81, 0x16, 0x3a, 0x84,
0x72, 0x95, 0x4a, 0xe5, 0x66, 0xb7, 0x45, 0x0a, 0x64, 0xa2, 0x4a, 0xc1, 0xed, 0xe0, 0xb2, 0x72,
0xb7, 0xac, 0xb2, 0xd6, 0xc5, 0x56, 0xed, 0x21, 0xf1, 0x00, 0xbc, 0x05, 0x0f, 0x8b, 0x62, 0x27,
0x6e, 0xee, 0x7a, 0xce, 0xf9, 0x7e, 0xf7, 0xff, 0x4f, 0x0e, 0x44, 0x3b, 0x7a, 0xf7, 0x58, 0x56,
0xf7, 0x33, 0x2a, 0xd8, 0xec, 0x81, 0x1d, 0x54, 0x79, 0x4c, 0xc5, 0x91, 0x2b, 0x8e, 0x11, 0x15,
0x6c, 0xf2, 0x76, 0xcf, 0xf9, 0xfe, 0x50, 0xea, 0x29, 0xad, 0x2a, 0xae, 0xa8, 0x62, 0xbc, 0x92,
0x06, 0x99, 0x5c, 0x36, 0x53, 0x5d, 0xed, 0x9e, 0x1f, 0x66, 0x8a, 0x3d, 0x95, 0x52, 0xd1, 0x27,
0x61, 0x80, 0xe9, 0xbf, 0x33, 0x08, 0xbe, 0x1f, 0xcb, 0x7b, 0x76, 0x47, 0x55, 0x89, 0xdf, 0x83,
0xcb, 0x45, 0xe4, 0xc4, 0x4e, 0x32, 0x9e, 0xbf, 0x48, 0xa9, 0x60, 0xa9, 0x9d, 0xa5, 0x2b, 0x41,
0x5c, 0x2e, 0x70, 0x08, 0xe8, 0xb1, 0xfc, 0x13, 0xb9, 0xb1, 0x93, 0x04, 0xa4, 0xfe, 0x89, 0xdf,
0x41, 0xc0, 0x2a, 0xb5, 0xfd, 0x4d, 0x0f, 0xcf, 0x65, 0x84, 0x62, 0x27, 0xf1, 0xf2, 0x1e, 0x19,
0xb0, 0x4a, 0xfd, 0xac, 0x3b, 0xf8, 0x12, 0xe0, 0xc0, 0xab, 0x7d, 0x33, 0x3f, 0x8b, 0x9d, 0x04,
0xe5, 0x3d, 0x12, 0xd4, 0x3d, 0x03, 0x5c, 0xc1, 0x48, 0xaa, 0x23, 0xb3, 0x88, 0x57, 0x3f, 0x9d,
0xf7, 0xc8, 0xd0, 0x74, 0x0d, 0x94, 0xc1, 0x85, 0xb5, 0xde, 0x70, 0x7e, 0xec, 0x24, 0xc3, 0xf9,
0x24, 0x35, 0x11, 0xd3, 0x36, 0x62, 0xba, 0x69, 0xb9, 0xbc, 0x47, 0xc6, 0x56, 0x64, 0x9e, 0x99,
0x01, 0x58, 0xaf, 0x32, 0xea, 0xeb, 0x17, 0xc6, 0x3a, 0xe8, 0x4d, 0xe3, 0x57, 0xd6, 0xe6, 0x5a,
0xf3, 0x12, 0xcf, 0x61, 0x78, 0x72, 0x2f, 0xa3, 0x81, 0x56, 0x5c, 0x68, 0xc5, 0xb2, 0x4d, 0x50,
0x4b, 0xc0, 0xe6, 0x91, 0xf8, 0x1a, 0xce, 0xbb, 0x81, 0x64, 0x14, 0x68, 0x95, 0x59, 0xe8, 0xfa,
0x14, 0xaa, 0xd6, 0x8d, 0x3a, 0x21, 0xe5, 0xf4, 0xaf, 0x03, 0xee, 0x4a, 0xe0, 0x21, 0xf4, 0x6f,
0x8b, 0x6f, 0xc5, 0xea, 0x57, 0x11, 0xf6, 0x30, 0x80, 0x9f, 0xfd, 0xb8, 0x5d, 0x2c, 0xd7, 0xa1,
0x83, 0xc7, 0x00, 0xc5, 0x6a, 0xb3, 0x6d, 0x6a, 0x17, 0x87, 0x30, 0xfa, 0x4a, 0xb2, 0xc5, 0x26,
0x23, 0xdb, 0x4d, 0xbe, 0x28, 0x42, 0x84, 0xdf, 0xc0, 0xcb, 0x6e, 0xa7, 0x45, 0x3d, 0x7c, 0x0e,
0xc1, 0x32, 0x5b, 0xaf, 0x0d, 0xe7, 0xe3, 0x57, 0x10, 0xda, 0xb2, 0x85, 0xfa, 0xd8, 0x07, 0xf7,
0xa6, 0x08, 0x07, 0x9f, 0xfb, 0xe0, 0x69, 0xeb, 0xd3, 0x2b, 0x08, 0xec, 0x62, 0xf0, 0x6b, 0xf0,
0x9b, 0x40, 0x4e, 0x8c, 0x12, 0x8f, 0x34, 0xd5, 0xf4, 0x23, 0x8c, 0xba, 0xa9, 0x3a, 0x9c, 0x1b,
0xa3, 0x24, 0xb0, 0xdc, 0x07, 0x80, 0xd3, 0xce, 0x3a, 0x14, 0x8a, 0x51, 0x82, 0x2c, 0x75, 0x0d,
0xfe, 0x17, 0x7d, 0xe5, 0x38, 0x05, 0x10, 0xed, 0xf9, 0x99, 0xff, 0x6c, 0x3f, 0x96, 0xbd, 0x4a,
0xd2, 0x21, 0x76, 0xbe, 0x3e, 0x81, 0x4f, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x24, 0xfb,
0x88, 0x32, 0x03, 0x00, 0x00,
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// DummyFilterServiceClient is the client API for DummyFilterService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type DummyFilterServiceClient interface {
GetFilter(ctx context.Context, in *Filter, opts ...grpc.CallOption) (*Filter, error)
}
type dummyFilterServiceClient struct {
cc *grpc.ClientConn
}
func NewDummyFilterServiceClient(cc *grpc.ClientConn) DummyFilterServiceClient {
return &dummyFilterServiceClient{cc}
}
func (c *dummyFilterServiceClient) GetFilter(ctx context.Context, in *Filter, opts ...grpc.CallOption) (*Filter, error) {
out := new(Filter)
err := c.cc.Invoke(ctx, "/api.DummyFilterService/GetFilter", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DummyFilterServiceServer is the server API for DummyFilterService service.
type DummyFilterServiceServer interface {
GetFilter(context.Context, *Filter) (*Filter, error)
}
func RegisterDummyFilterServiceServer(s *grpc.Server, srv DummyFilterServiceServer) {
s.RegisterService(&_DummyFilterService_serviceDesc, srv)
}
func _DummyFilterService_GetFilter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Filter)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DummyFilterServiceServer).GetFilter(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/api.DummyFilterService/GetFilter",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DummyFilterServiceServer).GetFilter(ctx, req.(*Filter))
}
return interceptor(ctx, in, info, handler)
}
var _DummyFilterService_serviceDesc = grpc.ServiceDesc{
ServiceName: "api.DummyFilterService",
HandlerType: (*DummyFilterServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetFilter",
Handler: _DummyFilterService_GetFilter_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "backend/api/filter.proto",
}
func init() { proto.RegisterFile("backend/api/filter.proto", fileDescriptor_filter_836b9ddef08e4ec1) }
var fileDescriptor_filter_836b9ddef08e4ec1 = []byte{
// 514 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x92, 0xcf, 0x6e, 0xda, 0x40,
0x10, 0xc6, 0xfd, 0x27, 0x18, 0x3c, 0x10, 0xe2, 0x6e, 0xab, 0xd6, 0x42, 0xad, 0xe2, 0x92, 0xaa,
0xf5, 0xc9, 0x48, 0xf4, 0x92, 0x4b, 0x0f, 0x44, 0xa5, 0x80, 0x8a, 0x4c, 0xbb, 0x86, 0xf6, 0x88,
0x0c, 0xd9, 0xa0, 0x55, 0xc0, 0x5e, 0xb1, 0x4b, 0xa4, 0x3c, 0x49, 0xdf, 0xa2, 0xcf, 0x18, 0x79,
0xd7, 0x5e, 0x7c, 0x63, 0x66, 0x7e, 0xdf, 0xf0, 0x7d, 0xeb, 0x01, 0x7f, 0x93, 0x6e, 0x1f, 0x49,
0x76, 0x3f, 0x48, 0x19, 0x1d, 0x3c, 0xd0, 0xbd, 0x20, 0xc7, 0x88, 0x1d, 0x73, 0x91, 0x23, 0x3b,
0x65, 0xb4, 0xf7, 0x7e, 0x97, 0xe7, 0xbb, 0x3d, 0x91, 0xd3, 0x34, 0xcb, 0x72, 0x91, 0x0a, 0x9a,
0x67, 0x5c, 0x21, 0xbd, 0xeb, 0x72, 0x2a, 0xab, 0xcd, 0xe9, 0x61, 0x20, 0xe8, 0x81, 0x70, 0x91,
0x1e, 0x98, 0x02, 0xfa, 0xff, 0x2f, 0xc0, 0xfd, 0x75, 0x24, 0xf7, 0x74, 0x9b, 0x0a, 0x82, 0x3e,
0x82, 0x95, 0x33, 0xdf, 0x0c, 0xcc, 0xb0, 0x3b, 0x7c, 0x15, 0xa5, 0x8c, 0x46, 0x7a, 0x16, 0x2d,
0x18, 0xb6, 0x72, 0x86, 0x3c, 0xb0, 0x1f, 0xc9, 0xb3, 0x6f, 0x05, 0x66, 0xe8, 0xe2, 0xe2, 0x27,
0xfa, 0x00, 0x2e, 0xcd, 0xc4, 0xfa, 0x29, 0xdd, 0x9f, 0x88, 0x6f, 0x07, 0x66, 0xd8, 0x98, 0x1a,
0xb8, 0x45, 0x33, 0xf1, 0xa7, 0xe8, 0xa0, 0x6b, 0x80, 0x7d, 0x9e, 0xed, 0xca, 0xf9, 0x45, 0x60,
0x86, 0xf6, 0xd4, 0xc0, 0x6e, 0xd1, 0x53, 0xc0, 0x0d, 0x74, 0xb8, 0x38, 0x52, 0x8d, 0x34, 0x8a,
0xd5, 0x53, 0x03, 0xb7, 0x55, 0x57, 0x41, 0x63, 0xb8, 0xd2, 0xd6, 0x4b, 0xce, 0x09, 0xcc, 0xb0,
0x3d, 0xec, 0x45, 0x2a, 0x62, 0x54, 0x45, 0x8c, 0x96, 0x15, 0x37, 0x35, 0x70, 0x57, 0x8b, 0xd4,
0x9a, 0x01, 0x80, 0xf6, 0xca, 0xfd, 0xa6, 0xdc, 0xd0, 0x95, 0x41, 0x67, 0xa5, 0x5f, 0x5e, 0x98,
0xab, 0xcc, 0x73, 0x34, 0x84, 0xf6, 0xd9, 0x3d, 0xf7, 0x5b, 0x52, 0x71, 0x25, 0x15, 0xf3, 0x2a,
0x41, 0x21, 0x01, 0x9d, 0x87, 0xa3, 0x5b, 0xb8, 0xac, 0x07, 0xe2, 0xbe, 0x2b, 0x55, 0xea, 0x41,
0x93, 0x73, 0xa8, 0x42, 0xd7, 0xa9, 0x85, 0xe4, 0xfd, 0x7f, 0x26, 0x58, 0x0b, 0x86, 0xda, 0xd0,
0x5c, 0xc5, 0x3f, 0xe3, 0xc5, 0xdf, 0xd8, 0x33, 0x10, 0x80, 0x33, 0xfe, 0xbd, 0x1a, 0xcd, 0x13,
0xcf, 0x44, 0x5d, 0x80, 0x78, 0xb1, 0x5c, 0x97, 0xb5, 0x85, 0x3c, 0xe8, 0x4c, 0xf0, 0x78, 0xb4,
0x1c, 0xe3, 0xf5, 0x72, 0x3a, 0x8a, 0x3d, 0x1b, 0xbd, 0x83, 0xd7, 0xf5, 0x4e, 0x85, 0x36, 0xd0,
0x25, 0xb8, 0xf3, 0x71, 0x92, 0x28, 0xce, 0x41, 0x6f, 0xc0, 0xd3, 0x65, 0x05, 0x35, 0x91, 0x03,
0xd6, 0x2c, 0xf6, 0x5a, 0xc5, 0xde, 0x59, 0xb2, 0x4e, 0x56, 0x77, 0xc9, 0x12, 0xcf, 0xe2, 0x89,
0xe7, 0xde, 0x35, 0xa1, 0x21, 0xc3, 0xf4, 0x6f, 0xc0, 0xd5, 0x4f, 0x85, 0xde, 0x82, 0x53, 0x46,
0x34, 0x03, 0x3b, 0x6c, 0xe0, 0xb2, 0xea, 0x7f, 0x86, 0x4e, 0x3d, 0x67, 0x8d, 0xb3, 0x02, 0x3b,
0x74, 0x35, 0xf7, 0x09, 0xe0, 0xfc, 0x8a, 0x35, 0xca, 0x0e, 0xec, 0xd0, 0xd6, 0xd4, 0x2d, 0x38,
0x3f, 0xe4, 0xdd, 0xa3, 0x08, 0x80, 0x55, 0x07, 0xa9, 0xfe, 0xb3, 0xfa, 0x7c, 0xfa, 0x4e, 0x71,
0x8d, 0x18, 0x7e, 0x03, 0xf4, 0xfd, 0x74, 0x38, 0x3c, 0x2b, 0x79, 0x42, 0x8e, 0x4f, 0x74, 0x4b,
0xd0, 0x17, 0x70, 0x27, 0x44, 0x94, 0x2b, 0xdb, 0x52, 0xae, 0x8a, 0x5e, 0xbd, 0xe8, 0x1b, 0x1b,
0x47, 0xde, 0xd4, 0xd7, 0x97, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x08, 0x4b, 0x8b, 0x83, 0x03,
0x00, 0x00,
}

View File

@ -15,5 +15,108 @@
"application/json"
],
"paths": {},
"definitions": {}
"definitions": {
"PredicateOp": {
"type": "string",
"enum": [
"UNKNOWN",
"EQUALS",
"NOT_EQUALS",
"GREATER_THAN",
"GREATER_THAN_EQUALS",
"LESS_THAN",
"LESS_THAN_EQUALS",
"IN",
"IS_SUBSTRING"
],
"default": "UNKNOWN",
"description": "Op is the operation to apply.\n\n - EQUALS: Operators on scalar values. Only applies to one of |int_value|,\n|long_value|, |string_value| or |timestamp_value|.\n - IN: Checks if the value is a member of a given array, which should be one of\n|int_values|, |long_values| or |string_values|.\n - IS_SUBSTRING: Checks if the value contains |string_value| as a substring match. Only\napplies to |string_value|."
},
"apiFilter": {
"type": "object",
"properties": {
"predicates": {
"type": "array",
"items": {
"$ref": "#/definitions/apiPredicate"
},
"description": "All predicates are AND-ed when this filter is applied."
}
},
"description": "Filter is used to filter resources returned from a ListXXX request.\n\nExample filters:\n1) Filter runs with status = 'Running'\nfilter {\n predicate {\n key: \"status\"\n op: EQUALS\n string_value: \"Running\"\n }\n}\n\n2) Filter runs that succeeded since Dec 1, 2018\nfilter {\n predicate {\n key: \"status\"\n op: EQUALS\n string_value: \"Succeeded\"\n }\n predicate {\n key: \"created_at\"\n op: GREATER_THAN\n timestamp_value {\n seconds: 1543651200\n }\n }\n}\n\n3) Filter runs with one of labels 'label_1' or 'label_2'\n\nfilter {\n predicate {\n key: \"label\"\n op: IN\n string_values {\n value: 'label_1'\n value: 'label_2'\n }\n }\n}"
},
"apiIntValues": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
}
}
}
},
"apiLongValues": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"type": "string",
"format": "int64"
}
}
}
},
"apiPredicate": {
"type": "object",
"properties": {
"op": {
"$ref": "#/definitions/PredicateOp"
},
"key": {
"type": "string"
},
"int_value": {
"type": "integer",
"format": "int32"
},
"long_value": {
"type": "string",
"format": "int64"
},
"string_value": {
"type": "string"
},
"timestamp_value": {
"type": "string",
"format": "date-time",
"description": "Timestamp values will be converted to Unix time (seconds since the epoch)\nprior to being used in a filtering operation."
},
"int_values": {
"$ref": "#/definitions/apiIntValues",
"description": "Array values below are only meant to be used by the IN operator."
},
"long_values": {
"$ref": "#/definitions/apiLongValues"
},
"string_values": {
"$ref": "#/definitions/apiStringValues"
}
},
"description": "Predicate captures individual conditions that must be true for a resource\nbeing filtered."
},
"apiStringValues": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}

View File

@ -21,6 +21,7 @@ import (
"github.com/Masterminds/squirrel"
"github.com/golang/protobuf/ptypes"
"github.com/kubeflow/pipelines/backend/src/common/util"
api "github.com/kubeflow/pipelines/backend/api/go_client"
)
@ -38,6 +39,8 @@ type Filter struct {
lte map[string]interface{}
in map[string]interface{}
substring map[string]interface{}
}
// New creates a new Filter from parsing the API filter protocol buffer.
@ -51,6 +54,7 @@ func New(filterProto *api.Filter) (*Filter, error) {
lt: make(map[string]interface{}),
lte: make(map[string]interface{}),
in: make(map[string]interface{}),
substring: make(map[string]interface{}),
}
if err := f.parseFilterProto(); err != nil {
@ -68,7 +72,7 @@ func NewWithKeyMap(filterProto *api.Filter, keyMap map[string]string) (*Filter,
for _, pred := range filterProto.Predicates {
k, ok := keyMap[pred.Key]
if !ok {
return nil, fmt.Errorf("no support for filtering on unrecognized field %q", pred.Key)
return nil, util.NewInvalidInputError("no support for filtering on unrecognized field %q", pred.Key)
}
pred.Key = k
}
@ -107,6 +111,16 @@ func (f *Filter) AddToSelect(sb squirrel.SelectBuilder) squirrel.SelectBuilder {
sb = sb.Where(squirrel.Eq(f.in))
}
if len(f.substring) > 0 {
like := make(squirrel.Like)
// Modify each string value v so it looks like %v% so we are doing a substring
// match with the LIKE operator.
for k, v := range f.substring {
like[k] = fmt.Sprintf("%%%s%%", v)
}
sb = sb.Where(like)
}
return sb
}
@ -115,17 +129,25 @@ func checkPredicate(p *api.Predicate) error {
case api.Predicate_IN:
switch t := p.Value.(type) {
case *api.Predicate_IntValue, *api.Predicate_LongValue, *api.Predicate_StringValue, *api.Predicate_TimestampValue:
return fmt.Errorf("cannot use IN operator with scalar type %T", t)
return util.NewInvalidInputError("cannot use IN operator with scalar type %T", t)
}
case api.Predicate_EQUALS, api.Predicate_NOT_EQUALS, api.Predicate_GREATER_THAN, api.Predicate_GREATER_THAN_EQUALS, api.Predicate_LESS_THAN, api.Predicate_LESS_THAN_EQUALS:
switch t := p.Value.(type) {
case *api.Predicate_IntValues, *api.Predicate_LongValues, *api.Predicate_StringValues:
return fmt.Errorf("cannot use scalar operator %v on array type %T", p.Op, t)
return util.NewInvalidInputError("cannot use scalar operator %v on array type %T", p.Op, t)
}
case api.Predicate_IS_SUBSTRING:
switch t := p.Value.(type) {
case *api.Predicate_StringValue:
return nil
default:
return util.NewInvalidInputError("cannot use non string value type %T with operator %v", p.Op, t)
}
default:
return fmt.Errorf("invalid predicate operation: %v", p.Op)
return util.NewInvalidInputError("invalid predicate operation: %v", p.Op)
}
return nil
@ -153,8 +175,10 @@ func (f *Filter) parseFilterProto() error {
m = f.lte
case api.Predicate_IN:
m = f.in
case api.Predicate_IS_SUBSTRING:
m = f.substring
default:
return fmt.Errorf("invalid predicate operation: %v", pred.Op)
return util.NewInvalidInputError("invalid predicate operation: %v", pred.Op)
}
if err := addPredicateValue(m, pred); err != nil {
@ -176,7 +200,7 @@ func addPredicateValue(m map[string]interface{}, p *api.Predicate) error {
case *api.Predicate_TimestampValue:
ts, err := ptypes.Timestamp(p.GetTimestampValue())
if err != nil {
return fmt.Errorf("invalid timestamp: %v", err)
return util.NewInvalidInputError("invalid timestamp: %v", err)
}
m[p.Key] = ts.Unix()
@ -202,10 +226,10 @@ func addPredicateValue(m map[string]interface{}, p *api.Predicate) error {
m[p.Key] = v
case nil:
return fmt.Errorf("no value set for predicate on key %q", p.Key)
return util.NewInvalidInputError("no value set for predicate on key %q", p.Key)
default:
return fmt.Errorf("unknown value type in Filter for predicate key %q: %T", p.Key, t)
return util.NewInvalidInputError("unknown value type in Filter for predicate key %q: %T", p.Key, t)
}
return nil

View File

@ -65,6 +65,11 @@ func TestValidNewFilters(t *testing.T) {
long_values { values: 100 values: 200 } }`,
&Filter{in: map[string]interface{}{"longvalues": []int64{100, 200}}},
},
{
`predicates {
key: "label" op: IS_SUBSTRING string_value: "label_substring" }`,
&Filter{substring: map[string]interface{}{"label": "label_substring"}},
},
}
for _, test := range tests {
@ -109,6 +114,14 @@ func TestInvalidFilters(t *testing.T) {
`predicates { key: "total" op: LESS_THAN_EQUALS
long_values { values: 10 values: 20} }`,
},
{
`predicates { key: "total" op: IS_SUBSTRING
long_values { values: 10 values: 20} }`,
},
{
`predicates { key: "total" op: IS_SUBSTRING
int_values { values: 10 values: 20} }`,
},
{
`predicates { key: "total" op: IN int_value: 10 }`,
@ -195,6 +208,11 @@ func TestAddToSelect(t *testing.T) {
"SELECT mycolumn WHERE label IN (?,?)",
[]interface{}{"l1", "l2"},
},
{
`predicates { key: "label" op: IS_SUBSTRING string_value: "label_substring" }`,
"SELECT mycolumn WHERE label LIKE ?",
[]interface{}{"%label_substring%"},
},
}
for _, test := range tests {

4
go.mod
View File

@ -2,7 +2,7 @@ module github.com/kubeflow/pipelines
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Masterminds/squirrel v0.0.0-20170825200431-a6b93000bd21
github.com/Masterminds/squirrel v0.0.0-20190107164353-fa735ea14f09
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
github.com/argoproj/argo v2.2.0+incompatible
github.com/cenkalti/backoff v2.0.0+incompatible
@ -46,8 +46,6 @@ require (
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kataras/iris v10.6.7+incompatible
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.0.0 // indirect
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect
github.com/mattn/go-sqlite3 v1.9.0

2
go.sum generated
View File

@ -4,6 +4,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/squirrel v0.0.0-20170825200431-a6b93000bd21 h1:7+Xpo0TxPIBqSHVktrR1VYU2LSNTrJ3B79nVzE74nPA=
github.com/Masterminds/squirrel v0.0.0-20170825200431-a6b93000bd21/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM=
github.com/Masterminds/squirrel v0.0.0-20190107164353-fa735ea14f09 h1:enWVS77aJkLWVIUExiqF6A8eWTVzCXUKUvkST3/wyKI=
github.com/Masterminds/squirrel v0.0.0-20190107164353-fa735ea14f09/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=