vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/plan_description.go (about)

     1  /*
     2  Copyright 2020 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package engine
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"sort"
    24  
    25  	"vitess.io/vitess/go/tools/graphviz"
    26  	"vitess.io/vitess/go/vt/key"
    27  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    28  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    29  )
    30  
    31  // PrimitiveDescription is used to create a serializable representation of the Primitive tree
    32  // Using this structure, all primitives can share json marshalling code, which gives us an uniform output
    33  type PrimitiveDescription struct {
    34  	OperatorType string
    35  	Variant      string
    36  	// Keyspace specifies the keyspace to send the query to.
    37  	Keyspace *vindexes.Keyspace
    38  	// TargetDestination specifies an explicit target destination to send the query to.
    39  	TargetDestination key.Destination
    40  	// TargetTabletType specifies an explicit target destination tablet type
    41  	// this is only used in conjunction with TargetDestination
    42  	TargetTabletType topodatapb.TabletType
    43  	Other            map[string]any
    44  	Inputs           []PrimitiveDescription
    45  }
    46  
    47  // MarshalJSON serializes the PlanDescription into a JSON representation.
    48  // We do this rather manual thing here so the `other` map looks like
    49  // fields belonging to pd and not a map in a field.
    50  func (pd PrimitiveDescription) MarshalJSON() ([]byte, error) {
    51  	buf := &bytes.Buffer{}
    52  	buf.WriteString("{")
    53  
    54  	if err := marshalAdd("", buf, "OperatorType", pd.OperatorType); err != nil {
    55  		return nil, err
    56  	}
    57  	if pd.Variant != "" {
    58  		if err := marshalAdd(",", buf, "Variant", pd.Variant); err != nil {
    59  			return nil, err
    60  		}
    61  	}
    62  	if pd.Keyspace != nil {
    63  		if err := marshalAdd(",", buf, "Keyspace", pd.Keyspace); err != nil {
    64  			return nil, err
    65  		}
    66  	}
    67  	if pd.TargetDestination != nil {
    68  		s := pd.TargetDestination.String()
    69  		dest := s[11:] // TODO: All these start with Destination. We should fix that instead if trimming it out here
    70  
    71  		if err := marshalAdd(",", buf, "TargetDestination", dest); err != nil {
    72  			return nil, err
    73  		}
    74  	}
    75  	if pd.TargetTabletType != topodatapb.TabletType_UNKNOWN {
    76  		if err := marshalAdd(",", buf, "TargetTabletType", pd.TargetTabletType.String()); err != nil {
    77  			return nil, err
    78  		}
    79  	}
    80  	err := addMap(pd.Other, buf)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	if len(pd.Inputs) > 0 {
    86  		if err := marshalAdd(",", buf, "Inputs", pd.Inputs); err != nil {
    87  			return nil, err
    88  		}
    89  	}
    90  
    91  	buf.WriteString("}")
    92  
    93  	return buf.Bytes(), nil
    94  }
    95  
    96  func (pd PrimitiveDescription) addToGraph(g *graphviz.Graph) (*graphviz.Node, error) {
    97  	var nodes []*graphviz.Node
    98  	for _, input := range pd.Inputs {
    99  		n, err := input.addToGraph(g)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		nodes = append(nodes, n)
   104  	}
   105  	name := pd.OperatorType + ":" + pd.Variant
   106  	if pd.Variant == "" {
   107  		name = pd.OperatorType
   108  	}
   109  	this := g.AddNode(name)
   110  	for k, v := range pd.Other {
   111  		switch k {
   112  		case "Query":
   113  			this.AddTooltip(fmt.Sprintf("%v", v))
   114  		case "FieldQuery":
   115  		// skip these
   116  		default:
   117  			slice, ok := v.([]string)
   118  			if ok {
   119  				this.AddAttribute(k)
   120  				for _, s := range slice {
   121  					this.AddAttribute(s)
   122  				}
   123  			} else {
   124  				this.AddAttribute(fmt.Sprintf("%s:%v", k, v))
   125  			}
   126  		}
   127  	}
   128  	for _, n := range nodes {
   129  		g.AddEdge(this, n)
   130  	}
   131  	return this, nil
   132  }
   133  
   134  func GraphViz(p Primitive) (*graphviz.Graph, error) {
   135  	g := graphviz.New()
   136  	description := PrimitiveToPlanDescription(p)
   137  	_, err := description.addToGraph(g)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return g, nil
   142  }
   143  
   144  func addMap(input map[string]any, buf *bytes.Buffer) error {
   145  	var mk []string
   146  	for k, v := range input {
   147  		if v == "" || v == nil || v == 0 {
   148  			continue
   149  		}
   150  		mk = append(mk, k)
   151  	}
   152  	sort.Strings(mk)
   153  	for _, k := range mk {
   154  		v := input[k]
   155  		if err := marshalAdd(",", buf, k, v); err != nil {
   156  			return err
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func marshalAdd(prepend string, buf *bytes.Buffer, name string, obj any) error {
   163  	buf.WriteString(prepend + `"` + name + `":`)
   164  
   165  	enc := json.NewEncoder(buf)
   166  	enc.SetEscapeHTML(false)
   167  
   168  	return enc.Encode(obj)
   169  }
   170  
   171  // PrimitiveToPlanDescription transforms a primitive tree into a corresponding PlanDescription tree
   172  func PrimitiveToPlanDescription(in Primitive) PrimitiveDescription {
   173  	this := in.description()
   174  
   175  	for _, input := range in.Inputs() {
   176  		this.Inputs = append(this.Inputs, PrimitiveToPlanDescription(input))
   177  	}
   178  
   179  	if len(in.Inputs()) == 0 {
   180  		this.Inputs = []PrimitiveDescription{}
   181  	}
   182  
   183  	return this
   184  }
   185  
   186  func orderedStringIntMap(in map[string]int) orderedMap {
   187  	result := make(orderedMap, 0, len(in))
   188  	for k, v := range in {
   189  		result = append(result, keyVal{key: k, val: v})
   190  	}
   191  	sort.Sort(result)
   192  	return result
   193  }
   194  
   195  type keyVal struct {
   196  	key string
   197  	val any
   198  }
   199  
   200  // Define an ordered, sortable map
   201  type orderedMap []keyVal
   202  
   203  func (m orderedMap) Len() int {
   204  	return len(m)
   205  }
   206  
   207  func (m orderedMap) Less(i, j int) bool {
   208  	return m[i].key < m[j].key
   209  }
   210  
   211  func (m orderedMap) Swap(i, j int) {
   212  	m[i], m[j] = m[j], m[i]
   213  }
   214  
   215  var _ sort.Interface = (orderedMap)(nil)
   216  
   217  func (m orderedMap) MarshalJSON() ([]byte, error) {
   218  	var buf bytes.Buffer
   219  
   220  	buf.WriteString("{")
   221  	for i, kv := range m {
   222  		if i != 0 {
   223  			buf.WriteString(",")
   224  		}
   225  		// marshal key
   226  		key, err := json.Marshal(kv.key)
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		buf.Write(key)
   231  		buf.WriteString(":")
   232  		// marshal value
   233  		val, err := json.Marshal(kv.val)
   234  		if err != nil {
   235  			return nil, err
   236  		}
   237  		buf.Write(val)
   238  	}
   239  
   240  	buf.WriteString("}")
   241  	return buf.Bytes(), nil
   242  }