mirror of
https://github.com/peridotbuild/peridot.git
synced 2024-06-15 22:30:15 +00:00
343 lines
7.2 KiB
Go
Vendored
343 lines
7.2 KiB
Go
Vendored
// SPDX-FileCopyrightText: Copyright (c) 2023, Ctrl IQ, Inc. All rights reserved
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package pika
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
orderedmap "github.com/wk8/go-ordered-map/v2"
|
|
"go.ciq.dev/pika/parser"
|
|
)
|
|
|
|
var (
|
|
logger *logrus.Logger
|
|
pikaMetadataModelName = "PikaMetadataModelName"
|
|
|
|
// Operators
|
|
// Equal
|
|
OpEq = "="
|
|
// Not equal
|
|
OpNeq = "!="
|
|
// Greater than
|
|
OpGt = ">"
|
|
// Greater than or equal
|
|
OpGte = ">="
|
|
// Less than
|
|
OpLt = "<"
|
|
// Less than or equal
|
|
OpLte = "<="
|
|
// Like
|
|
OpLike = "LIKE"
|
|
// Not like
|
|
OpNotLike = "NOT LIKE"
|
|
// ILike
|
|
OpILike = "ILIKE"
|
|
// Not ILike
|
|
OpNotILike = "NOT ILIKE"
|
|
// Is null
|
|
OpIsNull = "IS NULL"
|
|
// Is not null
|
|
OpIsNotNull = "IS NOT NULL"
|
|
// Empty
|
|
OpEmpty = ""
|
|
|
|
// Hints
|
|
// Negate
|
|
HintNegate = "__ne"
|
|
// In
|
|
HintIn = "__in"
|
|
// Not in
|
|
HintNotIn = "__nin"
|
|
// Greater than
|
|
HintGt = "__gt"
|
|
// Greater than or equal
|
|
HintGte = "__gte"
|
|
// Less than
|
|
HintLt = "__lt"
|
|
// Less than or equal
|
|
HintLte = "__lte"
|
|
// Like
|
|
HintLike = "__like"
|
|
// Not like
|
|
HintNotLike = "__nlike"
|
|
// ILike
|
|
HintILike = "__ilike"
|
|
// Not ILike
|
|
HintNotILike = "__nilike"
|
|
// Is null
|
|
HintIsNull = "__null"
|
|
// Is not null
|
|
HintIsNotNull = "__notnull"
|
|
// Or
|
|
HintOr = "__or"
|
|
// And
|
|
HintAnd = "__and"
|
|
// Empty
|
|
HintEmpty = ""
|
|
|
|
// Join Type
|
|
innerJoin = "INNER JOIN"
|
|
leftJoin = "LEFT JOIN"
|
|
rightJoin = "RIGHT JOIN"
|
|
fullJoin = "FULL JOIN"
|
|
|
|
// Operators map
|
|
operators = map[string]string{
|
|
HintNegate: OpNeq,
|
|
HintIn: OpEq,
|
|
HintNotIn: OpNeq,
|
|
HintGt: OpGt,
|
|
HintGte: OpGte,
|
|
HintLt: OpLt,
|
|
HintLte: OpLte,
|
|
HintLike: OpLike,
|
|
HintNotLike: OpNotLike,
|
|
HintILike: OpILike,
|
|
HintNotILike: OpNotILike,
|
|
HintIsNull: OpIsNull,
|
|
HintIsNotNull: OpIsNotNull,
|
|
HintEmpty: OpEmpty,
|
|
}
|
|
|
|
// Antlr operator mapping
|
|
antlrOperators = map[int]string{
|
|
parser.FilterLexerEQUALS: "__eq",
|
|
parser.FilterLexerNOT_EQUALS: HintNegate,
|
|
parser.FilterLexerLESS_THAN: HintLt,
|
|
parser.FilterLexerLESS_EQUALS: HintLte,
|
|
parser.FilterLexerGREATER_EQUALS: HintGte,
|
|
parser.FilterLexerGREATER_THAN: HintGt,
|
|
parser.FilterLexerCOLON: HintILike,
|
|
}
|
|
antlrOperatorsNot = map[int]string{
|
|
parser.FilterLexerEQUALS: HintNegate,
|
|
parser.FilterLexerNOT_EQUALS: "__eq",
|
|
parser.FilterLexerLESS_THAN: HintGt,
|
|
parser.FilterLexerLESS_EQUALS: HintGte,
|
|
parser.FilterLexerGREATER_EQUALS: HintLte,
|
|
parser.FilterLexerGREATER_THAN: HintLt,
|
|
parser.FilterLexerCOLON: HintNotILike,
|
|
}
|
|
|
|
// Antlr values
|
|
antlrValues = map[int]bool{
|
|
parser.FilterLexerSTRING: true,
|
|
parser.FilterLexerDURATION: true,
|
|
parser.FilterLexerTIMESTAMP: true,
|
|
parser.FilterLexerNUM_FLOAT: true,
|
|
parser.FilterLexerNUM_INT: true,
|
|
parser.FilterLexerNUM_UINT: true,
|
|
parser.FilterLexerTRUE: true,
|
|
parser.FilterLexerFALSE: true,
|
|
parser.FilterLexerNULL: true,
|
|
}
|
|
)
|
|
|
|
//nolint:gochecknoinits
|
|
func init() {
|
|
logger = logrus.New()
|
|
|
|
if os.Getenv("PIKA_DEBUG") == "1" {
|
|
logger.SetLevel(logrus.DebugLevel)
|
|
} else {
|
|
logger.SetLevel(logrus.InfoLevel)
|
|
}
|
|
}
|
|
|
|
type pikaAip160State struct {
|
|
initParens bool
|
|
activeParens bool
|
|
innerParens bool
|
|
activeOr bool
|
|
activeExpr *pikaFiltering
|
|
activeIdentifier string
|
|
activeValue any
|
|
activeValueType int
|
|
activeOperator string
|
|
activeNot bool
|
|
forceNot bool
|
|
filterContent []*pikaFiltering
|
|
args *orderedmap.OrderedMap[string, interface{}]
|
|
}
|
|
|
|
type pikaFiltering struct {
|
|
entries *orderedmap.OrderedMap[string, string]
|
|
or bool
|
|
innerOr bool
|
|
}
|
|
|
|
//nolint:structcheck
|
|
type base struct {
|
|
filters []pikaFiltering
|
|
args *orderedmap.OrderedMap[string, interface{}]
|
|
excludeColumns []string
|
|
includeColumns []string
|
|
orderBy []string
|
|
ignoreLimit bool
|
|
ignoreOffset bool
|
|
ignoreOrderBy bool
|
|
limit *int
|
|
offset *int
|
|
err error
|
|
metadata map[string]string
|
|
joins []*pikaJoin
|
|
replaceFields map[string]*replaceField
|
|
}
|
|
|
|
type pikaJoin struct {
|
|
joinType string
|
|
first *joinInfo
|
|
second *joinInfo
|
|
}
|
|
|
|
// Join details
|
|
type joinInfo struct {
|
|
tableName, modelName, key string
|
|
}
|
|
|
|
type replaceField struct {
|
|
tableName string
|
|
modelName string
|
|
}
|
|
|
|
type connBase struct {
|
|
tableAlias map[string]string
|
|
}
|
|
|
|
func newBase() *base {
|
|
return &base{
|
|
args: orderedmap.New[string, interface{}](),
|
|
replaceFields: map[string]*replaceField{},
|
|
joins: make([]*pikaJoin, 0),
|
|
}
|
|
}
|
|
|
|
func findEmptyForKey[T any](key string, f *orderedmap.OrderedMap[string, T]) string {
|
|
if _, ok := f.Get(key); ok {
|
|
return findEmptyForKey("!"+key, f)
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
func cleanKey(key string) string {
|
|
return strings.ReplaceAll(key, "!", "")
|
|
}
|
|
|
|
func (b *base) filter(innerOr bool, or bool, queries ...string) {
|
|
newFilters := orderedmap.New[string, string]()
|
|
for _, query := range queries {
|
|
split := strings.Split(query, "=")
|
|
if len(split) != 2 {
|
|
b.err = fmt.Errorf("invalid filter: %s", query)
|
|
break
|
|
}
|
|
if strings.Contains(split[0], "!") {
|
|
b.err = fmt.Errorf("filter key contains exclamation mark: %s", query)
|
|
break
|
|
}
|
|
newFilters.Set(findEmptyForKey(split[0], newFilters), split[1])
|
|
}
|
|
|
|
b.filters = append(b.filters, pikaFiltering{
|
|
entries: newFilters,
|
|
or: or,
|
|
innerOr: innerOr,
|
|
})
|
|
}
|
|
|
|
func (b *base) setArgs(args *orderedmap.OrderedMap[string, interface{}]) {
|
|
for pair := args.Oldest(); pair != nil; pair = pair.Next() {
|
|
b.args.Set(pair.Key, pair.Value)
|
|
}
|
|
}
|
|
|
|
func (b *base) clearAll() {
|
|
b.args = orderedmap.New[string, interface{}]()
|
|
b.filters = []pikaFiltering{}
|
|
b.joins = []*pikaJoin{}
|
|
}
|
|
|
|
func (b *base) setLimit(limit int) {
|
|
b.limit = &limit
|
|
}
|
|
|
|
func (b *base) setOffset(offset int) {
|
|
b.offset = &offset
|
|
}
|
|
|
|
func (b *base) setOrderBy(orderBy []string, reset bool) {
|
|
if reset {
|
|
b.orderBy = []string{}
|
|
}
|
|
b.orderBy = append(b.orderBy, orderBy...)
|
|
}
|
|
|
|
func (c *connBase) TableAlias(src string, dst string) {
|
|
if c.tableAlias == nil {
|
|
c.tableAlias = make(map[string]string)
|
|
}
|
|
if _, ok := c.tableAlias[src]; ok {
|
|
panic(fmt.Sprintf("duplicate table alias: %s", src))
|
|
}
|
|
c.tableAlias[src] = dst
|
|
}
|
|
|
|
func getPikaMetadata[T any]() map[string]string {
|
|
x := struct {
|
|
X T
|
|
}{}
|
|
|
|
metadata := make(map[string]string)
|
|
|
|
ref := reflect.ValueOf(x.X)
|
|
modelName := ref.Type().Name()
|
|
metadata[pikaMetadataModelName] = modelName
|
|
|
|
// Iterate through fields to get tags
|
|
for i := 0; i < ref.NumField(); i++ {
|
|
field := ref.Type().Field(i)
|
|
|
|
// We only care about fields starting with "Pika"
|
|
if strings.HasPrefix(field.Name, "Pika") {
|
|
if contains(PikaMetadataFields, field.Name) {
|
|
tag := field.Tag.Get("pika")
|
|
|
|
if _, ok := metadata[field.Name]; ok {
|
|
panic(fmt.Sprintf("duplicate Pika metadata field: %s", field.Name))
|
|
}
|
|
metadata[field.Name] = tag
|
|
}
|
|
continue
|
|
}
|
|
|
|
// This is a regular field, let's store information about it's type
|
|
tag := field.Tag.Get("db")
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
|
|
if _, ok := metadata[field.Name]; ok {
|
|
panic(fmt.Sprintf("duplicate Pika database field: %s", field.Name))
|
|
}
|
|
|
|
metadata[tag] = field.Type.String()
|
|
}
|
|
|
|
return metadata
|
|
}
|
|
|
|
func contains[T comparable](slice []T, element T) bool {
|
|
for _, e := range slice {
|
|
if e == element {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|