| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- package executor
- import (
- "fmt"
- "strings"
- "sync"
- "github.com/danfragoso/pizzasql-next/pkg/parser"
- "github.com/danfragoso/pizzasql-next/pkg/storage"
- )
- // statelessEval is a shared, immutable Executor used only to evaluate validated,
- // side-effect-free expressions (expression indexes and generated columns) from
- // the storage layer. It holds no session, catalog, or per-connection state, so
- // concurrent evaluations from different connections share no mutable data. The
- // expression validators below guarantee that only subquery-free, deterministic
- // expressions reach it.
- var statelessEval = &Executor{}
- // storedExprCache memoizes parsed stored expressions across all connections. A
- // stored expression is immutable text, so the cache is safe to share.
- var storedExprCache sync.Map // string -> parser.Expr
- // parseStoredExpr parses (once) an expression persisted in the durable schema
- // (generated column or expression index) and rejects anything that is not
- // deterministic, subquery-free SQL. The same text always parses to the same
- // expression, so the result is cached.
- func parseStoredExpr(text string) (parser.Expr, error) {
- if cached, ok := storedExprCache.Load(text); ok {
- return cached.(parser.Expr), nil
- }
- expr, err := parser.ParseExpr(text)
- if err != nil {
- return nil, fmt.Errorf("invalid stored expression %q: %w", text, err)
- }
- if err := ValidateDeterministicExpr(expr); err != nil {
- return nil, fmt.Errorf("invalid stored expression %q: %w", text, err)
- }
- storedExprCache.Store(text, expr)
- return expr, nil
- }
- // EvalStoredExpression evaluates a persisted, validated expression against a
- // row. It is the storage layer's expression-index evaluator and is stateless and
- // concurrency-safe.
- func EvalStoredExpression(text string, row storage.Row) (interface{}, error) {
- expr, err := parseStoredExpr(text)
- if err != nil {
- return nil, err
- }
- return statelessEval.evalExpr(expr, row)
- }
- // ValidateDeterministicExpr rejects an expression that SQLite would not allow in
- // an index or generated column: subqueries, window functions, aggregates, and
- // non-deterministic or unknown functions.
- func ValidateDeterministicExpr(expr parser.Expr) error {
- switch e := expr.(type) {
- case nil:
- return nil
- case *parser.LiteralExpr:
- return nil
- case *parser.ColumnRef:
- return nil
- case *parser.ParenExpr:
- return ValidateDeterministicExpr(e.Expr)
- case *parser.UnaryExpr:
- return ValidateDeterministicExpr(e.Operand)
- case *parser.BinaryExpr:
- if err := ValidateDeterministicExpr(e.Left); err != nil {
- return err
- }
- return ValidateDeterministicExpr(e.Right)
- case *parser.IsNullExpr:
- return ValidateDeterministicExpr(e.Left)
- case *parser.IsDistinctExpr:
- if err := ValidateDeterministicExpr(e.Left); err != nil {
- return err
- }
- return ValidateDeterministicExpr(e.Right)
- case *parser.BetweenExpr:
- if err := ValidateDeterministicExpr(e.Left); err != nil {
- return err
- }
- if err := ValidateDeterministicExpr(e.Low); err != nil {
- return err
- }
- return ValidateDeterministicExpr(e.High)
- case *parser.LikeExpr:
- if err := ValidateDeterministicExpr(e.Left); err != nil {
- return err
- }
- if err := ValidateDeterministicExpr(e.Pattern); err != nil {
- return err
- }
- return ValidateDeterministicExpr(e.Escape)
- case *parser.CaseExpr:
- if err := ValidateDeterministicExpr(e.Operand); err != nil {
- return err
- }
- for _, w := range e.Whens {
- if err := ValidateDeterministicExpr(w.Condition); err != nil {
- return err
- }
- if err := ValidateDeterministicExpr(w.Result); err != nil {
- return err
- }
- }
- return ValidateDeterministicExpr(e.Else)
- case *parser.CastExpr:
- return ValidateDeterministicExpr(e.Expr)
- case *parser.InExpr:
- if e.Subquery != nil {
- return fmt.Errorf("subqueries are not allowed in index expressions")
- }
- if err := ValidateDeterministicExpr(e.Left); err != nil {
- return err
- }
- for _, v := range e.Values {
- if err := ValidateDeterministicExpr(v); err != nil {
- return err
- }
- }
- return nil
- case *parser.FunctionCall:
- if e.Star {
- return fmt.Errorf("function %s(*) is not allowed in index expressions", e.Name)
- }
- if isAggregateFunctionName(e.Name, len(e.Args)) {
- return fmt.Errorf("aggregate function %s() is not allowed in index expressions", e.Name)
- }
- if !isDeterministicFunction(e.Name) {
- return fmt.Errorf("non-deterministic or unsupported function %s() is not allowed in index expressions", e.Name)
- }
- for _, a := range e.Args {
- if err := ValidateDeterministicExpr(a); err != nil {
- return err
- }
- }
- return nil
- case *parser.SubqueryExpr:
- return fmt.Errorf("subqueries are not allowed in index expressions")
- case *parser.ExistsExpr:
- return fmt.Errorf("subqueries are not allowed in index expressions")
- case *parser.WindowExpr:
- return fmt.Errorf("window functions are not allowed in index expressions")
- default:
- return fmt.Errorf("unsupported expression in index definition: %T", expr)
- }
- }
- // isAggregateFunctionName reports whether a function name is an aggregate in the
- // given call shape. MIN/MAX are scalar with two or more arguments.
- func isAggregateFunctionName(name string, argCount int) bool {
- switch strings.ToUpper(name) {
- case "COUNT", "SUM", "AVG", "TOTAL", "GROUP_CONCAT",
- "JSON_GROUP_ARRAY", "JSONB_GROUP_ARRAY", "JSON_GROUP_OBJECT", "JSONB_GROUP_OBJECT":
- return true
- case "MIN", "MAX":
- return argCount < 2
- }
- return false
- }
- // deterministicFunctions is the allowlist of scalar functions that may appear in
- // a persisted expression. It intentionally excludes date/time functions (which
- // are non-deterministic when using "now") and every random/session function.
- var deterministicFunctions = map[string]bool{
- "LOWER": true, "UPPER": true, "LENGTH": true, "ABS": true,
- "COALESCE": true, "NULLIF": true, "IFNULL": true, "NVL": true,
- "TYPEOF": true, "SUBSTR": true, "SUBSTRING": true, "TRIM": true,
- "REPLACE": true, "PRINTF": true, "HEX": true, "UNHEX": true,
- "ZEROBLOB": true, "INSTR": true, "GLOB": true, "ROUND": true,
- "MAX": true, "MIN": true, "CONCAT": true, "PERCENT_DIFF": true,
- // JSON1 scalar functions.
- "JSON": true, "JSONB": true, "JSON_VALID": true, "JSONB_VALID": true,
- "JSON_TYPE": true, "JSON_EXTRACT": true, "JSONB_EXTRACT": true,
- "JSON_SET": true, "JSONB_SET": true, "JSON_INSERT": true, "JSONB_INSERT": true,
- "JSON_REPLACE": true, "JSONB_REPLACE": true, "JSON_REMOVE": true, "JSONB_REMOVE": true,
- "JSON_ARRAY": true, "JSONB_ARRAY": true, "JSON_OBJECT": true, "JSONB_OBJECT": true,
- "JSON_QUOTE": true, "JSONB_QUOTE": true,
- }
- func isDeterministicFunction(name string) bool {
- return deterministicFunctions[strings.ToUpper(name)]
- }
- // ValidateIndexColumns checks that every column referenced by a stored
- // expression exists on the table (or is a hidden rowid alias). It is applied to
- // expression indexes and generated columns at creation time.
- func ValidateIndexColumns(expr parser.Expr, schema *storage.Schema) error {
- switch e := expr.(type) {
- case nil:
- return nil
- case *parser.ColumnRef:
- if e.Column == "*" {
- return fmt.Errorf("wildcards are not allowed in index expressions")
- }
- if storage.IsRowIDColumn(e.Column) {
- return nil
- }
- if _, ok := schema.GetColumn(e.Column); !ok {
- return fmt.Errorf("column not found in index expression: %s", e.Column)
- }
- return nil
- case *parser.ParenExpr:
- return ValidateIndexColumns(e.Expr, schema)
- case *parser.UnaryExpr:
- return ValidateIndexColumns(e.Operand, schema)
- case *parser.BinaryExpr:
- if err := ValidateIndexColumns(e.Left, schema); err != nil {
- return err
- }
- return ValidateIndexColumns(e.Right, schema)
- case *parser.IsNullExpr:
- return ValidateIndexColumns(e.Left, schema)
- case *parser.IsDistinctExpr:
- if err := ValidateIndexColumns(e.Left, schema); err != nil {
- return err
- }
- return ValidateIndexColumns(e.Right, schema)
- case *parser.BetweenExpr:
- if err := ValidateIndexColumns(e.Left, schema); err != nil {
- return err
- }
- if err := ValidateIndexColumns(e.Low, schema); err != nil {
- return err
- }
- return ValidateIndexColumns(e.High, schema)
- case *parser.LikeExpr:
- if err := ValidateIndexColumns(e.Left, schema); err != nil {
- return err
- }
- if err := ValidateIndexColumns(e.Pattern, schema); err != nil {
- return err
- }
- return ValidateIndexColumns(e.Escape, schema)
- case *parser.CaseExpr:
- if err := ValidateIndexColumns(e.Operand, schema); err != nil {
- return err
- }
- for _, w := range e.Whens {
- if err := ValidateIndexColumns(w.Condition, schema); err != nil {
- return err
- }
- if err := ValidateIndexColumns(w.Result, schema); err != nil {
- return err
- }
- }
- return ValidateIndexColumns(e.Else, schema)
- case *parser.CastExpr:
- return ValidateIndexColumns(e.Expr, schema)
- case *parser.InExpr:
- if err := ValidateIndexColumns(e.Left, schema); err != nil {
- return err
- }
- for _, v := range e.Values {
- if err := ValidateIndexColumns(v, schema); err != nil {
- return err
- }
- }
- return nil
- case *parser.FunctionCall:
- for _, a := range e.Args {
- if err := ValidateIndexColumns(a, schema); err != nil {
- return err
- }
- }
- return nil
- default:
- return nil
- }
- }
|