github.com/dolthub/go-mysql-server@v0.18.0/sql/analyzer/batch.go (about)

     1  // Copyright 2020-2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package analyzer
    16  
    17  import (
    18  	"reflect"
    19  
    20  	"github.com/dolthub/go-mysql-server/sql"
    21  	"github.com/dolthub/go-mysql-server/sql/plan"
    22  	"github.com/dolthub/go-mysql-server/sql/transform"
    23  )
    24  
    25  // RuleFunc is the function to be applied in a rule.
    26  type RuleFunc func(*sql.Context, *Analyzer, sql.Node, *plan.Scope, RuleSelector) (sql.Node, transform.TreeIdentity, error)
    27  
    28  // RuleSelector filters analysis rules by id
    29  type RuleSelector func(RuleId) bool
    30  
    31  // Rule to transform nodes.
    32  type Rule struct {
    33  	// Name of the rule.
    34  	Id RuleId
    35  	// Apply transforms a node.
    36  	Apply RuleFunc
    37  }
    38  
    39  // BatchSelector filters analysis batches by name
    40  type BatchSelector func(string) bool
    41  
    42  // Batch executes a set of rules a specific number of times.
    43  // When this number of times is reached, the actual node
    44  // and ErrMaxAnalysisIters is returned.
    45  type Batch struct {
    46  	Desc       string
    47  	Iterations int
    48  	Rules      []Rule
    49  }
    50  
    51  // Eval executes the rules of the batch. On any error, the partially transformed node is returned along with the error.
    52  // If the batch's max number of iterations is reached without achieving stabilization (batch evaluation no longer
    53  // changes the node), then this method returns ErrMaxAnalysisIters.
    54  func (b *Batch) Eval(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
    55  	return b.EvalWithSelector(ctx, a, n, scope, sel)
    56  }
    57  
    58  func (b *Batch) EvalWithSelector(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
    59  	if b.Iterations == 0 {
    60  		return n, transform.SameTree, nil
    61  	}
    62  	a.PushDebugContext("0")
    63  	cur, _, err := b.evalOnce(ctx, a, n, scope, sel)
    64  	a.PopDebugContext()
    65  	if err != nil {
    66  		return cur, transform.SameTree, err
    67  	}
    68  	return cur, transform.NewTree, nil
    69  }
    70  
    71  // evalOnce returns the result of evaluating a batch of rules on the node given. In the result of an error, the result
    72  // of the last successful transformation is returned along with the error. If no transformation was successful, the
    73  // input node is returned as-is.
    74  func (b *Batch) evalOnce(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector) (sql.Node, transform.TreeIdentity, error) {
    75  	var (
    76  		same    = transform.SameTree
    77  		allSame = transform.SameTree
    78  		next    sql.Node
    79  		prev    = n
    80  	)
    81  	for _, rule := range b.Rules {
    82  		if !sel(rule.Id) {
    83  			a.Log("Skipping rule %s", rule.Id)
    84  			continue
    85  		}
    86  		var err error
    87  		a.Log("Evaluating rule %s", rule.Id)
    88  		a.PushDebugContext(rule.Id.String())
    89  		next, same, err = rule.Apply(ctx, a, prev, scope, sel)
    90  		allSame = same && allSame
    91  		if next != nil && !same {
    92  			a.LogNode(next)
    93  			// We should only do this if the result has changed, but some rules currently misbehave and falsely report nothing
    94  			// changed
    95  			a.LogDiff(prev, next)
    96  		}
    97  		a.PopDebugContext()
    98  		if err != nil {
    99  			// Returning the last node before the error is important. This is non-idiomatic, but in the case of partial
   100  			// resolution before an error we want the last successful transformation result. Very important for resolving
   101  			// subqueries.
   102  			return prev, allSame, err
   103  		}
   104  		prev = next
   105  	}
   106  
   107  	return prev, allSame, nil
   108  }
   109  
   110  func nodesEqual(a, b sql.Node) bool {
   111  	if e, ok := a.(equaler); ok {
   112  		return e.Equal(b)
   113  	}
   114  
   115  	if e, ok := b.(equaler); ok {
   116  		return e.Equal(a)
   117  	}
   118  
   119  	return reflect.DeepEqual(a, b)
   120  }
   121  
   122  type equaler interface {
   123  	Equal(sql.Node) bool
   124  }