mirror of
https://github.com/openziti/ziti.git
synced 2026-09-24 11:26:27 +00:00
341 lines
6.9 KiB
Go
341 lines
6.9 KiB
Go
/*
|
|
Copyright NetFoundry, Inc.
|
|
|
|
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
|
|
|
|
https://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 predicate
|
|
|
|
import (
|
|
"github.com/michaelquigley/pfxlog"
|
|
"gopkg.in/Masterminds/squirrel.v1"
|
|
)
|
|
|
|
type ConjType string
|
|
|
|
const (
|
|
AndConj ConjType = "AND"
|
|
OrConj ConjType = "OR"
|
|
)
|
|
|
|
type ResolverType string
|
|
|
|
const (
|
|
ConjResolver ResolverType = "CONJ"
|
|
GroupResolver ResolverType = "GROUP"
|
|
OpResolver ResolverType = "OP"
|
|
)
|
|
|
|
type OpType string
|
|
|
|
const (
|
|
EqOp OpType = "="
|
|
NeqOp OpType = "!="
|
|
BetweenOp OpType = "BETWEEN"
|
|
NotBetweenOp OpType = "NOT BETWEEN"
|
|
InOp OpType = "IN"
|
|
NotInOp OpType = "NOT IN"
|
|
ContainsOp OpType = "CONTAINS"
|
|
NotContainsOp OpType = "NOT CONTAINS"
|
|
LtOp OpType = "<"
|
|
LtEOp OpType = "<="
|
|
GtOp OpType = ">"
|
|
GtEOp OpType = ">="
|
|
)
|
|
|
|
type ValueType string
|
|
|
|
const (
|
|
StringType ValueType = "STRING"
|
|
NumberType ValueType = "NUMBER"
|
|
DatetimeType ValueType = "DATETIME"
|
|
BoolType ValueType = "BOOL"
|
|
NullType ValueType = "NULL"
|
|
StringArrayType ValueType = "STRING_ARRAY"
|
|
NumberArrayType ValueType = "NUMBER_ARRAY"
|
|
DatetimeArrayType ValueType = "DATETIME_ARRAY"
|
|
)
|
|
|
|
type OpSet map[OpType]bool
|
|
|
|
func (os *OpSet) Merge(os2 *OpSet) {
|
|
for k, v := range *os2 {
|
|
(*os)[k] = v
|
|
}
|
|
}
|
|
|
|
type IdentifierOps map[string]OpSet
|
|
|
|
func (io *IdentifierOps) Merge(io2 *IdentifierOps) {
|
|
for k, v := range *io2 {
|
|
if g, found := (*io)[k]; found {
|
|
g.Merge(&v)
|
|
} else {
|
|
(*io)[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
type IdentifierTranslations map[string]string
|
|
|
|
type resolver interface {
|
|
Resolve() (squirrel.Sqlizer, IdentifierOps)
|
|
ResolverType() ResolverType
|
|
}
|
|
|
|
type conj interface {
|
|
Resolve() (squirrel.Sqlizer, IdentifierOps)
|
|
ResolverType() ResolverType
|
|
ConjType() ConjType
|
|
Append(r resolver)
|
|
}
|
|
|
|
type group struct {
|
|
Parent *group
|
|
conjugations []conj
|
|
operations []resolver
|
|
resolverType ResolverType
|
|
}
|
|
|
|
func newGroup(parent *group) *group {
|
|
return &group{
|
|
Parent: parent,
|
|
conjugations: []conj{},
|
|
operations: []resolver{},
|
|
resolverType: GroupResolver,
|
|
}
|
|
}
|
|
|
|
func (g *group) Resolve() (squirrel.Sqlizer, IdentifierOps) {
|
|
if len(g.conjugations) == 0 {
|
|
|
|
if len(g.operations) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
switch len(g.operations) {
|
|
case 1:
|
|
return g.operations[0].Resolve()
|
|
default:
|
|
pfxlog.Logger().Panic("Invalid number of operations")
|
|
}
|
|
}
|
|
|
|
var root conj
|
|
|
|
ops := g.operations
|
|
|
|
for i, curConj := range g.conjugations {
|
|
if i == 0 {
|
|
root = curConj
|
|
}
|
|
|
|
// One or in a group causes the entire root clause to swap to an OR
|
|
if root.ConjType() != OrConj && curConj.ConjType() == OrConj {
|
|
curConj.Append(root)
|
|
root = curConj
|
|
}
|
|
|
|
var prevConj conj
|
|
|
|
if i > 0 {
|
|
prevConj = g.conjugations[i-1]
|
|
}
|
|
|
|
if len(ops) == 0 {
|
|
break
|
|
}
|
|
|
|
var r resolver
|
|
switch {
|
|
case prevConj == nil && curConj.ConjType() == AndConj:
|
|
//First ANDs consume 2 ops
|
|
r, ops = ops[0], ops[1:]
|
|
curConj.Append(r)
|
|
|
|
if len(ops) == 0 {
|
|
break
|
|
}
|
|
|
|
r, ops = ops[0], ops[1:]
|
|
curConj.Append(r)
|
|
|
|
case prevConj == nil && curConj.ConjType() == OrConj:
|
|
//First ORs consume 1 op
|
|
r, ops = ops[0], ops[1:]
|
|
curConj.Append(r)
|
|
|
|
//..unless there are no other conjs e.g. (a=1 or b=2)
|
|
onLastConj := i >= len(g.conjugations)-1
|
|
if onLastConj || g.conjugations[i+1].ConjType() == OrConj {
|
|
r, ops = ops[0], ops[1:]
|
|
root.Append(r)
|
|
}
|
|
|
|
case prevConj.ConjType() == AndConj && curConj.ConjType() == AndConj:
|
|
// serial ANDs after the first consume 1 op (e.g a=1 AND b=3 AND c=3)
|
|
r, ops = ops[0], ops[1:]
|
|
prevConj.Append(r)
|
|
|
|
case prevConj.ConjType() == OrConj && curConj.ConjType() == AndConj:
|
|
// swapping from OR to AND means this is the new first AND of potentially serial ANDs, consume 2 ops
|
|
r, ops = ops[0], ops[1:]
|
|
curConj.Append(r)
|
|
|
|
if len(ops) == 0 {
|
|
break
|
|
}
|
|
|
|
r, ops = ops[0], ops[1:]
|
|
curConj.Append(r)
|
|
|
|
root.Append(curConj)
|
|
|
|
case curConj.ConjType() == OrConj:
|
|
// ors only consume tokens if they are the last conj, always append ors to root OR (1 or makes root an OR)
|
|
onLastConj := i >= len(g.conjugations)-1
|
|
if len(ops) > 0 && (onLastConj || g.conjugations[i+1].ConjType() == OrConj) {
|
|
r, ops = ops[0], ops[1:]
|
|
root.Append(r)
|
|
}
|
|
|
|
//only append if we are also not root
|
|
if root != curConj {
|
|
root.Append(curConj)
|
|
}
|
|
default:
|
|
|
|
panic("unhandled case")
|
|
}
|
|
|
|
}
|
|
|
|
if len(ops) != 0 {
|
|
panic("ops left behind")
|
|
}
|
|
|
|
return root.Resolve()
|
|
}
|
|
|
|
func (g *group) ResolverType() ResolverType {
|
|
return g.resolverType
|
|
}
|
|
|
|
type op struct {
|
|
op squirrel.Sqlizer
|
|
resolverType ResolverType
|
|
identifier string
|
|
value interface{}
|
|
opType OpType
|
|
valueType ValueType
|
|
}
|
|
|
|
func newOp(o squirrel.Sqlizer, identifier string, opType OpType, value interface{}, valueType ValueType) resolver {
|
|
return &op{
|
|
op: o,
|
|
resolverType: OpResolver,
|
|
identifier: identifier,
|
|
opType: opType,
|
|
value: value,
|
|
valueType: valueType,
|
|
}
|
|
}
|
|
|
|
func (o *op) Resolve() (squirrel.Sqlizer, IdentifierOps) {
|
|
i := IdentifierOps{
|
|
o.identifier: OpSet{
|
|
o.opType: true,
|
|
},
|
|
}
|
|
|
|
return o.op, i
|
|
}
|
|
|
|
func (o *op) ResolverType() ResolverType {
|
|
return o.resolverType
|
|
}
|
|
|
|
type and struct {
|
|
resolverType ResolverType
|
|
conjType ConjType
|
|
resolvers []resolver
|
|
}
|
|
|
|
func newAnd() *and {
|
|
return &and{
|
|
resolverType: ConjResolver,
|
|
conjType: AndConj,
|
|
resolvers: []resolver{},
|
|
}
|
|
}
|
|
|
|
func (a *and) Resolve() (squirrel.Sqlizer, IdentifierOps) {
|
|
sos := []squirrel.Sqlizer{}
|
|
io := IdentifierOps{}
|
|
for _, r := range a.resolvers {
|
|
so, io2 := r.Resolve()
|
|
sos = append(sos, so)
|
|
io.Merge(&io2)
|
|
}
|
|
return squirrel.And(sos), io
|
|
}
|
|
|
|
func (a *and) ResolverType() ResolverType {
|
|
return a.resolverType
|
|
}
|
|
|
|
func (a *and) ConjType() ConjType {
|
|
return a.conjType
|
|
}
|
|
|
|
func (a *and) Append(op resolver) {
|
|
a.resolvers = append(a.resolvers, op)
|
|
}
|
|
|
|
type or struct {
|
|
resolverType ResolverType
|
|
conjType ConjType
|
|
resolvers []resolver
|
|
}
|
|
|
|
func newOr() *or {
|
|
return &or{
|
|
resolverType: ConjResolver,
|
|
conjType: OrConj,
|
|
resolvers: []resolver{},
|
|
}
|
|
}
|
|
|
|
func (o *or) Resolve() (squirrel.Sqlizer, IdentifierOps) {
|
|
sos := []squirrel.Sqlizer{}
|
|
io := IdentifierOps{}
|
|
for _, r := range o.resolvers {
|
|
so, io2 := r.Resolve()
|
|
sos = append(sos, so)
|
|
io.Merge(&io2)
|
|
}
|
|
return squirrel.Or(sos), io
|
|
}
|
|
|
|
func (o *or) ResolverType() ResolverType {
|
|
return ConjResolver
|
|
}
|
|
|
|
func (o *or) ConjType() ConjType {
|
|
return o.conjType
|
|
}
|
|
|
|
func (o *or) Append(op resolver) {
|
|
o.resolvers = append(o.resolvers, op)
|
|
}
|