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 }