mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
389 lines
8.7 KiB
Go
389 lines
8.7 KiB
Go
// Copyright 2022 Molecula Corp. (DBA FeatureBase).
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package sql
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"vitess.io/vitess/go/vt/sqlparser"
|
|
)
|
|
|
|
type selectPart int
|
|
|
|
const (
|
|
SelectPartDistinct selectPart = 1 << iota
|
|
SelectPartID
|
|
SelectPartStar
|
|
SelectPartField
|
|
SelectPartFields
|
|
SelectPartCountStar
|
|
SelectPartCountField
|
|
SelectPartCountDistinctField
|
|
SelectPartMinField
|
|
SelectPartMaxField
|
|
SelectPartSumField
|
|
SelectPartAvgField
|
|
)
|
|
|
|
type fromPart int
|
|
|
|
const (
|
|
FromPartTable fromPart = 1 << iota
|
|
FromPartTables
|
|
FromPartJoin
|
|
)
|
|
|
|
type wherePart int
|
|
|
|
const (
|
|
WherePartIDCondition wherePart = 1 << iota
|
|
WherePartFieldCondition
|
|
WherePartMultiFieldCondition
|
|
)
|
|
|
|
type groupByPart int
|
|
|
|
const (
|
|
GroupByPartField groupByPart = 1 << iota
|
|
GroupByPartFields
|
|
)
|
|
|
|
type havingPart int
|
|
|
|
const (
|
|
HavingPartCondition havingPart = 1 << iota
|
|
)
|
|
|
|
type orderByPart int
|
|
|
|
const (
|
|
OrderByPartField orderByPart = 1 << iota
|
|
OrderByPartFields
|
|
)
|
|
|
|
type limitPart int
|
|
|
|
const (
|
|
LimitPartLimit limitPart = 1 << iota
|
|
LimitPartOffset
|
|
)
|
|
|
|
type QueryMask struct {
|
|
SelectMask selectPart
|
|
FromMask fromPart
|
|
WhereMask wherePart
|
|
GroupByMask groupByPart
|
|
HavingMask havingPart
|
|
OrderByMask orderByPart
|
|
LimitMask limitPart
|
|
}
|
|
|
|
func NewQueryMask(sp selectPart, fp fromPart, wp wherePart, gp groupByPart, hp havingPart) QueryMask {
|
|
qm := QueryMask{}
|
|
qm.orSelect(sp)
|
|
qm.orFrom(fp)
|
|
qm.orWhere(wp)
|
|
qm.orGroupBy(gp)
|
|
qm.orHaving(hp)
|
|
return qm
|
|
}
|
|
|
|
// ApplyFilter returns true if m passes the filter f.
|
|
// Note: only certain query parts are included; namely,
|
|
// the orderBy and limit masks are not applied to the
|
|
// filter.
|
|
func (m *QueryMask) ApplyFilter(f QueryMask) bool {
|
|
if m.SelectMask&f.SelectMask != m.SelectMask {
|
|
return false
|
|
}
|
|
if m.FromMask&f.FromMask != m.FromMask {
|
|
return false
|
|
}
|
|
if m.WhereMask&f.WhereMask != m.WhereMask {
|
|
return false
|
|
}
|
|
if m.GroupByMask&f.GroupByMask != m.GroupByMask {
|
|
return false
|
|
}
|
|
if m.HavingMask&f.HavingMask != m.HavingMask {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// orSelect applies the bitwise-OR operation to the selectMask.
|
|
func (m *QueryMask) orSelect(o selectPart) {
|
|
m.SelectMask |= o
|
|
}
|
|
|
|
// orFrom applies the bitwise-OR operation to the fromMask.
|
|
func (m *QueryMask) orFrom(o fromPart) {
|
|
m.FromMask |= o
|
|
}
|
|
|
|
// orWhere applies the bitwise-OR operation to the whereMask.
|
|
func (m *QueryMask) orWhere(parts ...wherePart) {
|
|
for _, o := range parts {
|
|
m.WhereMask |= o
|
|
}
|
|
}
|
|
|
|
// orGroupBy applies the bitwise-OR operation to the groupByMask.
|
|
func (m *QueryMask) orGroupBy(o groupByPart) {
|
|
m.GroupByMask |= o
|
|
}
|
|
|
|
// orHaving applies the bitwise-OR operation to the havingMask.
|
|
func (m *QueryMask) orHaving(o havingPart) {
|
|
m.HavingMask |= o
|
|
}
|
|
|
|
// orOrderBy applies the bitwise-OR operation to the orderByMask.
|
|
func (m *QueryMask) orOrderBy(o orderByPart) {
|
|
m.OrderByMask |= o
|
|
}
|
|
|
|
// orLimit applies the bitwise-OR operation to the limitMask.
|
|
func (m *QueryMask) orLimit(o limitPart) {
|
|
m.LimitMask |= o
|
|
}
|
|
|
|
// HasSelect returns true if the mask contains a supported select clause.
|
|
func (m *QueryMask) HasSelect() bool {
|
|
return m.SelectMask > 0
|
|
}
|
|
|
|
// HasSelectPart returns true if the mask contains the provided select part.
|
|
func (m *QueryMask) HasSelectPart(p selectPart) bool {
|
|
return (m.SelectMask & p) > 0
|
|
}
|
|
|
|
// HasFrom returns true if the mask contains a supported from clause.
|
|
func (m *QueryMask) HasFrom() bool {
|
|
return m.FromMask > 0
|
|
}
|
|
|
|
// HasWhere returns true if the mask contains a supported where clause.
|
|
func (m *QueryMask) HasWhere() bool {
|
|
return m.WhereMask > 0
|
|
}
|
|
|
|
// HasGroupBy returns true if the mask contains a supported group by clause.
|
|
func (m *QueryMask) HasGroupBy() bool {
|
|
return m.GroupByMask > 0
|
|
}
|
|
|
|
// HasHaving returns true if the mask contains a supported having clause.
|
|
func (m *QueryMask) HasHaving() bool {
|
|
return m.HavingMask > 0
|
|
}
|
|
|
|
// HasOrderBy returns true if the mask contains a supported order by clause.
|
|
func (m *QueryMask) HasOrderBy() bool {
|
|
return m.OrderByMask > 0
|
|
}
|
|
|
|
// HasLimit returns true if the mask contains a supported limit clause.
|
|
func (m *QueryMask) HasLimit() bool {
|
|
return m.LimitMask > 0
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
func MustGenerateMask(sql string) QueryMask {
|
|
parsed, err := sqlparser.Parse(sql)
|
|
if err != nil {
|
|
return QueryMask{}
|
|
}
|
|
return GenerateMask(parsed)
|
|
}
|
|
|
|
func GenerateMask(parsed sqlparser.Statement) QueryMask {
|
|
qm := QueryMask{}
|
|
|
|
switch stmt := parsed.(type) {
|
|
case *sqlparser.Select:
|
|
if strings.ToLower(strings.TrimSpace(stmt.Distinct)) == "distinct" {
|
|
qm.orSelect(SelectPartDistinct)
|
|
}
|
|
// select parts
|
|
var fldCount int
|
|
for _, item := range stmt.SelectExprs {
|
|
switch expr := item.(type) {
|
|
case *sqlparser.AliasedExpr:
|
|
switch colExpr := expr.Expr.(type) {
|
|
case *sqlparser.ColName:
|
|
name := colExpr.Name.String()
|
|
if name == ColID {
|
|
qm.orSelect(SelectPartID)
|
|
} else {
|
|
fldCount++
|
|
}
|
|
case *sqlparser.FuncExpr:
|
|
funcName := FuncName(strings.ToLower(colExpr.Name.String()))
|
|
switch len(colExpr.Exprs) {
|
|
case 1:
|
|
var isStar bool
|
|
var isField bool
|
|
var isDistinctField bool
|
|
switch exp := colExpr.Exprs[0].(type) {
|
|
case *sqlparser.AliasedExpr:
|
|
switch exp.Expr.(type) {
|
|
case *sqlparser.ColName:
|
|
isField = true
|
|
isDistinctField = colExpr.Distinct
|
|
}
|
|
case *sqlparser.StarExpr:
|
|
isStar = true
|
|
}
|
|
|
|
switch funcName {
|
|
case FuncCount:
|
|
if isStar {
|
|
qm.orSelect(SelectPartCountStar)
|
|
} else if isDistinctField {
|
|
qm.orSelect(SelectPartCountDistinctField)
|
|
} else if isField {
|
|
qm.orSelect(SelectPartCountField)
|
|
}
|
|
case FuncMin:
|
|
if isField {
|
|
qm.orSelect(SelectPartMinField)
|
|
}
|
|
case FuncMax:
|
|
if isField {
|
|
qm.orSelect(SelectPartMaxField)
|
|
}
|
|
case FuncSum:
|
|
if isField {
|
|
qm.orSelect(SelectPartSumField)
|
|
}
|
|
case FuncAvg:
|
|
if isField {
|
|
qm.orSelect(SelectPartAvgField)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case *sqlparser.StarExpr:
|
|
qm.orSelect(SelectPartStar)
|
|
}
|
|
}
|
|
if fldCount == 1 {
|
|
qm.orSelect(SelectPartField)
|
|
} else if fldCount > 1 {
|
|
qm.orSelect(SelectPartFields)
|
|
}
|
|
|
|
// from parts
|
|
switch len(stmt.From) {
|
|
case 1:
|
|
switch stmt.From[0].(type) {
|
|
case *sqlparser.AliasedTableExpr:
|
|
qm.orFrom(FromPartTable)
|
|
case *sqlparser.JoinTableExpr:
|
|
qm.orFrom(FromPartJoin)
|
|
}
|
|
case 2:
|
|
switch stmt.From[0].(type) {
|
|
case *sqlparser.AliasedTableExpr:
|
|
switch stmt.From[1].(type) {
|
|
case *sqlparser.AliasedTableExpr:
|
|
qm.orFrom(FromPartTables)
|
|
}
|
|
}
|
|
}
|
|
|
|
// where parts
|
|
where := stmt.Where
|
|
if where != nil {
|
|
switch where.Type {
|
|
case "where":
|
|
qm.orWhere(generateWhereMask(where.Expr)...)
|
|
}
|
|
}
|
|
|
|
// group by parts
|
|
var groupByFieldCount int
|
|
for _, item := range stmt.GroupBy {
|
|
switch item.(type) {
|
|
case *sqlparser.ColName:
|
|
groupByFieldCount++
|
|
}
|
|
}
|
|
if groupByFieldCount == 1 {
|
|
qm.orGroupBy(GroupByPartField)
|
|
} else if groupByFieldCount > 1 {
|
|
qm.orGroupBy(GroupByPartFields)
|
|
}
|
|
|
|
// having parts
|
|
if stmt.Having != nil {
|
|
qm.orHaving(HavingPartCondition)
|
|
}
|
|
|
|
// order by parts
|
|
var orderByFieldCount int
|
|
for _, item := range stmt.OrderBy {
|
|
switch item.Expr.(type) {
|
|
case *sqlparser.ColName:
|
|
orderByFieldCount++
|
|
}
|
|
}
|
|
if orderByFieldCount == 1 {
|
|
qm.orOrderBy(OrderByPartField)
|
|
} else if orderByFieldCount > 1 {
|
|
qm.orOrderBy(OrderByPartFields)
|
|
}
|
|
|
|
// limit parts
|
|
if stmt.Limit != nil {
|
|
switch stmt.Limit.Rowcount.(type) {
|
|
case *sqlparser.SQLVal:
|
|
qm.orLimit(LimitPartLimit)
|
|
}
|
|
switch stmt.Limit.Offset.(type) {
|
|
case *sqlparser.SQLVal:
|
|
qm.orLimit(LimitPartOffset)
|
|
}
|
|
}
|
|
}
|
|
return qm
|
|
}
|
|
|
|
// TODO: add more recursion within the comparison operators (left/right parts)
|
|
func generateWhereMask(e sqlparser.Expr) []wherePart {
|
|
var wp []wherePart
|
|
|
|
switch expr := e.(type) {
|
|
case *sqlparser.ComparisonExpr:
|
|
var leftName string
|
|
if colExpr, ok := expr.Left.(*sqlparser.ColName); ok {
|
|
leftName = colExpr.Name.String()
|
|
}
|
|
switch leftName {
|
|
case ColID:
|
|
wp = append(wp, WherePartIDCondition)
|
|
case "":
|
|
//
|
|
default:
|
|
wp = append(wp, WherePartFieldCondition)
|
|
}
|
|
case *sqlparser.RangeCond:
|
|
wp = append(wp, WherePartFieldCondition)
|
|
case *sqlparser.AndExpr, *sqlparser.OrExpr:
|
|
// TODO: we need to recursively ensure that the left/right
|
|
// sides of these and/or expressions are field-op-val, and
|
|
// that none of the fields are "_id"
|
|
// TODO: could we use extractComparison or something like it?
|
|
wp = append(wp, WherePartMultiFieldCondition)
|
|
case *sqlparser.NotExpr:
|
|
wp = append(wp, generateWhereMask(expr.Expr)...)
|
|
case *sqlparser.IsExpr:
|
|
wp = append(wp, WherePartFieldCondition)
|
|
case *sqlparser.ParenExpr:
|
|
wp = append(wp, generateWhereMask(expr.Expr)...)
|
|
}
|
|
|
|
return wp
|
|
}
|