github.com/matrixorigin/matrixone@v1.2.0/pkg/sql/plan/explain/explain_node.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     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 explain
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  	"strconv"
    23  
    24  	plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan"
    25  
    26  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    27  	"github.com/matrixorigin/matrixone/pkg/pb/plan"
    28  )
    29  
    30  var _ NodeDescribe = &NodeDescribeImpl{}
    31  
    32  const MB = 1024 * 1024
    33  const GB = MB * 1024
    34  const MILLION = 1000000
    35  
    36  type NodeDescribeImpl struct {
    37  	Node *plan.Node
    38  }
    39  
    40  func NewNodeDescriptionImpl(node *plan.Node) *NodeDescribeImpl {
    41  	return &NodeDescribeImpl{
    42  		Node: node,
    43  	}
    44  }
    45  
    46  const TableScan = "Table Scan"
    47  const ExternalScan = "External Scan"
    48  
    49  func (ndesc *NodeDescribeImpl) GetNodeBasicInfo(ctx context.Context, options *ExplainOptions) (string, error) {
    50  	buf := bytes.NewBuffer(make([]byte, 0, 400))
    51  	var pname string /* node type name for text output */
    52  
    53  	// Get the Node Name
    54  	switch ndesc.Node.NodeType {
    55  	case plan.Node_UNKNOWN:
    56  		pname = "UnKnow Node"
    57  	case plan.Node_VALUE_SCAN:
    58  		pname = "Values Scan"
    59  	case plan.Node_TABLE_SCAN:
    60  		pname = TableScan
    61  	case plan.Node_EXTERNAL_SCAN:
    62  		pname = ExternalScan
    63  	case plan.Node_SOURCE_SCAN:
    64  		pname = "Source Scan"
    65  	case plan.Node_MATERIAL_SCAN:
    66  		pname = "Material Scan"
    67  	case plan.Node_PROJECT:
    68  		pname = "Project"
    69  	case plan.Node_EXTERNAL_FUNCTION:
    70  		pname = "External Function"
    71  	case plan.Node_MATERIAL:
    72  		pname = "Material"
    73  	case plan.Node_SINK:
    74  		pname = "Sink"
    75  	case plan.Node_SINK_SCAN:
    76  		pname = "Sink Scan"
    77  	case plan.Node_RECURSIVE_SCAN:
    78  		pname = "Recursive Scan"
    79  	case plan.Node_RECURSIVE_CTE:
    80  		pname = "CTE Scan"
    81  	case plan.Node_AGG:
    82  		pname = "Aggregate"
    83  	case plan.Node_DISTINCT:
    84  		pname = "Distinct"
    85  	case plan.Node_FILTER:
    86  		pname = "Filter"
    87  	case plan.Node_JOIN:
    88  		pname = "Join"
    89  	case plan.Node_SAMPLE:
    90  		pname = "Sample"
    91  	case plan.Node_SORT:
    92  		pname = "Sort"
    93  	case plan.Node_PARTITION:
    94  		pname = "Partition"
    95  	case plan.Node_UNION:
    96  		pname = "Union"
    97  	case plan.Node_UNION_ALL:
    98  		pname = "Union All"
    99  	case plan.Node_UNIQUE:
   100  		pname = "Unique"
   101  	case plan.Node_WINDOW:
   102  		pname = "Window"
   103  	case plan.Node_TIME_WINDOW:
   104  		pname = "Time window"
   105  	case plan.Node_FILL:
   106  		pname = "Fill"
   107  	case plan.Node_BROADCAST:
   108  		pname = "Broadcast"
   109  	case plan.Node_SPLIT:
   110  		pname = "Split"
   111  	case plan.Node_GATHER:
   112  		pname = "Gather"
   113  	case plan.Node_ASSERT:
   114  		pname = "Assert"
   115  	case plan.Node_INSERT:
   116  		pname = "Insert"
   117  	case plan.Node_DELETE:
   118  		pname = "Delete"
   119  	case plan.Node_INTERSECT:
   120  		pname = "Intersect"
   121  	case plan.Node_INTERSECT_ALL:
   122  		pname = "Intersect All"
   123  	case plan.Node_MINUS:
   124  		pname = "Minus"
   125  	case plan.Node_MINUS_ALL:
   126  		pname = "Minus All"
   127  	case plan.Node_FUNCTION_SCAN:
   128  		pname = "Table Function"
   129  	case plan.Node_PRE_INSERT:
   130  		pname = "PreInsert"
   131  	case plan.Node_PRE_INSERT_UK:
   132  		pname = "PreInsert UniqueKey"
   133  	case plan.Node_PRE_INSERT_SK:
   134  		pname = "PreInsert SecondaryKey"
   135  	case plan.Node_PRE_DELETE:
   136  		pname = "PreDelete"
   137  	case plan.Node_ON_DUPLICATE_KEY:
   138  		pname = "On Duplicate Key"
   139  	case plan.Node_FUZZY_FILTER:
   140  		pname = "Fuzzy Filter for duplicate key"
   141  	case plan.Node_LOCK_OP:
   142  		pname = "Lock"
   143  	default:
   144  		panic("error node type")
   145  	}
   146  
   147  	// Get Node's operator object info ,such as table, view
   148  	if options.Format == EXPLAIN_FORMAT_TEXT {
   149  		buf.WriteString(pname)
   150  		switch ndesc.Node.NodeType {
   151  		case plan.Node_VALUE_SCAN:
   152  			buf.WriteString(" \"*VALUES*\" ")
   153  		case plan.Node_TABLE_SCAN, plan.Node_EXTERNAL_SCAN, plan.Node_MATERIAL_SCAN, plan.Node_INSERT, plan.Node_SOURCE_SCAN:
   154  			buf.WriteString(" on ")
   155  			if ndesc.Node.ObjRef != nil {
   156  				buf.WriteString(ndesc.Node.ObjRef.GetSchemaName() + "." + ndesc.Node.ObjRef.GetObjName())
   157  			} else if ndesc.Node.TableDef != nil {
   158  				buf.WriteString(ndesc.Node.TableDef.GetName())
   159  			}
   160  			if ndesc.Node.Stats.ForceOneCN {
   161  				buf.WriteString(" [ForceOneCN]")
   162  			}
   163  		case plan.Node_FUNCTION_SCAN:
   164  			buf.WriteString(" on ")
   165  			if ndesc.Node.TableDef != nil && ndesc.Node.TableDef.TblFunc != nil {
   166  				buf.WriteString(ndesc.Node.TableDef.TblFunc.Name)
   167  			}
   168  		case plan.Node_DELETE:
   169  			buf.WriteString(" on ")
   170  			if ndesc.Node.DeleteCtx != nil {
   171  				ctx := ndesc.Node.DeleteCtx.Ref
   172  				buf.WriteString(ctx.SchemaName + "." + ctx.ObjName)
   173  			}
   174  		case plan.Node_PRE_INSERT:
   175  			buf.WriteString(" on ")
   176  			if ndesc.Node.PreInsertCtx != nil {
   177  				if ndesc.Node.PreInsertCtx.Ref != nil {
   178  					buf.WriteString(ndesc.Node.PreInsertCtx.Ref.GetSchemaName() + "." + ndesc.Node.PreInsertCtx.Ref.GetObjName())
   179  				} else if ndesc.Node.PreInsertCtx.TableDef != nil {
   180  					buf.WriteString(ndesc.Node.TableDef.GetName())
   181  				}
   182  			}
   183  		case plan.Node_PRE_DELETE:
   184  			buf.WriteString(" on ")
   185  			if ndesc.Node.ObjRef != nil {
   186  				buf.WriteString(ndesc.Node.ObjRef.GetSchemaName() + "." + ndesc.Node.ObjRef.GetObjName())
   187  			} else if ndesc.Node.TableDef != nil {
   188  				buf.WriteString(ndesc.Node.TableDef.GetName())
   189  			}
   190  		}
   191  	}
   192  
   193  	// Get Costs info of Node
   194  	if options.Format == EXPLAIN_FORMAT_TEXT {
   195  		//result += " (cost=%.2f..%.2f rows=%.0f width=%f)"
   196  		if options.Verbose {
   197  			costDescImpl := &CostDescribeImpl{
   198  				Stats: ndesc.Node.GetStats(),
   199  			}
   200  			err := costDescImpl.GetDescription(ctx, options, buf)
   201  			if err != nil {
   202  				return "", err
   203  			}
   204  		}
   205  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   206  		return "", moerr.NewNYI(ctx, "explain format json")
   207  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   208  		return "", moerr.NewNYI(ctx, "explain format dot")
   209  	}
   210  	return buf.String(), nil
   211  }
   212  
   213  func (ndesc *NodeDescribeImpl) GetActualAnalyzeInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   214  	buf := bytes.NewBuffer(make([]byte, 0, 400))
   215  	buf.WriteString("Analyze: ")
   216  	if ndesc.Node.AnalyzeInfo != nil {
   217  		impl := NewAnalyzeInfoDescribeImpl(ndesc.Node.AnalyzeInfo)
   218  		options.NodeType = ndesc.Node.NodeType
   219  		err := impl.GetDescription(ctx, options, buf)
   220  		if err != nil {
   221  			return "", err
   222  		}
   223  	} else {
   224  		buf.WriteString("timeConsumed=0ns waitTime=0ns inputRows=0  outputRows=0 inputSize=0 bytes outputSize:0 bytes, memorySize=0 bytes")
   225  	}
   226  	return buf.String(), nil
   227  }
   228  
   229  func (ndesc *NodeDescribeImpl) GetTableDef(ctx context.Context, options *ExplainOptions) (string, error) {
   230  	result := "Table: "
   231  	if ndesc.Node.NodeType == plan.Node_TABLE_SCAN {
   232  		tableDef := ndesc.Node.TableDef
   233  		result += "'" + tableDef.Name + "' ("
   234  		first := true
   235  		for i, col := range tableDef.Cols {
   236  			if !first {
   237  				result += ", "
   238  			}
   239  			first = false
   240  			result += strconv.Itoa(i) + ":'" + col.Name + "'"
   241  		}
   242  		result += ")"
   243  	} else {
   244  		panic("implement me")
   245  	}
   246  	return result, nil
   247  }
   248  
   249  func (ndesc *NodeDescribeImpl) GetExtraInfo(ctx context.Context, options *ExplainOptions) ([]string, error) {
   250  	lines := make([]string, 0)
   251  
   252  	// Get partition prune information
   253  	if ndesc.Node.NodeType == plan.Node_TABLE_SCAN && ndesc.Node.TableDef.Partition != nil {
   254  		partPruneInfo, err := ndesc.GetPartitionPruneInfo(ctx, options)
   255  		if err != nil {
   256  			return nil, err
   257  		}
   258  		lines = append(lines, partPruneInfo)
   259  	}
   260  
   261  	// Get Sort list info
   262  	if len(ndesc.Node.OrderBy) > 0 {
   263  		orderByInfo, err := ndesc.GetOrderByInfo(ctx, options)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  		lines = append(lines, orderByInfo)
   268  	}
   269  
   270  	// Get Join type info
   271  	if ndesc.Node.NodeType == plan.Node_JOIN {
   272  		joinTypeInfo, err := ndesc.GetJoinTypeInfo(ctx, options)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  		lines = append(lines, joinTypeInfo)
   277  	}
   278  
   279  	// Get Join Condition info
   280  	if len(ndesc.Node.OnList) > 0 {
   281  		joinOnInfo, err := ndesc.GetJoinConditionInfo(ctx, options)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		lines = append(lines, joinOnInfo)
   286  	}
   287  
   288  	// Get Group key info
   289  	if len(ndesc.Node.GroupBy) > 0 {
   290  		groupByInfo, err := ndesc.GetGroupByInfo(ctx, options)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		lines = append(lines, groupByInfo)
   295  	}
   296  
   297  	if ndesc.Node.NodeType == plan.Node_FILL {
   298  		fillCoslInfo, err := ndesc.GetFillColsInfo(ctx, options)
   299  		if err != nil {
   300  			return nil, err
   301  		}
   302  		lines = append(lines, fillCoslInfo)
   303  		fillModelInfo, err := ndesc.GetFillModeInfo(ctx, options)
   304  		if err != nil {
   305  			return nil, err
   306  		}
   307  		lines = append(lines, fillModelInfo)
   308  	}
   309  
   310  	// Get Aggregate function info
   311  	if len(ndesc.Node.AggList) > 0 && ndesc.Node.NodeType != plan.Node_FILL {
   312  		var listInfo string
   313  		var err error
   314  		if ndesc.Node.NodeType == plan.Node_SAMPLE {
   315  			listInfo, err = ndesc.GetSampleFuncInfo(ctx, options)
   316  		} else {
   317  			listInfo, err = ndesc.GetAggregationInfo(ctx, options)
   318  		}
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		lines = append(lines, listInfo)
   323  	}
   324  
   325  	// Get Window function info
   326  	if ndesc.Node.NodeType == plan.Node_WINDOW {
   327  		windowSpecListInfo, err := ndesc.GetWindowSpectListInfo(ctx, options)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  		lines = append(lines, windowSpecListInfo)
   332  	}
   333  
   334  	// Get Filter list info
   335  	if len(ndesc.Node.FilterList) > 0 {
   336  		filterInfo, err := ndesc.GetFilterConditionInfo(ctx, options)
   337  		if err != nil {
   338  			return nil, err
   339  		}
   340  		lines = append(lines, filterInfo)
   341  	}
   342  
   343  	// Get Block Filter list info
   344  	if len(ndesc.Node.BlockFilterList) > 0 {
   345  		filterInfo, err := ndesc.GetBlockFilterConditionInfo(ctx, options)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  		lines = append(lines, filterInfo)
   350  	}
   351  
   352  	if len(ndesc.Node.RuntimeFilterProbeList) > 0 {
   353  		filterInfo, err := ndesc.GetRuntimeFilteProbeInfo(ctx, options)
   354  		if err != nil {
   355  			return nil, err
   356  		}
   357  		lines = append(lines, filterInfo)
   358  	}
   359  
   360  	if len(ndesc.Node.RuntimeFilterBuildList) > 0 {
   361  		filterInfo, err := ndesc.GetRuntimeFilterBuildInfo(ctx, options)
   362  		if err != nil {
   363  			return nil, err
   364  		}
   365  		lines = append(lines, filterInfo)
   366  	}
   367  
   368  	// Get Limit And Offset info
   369  	if ndesc.Node.Limit != nil {
   370  		buf := bytes.NewBuffer(make([]byte, 0, 160))
   371  		buf.WriteString("Limit: ")
   372  		err := describeExpr(ctx, ndesc.Node.Limit, options, buf)
   373  		if err != nil {
   374  			return nil, err
   375  		}
   376  		if ndesc.Node.Offset != nil {
   377  			buf.WriteString(", Offset: ")
   378  			err := describeExpr(ctx, ndesc.Node.Offset, options, buf)
   379  			if err != nil {
   380  				return nil, err
   381  			}
   382  		}
   383  		lines = append(lines, buf.String())
   384  	}
   385  
   386  	//if ndesc.Node.UpdateList != nil {
   387  	//	updateListDesc := &UpdateListDescribeImpl{
   388  	//		UpdateList: ndesc.Node.UpdateList,
   389  	//	}
   390  	//	updatedesc, err := updateListDesc.GetDescription(options)
   391  	//	if err != nil {
   392  	//		return nil, err
   393  	//	}
   394  	//	lines = append(lines, "Set columns with("+updatedesc+")")
   395  	//}
   396  
   397  	if len(ndesc.Node.SendMsgList) > 0 {
   398  		msgInfo, err := ndesc.GetSendMessageInfo(ctx, options)
   399  		if err != nil {
   400  			return nil, err
   401  		}
   402  		lines = append(lines, msgInfo)
   403  	}
   404  
   405  	if len(ndesc.Node.RecvMsgList) > 0 {
   406  		msgInfo, err := ndesc.GetRecvMessageInfo(ctx, options)
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  		lines = append(lines, msgInfo)
   411  	}
   412  	return lines, nil
   413  }
   414  
   415  func (ndesc *NodeDescribeImpl) GetProjectListInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   416  	buf := bytes.NewBuffer(make([]byte, 0, 400))
   417  	buf.WriteString("Output: ")
   418  	exprs := NewExprListDescribeImpl(ndesc.Node.ProjectList)
   419  	err := exprs.GetDescription(ctx, options, buf)
   420  	if err != nil {
   421  		return "", err
   422  	}
   423  	return buf.String(), nil
   424  }
   425  
   426  func (ndesc *NodeDescribeImpl) GetJoinTypeInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   427  	result := "Join Type: " + ndesc.Node.JoinType.String()
   428  	if ndesc.Node.BuildOnLeft {
   429  		if ndesc.Node.JoinType == plan.Node_SEMI || ndesc.Node.JoinType == plan.Node_ANTI {
   430  			result = "Join Type: RIGHT " + ndesc.Node.JoinType.String()
   431  		}
   432  	}
   433  	if ndesc.Node.Stats.HashmapStats != nil && ndesc.Node.Stats.HashmapStats.HashOnPK {
   434  		result += "   hashOnPK"
   435  	}
   436  	return result, nil
   437  }
   438  
   439  func (ndesc *NodeDescribeImpl) GetJoinConditionInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   440  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   441  	buf.WriteString("Join Cond: ")
   442  	exprs := NewExprListDescribeImpl(ndesc.Node.OnList)
   443  	err := exprs.GetDescription(ctx, options, buf)
   444  	if err != nil {
   445  		return "", err
   446  	}
   447  
   448  	if ndesc.Node.Stats.HashmapStats.Shuffle {
   449  		idx := ndesc.Node.Stats.HashmapStats.ShuffleColIdx
   450  		shuffleType := ndesc.Node.Stats.HashmapStats.ShuffleType
   451  		var hashCol *plan.Expr
   452  		switch exprImpl := ndesc.Node.OnList[idx].Expr.(type) {
   453  		case *plan.Expr_F:
   454  			hashCol = exprImpl.F.Args[0]
   455  		}
   456  
   457  		if shuffleType == plan.ShuffleType_Hash {
   458  			buf.WriteString(" shuffle: hash(")
   459  			err := describeExpr(ctx, hashCol, options, buf)
   460  			if err != nil {
   461  				return "", err
   462  			}
   463  			buf.WriteString(")")
   464  		} else {
   465  			buf.WriteString(" shuffle: range(")
   466  			err := describeExpr(ctx, hashCol, options, buf)
   467  			if err != nil {
   468  				return "", err
   469  			}
   470  			buf.WriteString(")")
   471  		}
   472  
   473  		if ndesc.Node.Stats.HashmapStats.ShuffleTypeForMultiCN == plan.ShuffleTypeForMultiCN_Hybrid {
   474  			buf.WriteString(" HYBRID ")
   475  		}
   476  	}
   477  
   478  	return buf.String(), nil
   479  }
   480  
   481  func (ndesc *NodeDescribeImpl) GetPartitionPruneInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   482  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   483  	buf.WriteString("Hit Partition: ")
   484  	if options.Format == EXPLAIN_FORMAT_TEXT {
   485  		if ndesc.Node.PartitionPrune != nil {
   486  			first := true
   487  			for _, v := range ndesc.Node.PartitionPrune.SelectedPartitions {
   488  				if !first {
   489  					buf.WriteString(", ")
   490  				}
   491  				first = false
   492  				buf.WriteString(v.PartitionName)
   493  			}
   494  		} else {
   495  			buf.WriteString("all partitions")
   496  		}
   497  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   498  		return "", moerr.NewNYI(ctx, "explain format json")
   499  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   500  		return "", moerr.NewNYI(ctx, "explain format dot")
   501  	}
   502  	return buf.String(), nil
   503  }
   504  
   505  func (ndesc *NodeDescribeImpl) GetFilterConditionInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   506  	buf := bytes.NewBuffer(make([]byte, 0, 512))
   507  	buf.WriteString("Filter Cond: ")
   508  	if options.Format == EXPLAIN_FORMAT_TEXT {
   509  		first := true
   510  		for _, v := range ndesc.Node.FilterList {
   511  			if !first {
   512  				buf.WriteString(", ")
   513  			}
   514  			first = false
   515  			err := describeExpr(ctx, v, options, buf)
   516  			if err != nil {
   517  				return "", err
   518  			}
   519  		}
   520  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   521  		return "", moerr.NewNYI(ctx, "explain format json")
   522  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   523  		return "", moerr.NewNYI(ctx, "explain format dot")
   524  	}
   525  	return buf.String(), nil
   526  }
   527  
   528  func (ndesc *NodeDescribeImpl) GetBlockFilterConditionInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   529  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   530  	buf.WriteString("Block Filter Cond: ")
   531  	if options.Format == EXPLAIN_FORMAT_TEXT {
   532  		first := true
   533  		for _, v := range ndesc.Node.BlockFilterList {
   534  			if !first {
   535  				buf.WriteString(", ")
   536  			}
   537  			first = false
   538  			err := describeExpr(ctx, v, options, buf)
   539  			if err != nil {
   540  				return "", err
   541  			}
   542  		}
   543  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   544  		return "", moerr.NewNYI(ctx, "explain format json")
   545  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   546  		return "", moerr.NewNYI(ctx, "explain format dot")
   547  	}
   548  	return buf.String(), nil
   549  }
   550  
   551  func (ndesc *NodeDescribeImpl) GetRuntimeFilteProbeInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   552  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   553  	buf.WriteString("Runtime Filter Probe: ")
   554  	if options.Format == EXPLAIN_FORMAT_TEXT {
   555  		first := true
   556  		for _, v := range ndesc.Node.RuntimeFilterProbeList {
   557  			if !first {
   558  				buf.WriteString(", ")
   559  			}
   560  			first = false
   561  			err := describeExpr(ctx, v.Expr, options, buf)
   562  			if err != nil {
   563  				return "", err
   564  			}
   565  			if v.MatchPrefix {
   566  				buf.WriteString(" Match Prefix")
   567  			}
   568  		}
   569  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   570  		return "", moerr.NewNYI(ctx, "explain format json")
   571  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   572  		return "", moerr.NewNYI(ctx, "explain format dot")
   573  	}
   574  	return buf.String(), nil
   575  }
   576  
   577  func (ndesc *NodeDescribeImpl) GetRuntimeFilterBuildInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   578  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   579  	buf.WriteString("Runtime Filter Build: ")
   580  	if options.Format == EXPLAIN_FORMAT_TEXT {
   581  		first := true
   582  		for _, v := range ndesc.Node.RuntimeFilterBuildList {
   583  			if !first {
   584  				buf.WriteString(", ")
   585  			}
   586  			first = false
   587  			err := describeExpr(ctx, v.Expr, options, buf)
   588  			if err != nil {
   589  				return "", err
   590  			}
   591  		}
   592  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   593  		return "", moerr.NewNYI(ctx, "explain format json")
   594  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   595  		return "", moerr.NewNYI(ctx, "explain format dot")
   596  	}
   597  	return buf.String(), nil
   598  }
   599  
   600  func (ndesc *NodeDescribeImpl) GetSendMessageInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   601  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   602  	buf.WriteString("Send Message: ")
   603  	if options.Format == EXPLAIN_FORMAT_TEXT {
   604  		first := true
   605  		for _, v := range ndesc.Node.SendMsgList {
   606  			if !first {
   607  				buf.WriteString(", ")
   608  			}
   609  			first = false
   610  			describeMessage(v, buf)
   611  		}
   612  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   613  		return "", moerr.NewNYI(ctx, "explain format json")
   614  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   615  		return "", moerr.NewNYI(ctx, "explain format dot")
   616  	}
   617  	return buf.String(), nil
   618  }
   619  
   620  func (ndesc *NodeDescribeImpl) GetRecvMessageInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   621  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   622  	buf.WriteString("Recv Message: ")
   623  	if options.Format == EXPLAIN_FORMAT_TEXT {
   624  		first := true
   625  		for _, v := range ndesc.Node.RecvMsgList {
   626  			if !first {
   627  				buf.WriteString(", ")
   628  			}
   629  			first = false
   630  			describeMessage(v, buf)
   631  		}
   632  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   633  		return "", moerr.NewNYI(ctx, "explain format json")
   634  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   635  		return "", moerr.NewNYI(ctx, "explain format dot")
   636  	}
   637  	return buf.String(), nil
   638  }
   639  
   640  func (ndesc *NodeDescribeImpl) GetGroupByInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   641  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   642  	buf.WriteString("Group Key: ")
   643  	if options.Format == EXPLAIN_FORMAT_TEXT {
   644  		first := true
   645  		for _, v := range ndesc.Node.GetGroupBy() {
   646  			if !first {
   647  				buf.WriteString(", ")
   648  			}
   649  			first = false
   650  			err := describeExpr(ctx, v, options, buf)
   651  			if err != nil {
   652  				return "", err
   653  			}
   654  		}
   655  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   656  		return "", moerr.NewNYI(ctx, "explain format json")
   657  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   658  		return "", moerr.NewNYI(ctx, "explain format dot")
   659  	}
   660  
   661  	if ndesc.Node.Stats.HashmapStats != nil && ndesc.Node.Stats.HashmapStats.Shuffle {
   662  		idx := ndesc.Node.Stats.HashmapStats.ShuffleColIdx
   663  		shuffleType := ndesc.Node.Stats.HashmapStats.ShuffleType
   664  		if ndesc.Node.Stats.HashmapStats.ShuffleMethod != plan.ShuffleMethod_Reuse {
   665  			if shuffleType == plan.ShuffleType_Hash {
   666  				buf.WriteString(" shuffle: hash(")
   667  				err := describeExpr(ctx, ndesc.Node.GroupBy[idx], options, buf)
   668  				if err != nil {
   669  					return "", err
   670  				}
   671  				buf.WriteString(")")
   672  			} else {
   673  				buf.WriteString(" shuffle: range(")
   674  				err := describeExpr(ctx, ndesc.Node.GroupBy[idx], options, buf)
   675  				if err != nil {
   676  					return "", err
   677  				}
   678  				buf.WriteString(")")
   679  			}
   680  		}
   681  
   682  		if ndesc.Node.Stats.HashmapStats.ShuffleMethod == plan.ShuffleMethod_Reuse {
   683  			buf.WriteString(" shuffle: REUSE ")
   684  		} else if ndesc.Node.Stats.HashmapStats.ShuffleMethod == plan.ShuffleMethod_Reshuffle {
   685  			buf.WriteString(" RESHUFFLE ")
   686  		}
   687  	}
   688  	return buf.String(), nil
   689  }
   690  
   691  func (ndesc *NodeDescribeImpl) GetAggregationInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   692  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   693  	buf.WriteString("Aggregate Functions: ")
   694  	if options.Format == EXPLAIN_FORMAT_TEXT {
   695  		first := true
   696  		for _, v := range ndesc.Node.GetAggList() {
   697  			if !first {
   698  				buf.WriteString(", ")
   699  			}
   700  			first = false
   701  			err := describeExpr(ctx, v, options, buf)
   702  			if err != nil {
   703  				return "", err
   704  			}
   705  		}
   706  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   707  		return "", moerr.NewNYI(ctx, "explain format json")
   708  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   709  		return "", moerr.NewNYI(ctx, "explain format dot")
   710  	}
   711  	return buf.String(), nil
   712  }
   713  
   714  func (ndesc *NodeDescribeImpl) GetSampleFuncInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   715  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   716  	if ndesc.Node.SampleFunc.Rows == plan2.NotSampleByRows {
   717  		buf.WriteString(fmt.Sprintf("Sample %.2f Percent by: ", ndesc.Node.SampleFunc.Percent))
   718  	} else {
   719  		buf.WriteString(fmt.Sprintf("Sample %d Rows by: ", ndesc.Node.SampleFunc.Rows))
   720  	}
   721  
   722  	if options.Format == EXPLAIN_FORMAT_TEXT {
   723  		first := true
   724  		for _, v := range ndesc.Node.GetAggList() {
   725  			if !first {
   726  				buf.WriteString(", ")
   727  			}
   728  			first = false
   729  			err := describeExpr(ctx, v, options, buf)
   730  			if err != nil {
   731  				return "", err
   732  			}
   733  		}
   734  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   735  		return "", moerr.NewNYI(ctx, "explain format json")
   736  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   737  		return "", moerr.NewNYI(ctx, "explain format dot")
   738  	}
   739  	return buf.String(), nil
   740  }
   741  
   742  func (ndesc *NodeDescribeImpl) GetFillColsInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   743  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   744  	buf.WriteString("Fill Columns: ")
   745  	if options.Format == EXPLAIN_FORMAT_TEXT {
   746  		first := true
   747  		for _, v := range ndesc.Node.GetAggList() {
   748  			if !first {
   749  				buf.WriteString(", ")
   750  			}
   751  			first = false
   752  			err := describeExpr(ctx, v, options, buf)
   753  			if err != nil {
   754  				return "", err
   755  			}
   756  		}
   757  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   758  		return "", moerr.NewNYI(ctx, "explain format json")
   759  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   760  		return "", moerr.NewNYI(ctx, "explain format dot")
   761  	}
   762  	return buf.String(), nil
   763  }
   764  
   765  func (ndesc *NodeDescribeImpl) GetFillModeInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   766  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   767  	buf.WriteString("Fill Mode: ")
   768  	if options.Format == EXPLAIN_FORMAT_TEXT {
   769  		switch ndesc.Node.FillType {
   770  		case plan.Node_NONE:
   771  			buf.WriteString("None")
   772  		case plan.Node_LINEAR:
   773  			buf.WriteString("Linear")
   774  		case plan.Node_NULL:
   775  			buf.WriteString("Null")
   776  		case plan.Node_VALUE:
   777  			buf.WriteString("Value")
   778  		case plan.Node_PREV:
   779  			buf.WriteString("Prev")
   780  		case plan.Node_NEXT:
   781  			buf.WriteString("Next")
   782  		}
   783  		if len(ndesc.Node.FillVal) > 0 {
   784  			buf.WriteString(" Fill Value: ")
   785  			first := true
   786  			for _, v := range ndesc.Node.GetFillVal() {
   787  				if !first {
   788  					buf.WriteString(", ")
   789  				}
   790  				first = false
   791  				err := describeExpr(ctx, v, options, buf)
   792  				if err != nil {
   793  					return "", err
   794  				}
   795  			}
   796  		}
   797  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   798  		return "", moerr.NewNYI(ctx, "explain format json")
   799  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   800  		return "", moerr.NewNYI(ctx, "explain format dot")
   801  	}
   802  	return buf.String(), nil
   803  }
   804  
   805  func (ndesc *NodeDescribeImpl) GetWindowSpectListInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   806  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   807  	buf.WriteString("Window Function: ")
   808  	if options.Format == EXPLAIN_FORMAT_TEXT {
   809  		first := true
   810  		for _, v := range ndesc.Node.GetWinSpecList() {
   811  			if !first {
   812  				buf.WriteString("\n")
   813  			}
   814  			first = false
   815  			err := describeExpr(ctx, v, options, buf)
   816  			if err != nil {
   817  				return "", err
   818  			}
   819  		}
   820  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   821  		return "", moerr.NewNYI(ctx, "explain format json")
   822  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   823  		return "", moerr.NewNYI(ctx, "explain format dot")
   824  	}
   825  	return buf.String(), nil
   826  }
   827  
   828  func (ndesc *NodeDescribeImpl) GetOrderByInfo(ctx context.Context, options *ExplainOptions) (string, error) {
   829  	buf := bytes.NewBuffer(make([]byte, 0, 300))
   830  	if options.Format == EXPLAIN_FORMAT_TEXT {
   831  		buf.WriteString("Sort Key: ")
   832  		orderByDescImpl := NewOrderByDescribeImpl(ndesc.Node.OrderBy)
   833  		err := orderByDescImpl.GetDescription(ctx, options, buf)
   834  		if err != nil {
   835  			return "", err
   836  		}
   837  	} else if options.Format == EXPLAIN_FORMAT_JSON {
   838  		return "", moerr.NewNYI(ctx, "explain format json")
   839  	} else if options.Format == EXPLAIN_FORMAT_DOT {
   840  		return "", moerr.NewNYI(ctx, "explain format dot")
   841  	}
   842  	return buf.String(), nil
   843  }
   844  
   845  var _ NodeElemDescribe = (*CostDescribeImpl)(nil)
   846  var _ NodeElemDescribe = (*ExprListDescribeImpl)(nil)
   847  var _ NodeElemDescribe = (*OrderByDescribeImpl)(nil)
   848  var _ NodeElemDescribe = (*WinSpecDescribeImpl)(nil)
   849  var _ NodeElemDescribe = (*RowsetDataDescribeImpl)(nil)
   850  var _ NodeElemDescribe = (*AnalyzeInfoDescribeImpl)(nil)
   851  
   852  type AnalyzeInfoDescribeImpl struct {
   853  	AnalyzeInfo *plan.AnalyzeInfo
   854  }
   855  
   856  func NewAnalyzeInfoDescribeImpl(analyze *plan.AnalyzeInfo) *AnalyzeInfoDescribeImpl {
   857  	return &AnalyzeInfoDescribeImpl{
   858  		AnalyzeInfo: analyze,
   859  	}
   860  }
   861  
   862  func (a AnalyzeInfoDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
   863  	fmt.Fprintf(buf, "timeConsumed=%dms", a.AnalyzeInfo.TimeConsumed/1000000)
   864  
   865  	var majorStr, minorStr string
   866  	switch options.NodeType {
   867  	case plan.Node_TABLE_SCAN, plan.Node_EXTERNAL_SCAN:
   868  		majorStr = "scan"
   869  		minorStr = "filter"
   870  	case plan.Node_JOIN:
   871  		majorStr = "probe"
   872  		minorStr = "build"
   873  	case plan.Node_AGG:
   874  		majorStr = "group"
   875  		minorStr = "mergegroup"
   876  	case plan.Node_SORT:
   877  		majorStr = "sort"
   878  		minorStr = "mergesort"
   879  	case plan.Node_FILTER:
   880  		majorStr = ""
   881  		minorStr = "filter"
   882  	}
   883  
   884  	majordop := len(a.AnalyzeInfo.TimeConsumedArrayMajor)
   885  	if majordop > 1 {
   886  		fmt.Fprintf(buf, " %v_time=[", majorStr)
   887  		sort.Slice(a.AnalyzeInfo.TimeConsumedArrayMajor, func(i, j int) bool {
   888  			return a.AnalyzeInfo.TimeConsumedArrayMajor[i] < a.AnalyzeInfo.TimeConsumedArrayMajor[j]
   889  		})
   890  		if majordop > 4 {
   891  			var totalTime int64
   892  			for i := range a.AnalyzeInfo.TimeConsumedArrayMajor {
   893  				totalTime += a.AnalyzeInfo.TimeConsumedArrayMajor[i]
   894  			}
   895  			fmt.Fprintf(buf,
   896  				"total=%vms,min=%vms,max=%vms,dop=%v]",
   897  				totalTime/MILLION,
   898  				a.AnalyzeInfo.TimeConsumedArrayMajor[0]/MILLION,
   899  				a.AnalyzeInfo.TimeConsumedArrayMajor[len(a.AnalyzeInfo.TimeConsumedArrayMajor)-1]/MILLION,
   900  				majordop)
   901  		} else {
   902  			for i := range a.AnalyzeInfo.TimeConsumedArrayMajor {
   903  				if i != 0 {
   904  					fmt.Fprintf(buf, ",")
   905  				}
   906  				fmt.Fprintf(buf, "%vms", a.AnalyzeInfo.TimeConsumedArrayMajor[i]/MILLION)
   907  			}
   908  			fmt.Fprintf(buf, "]")
   909  		}
   910  
   911  		minordop := len(a.AnalyzeInfo.TimeConsumedArrayMinor)
   912  		if minordop > 0 {
   913  			if minorStr == "mergegroup" || minorStr == "mergesort" {
   914  				for i := range a.AnalyzeInfo.TimeConsumedArrayMinor {
   915  					if i != 0 {
   916  						a.AnalyzeInfo.TimeConsumedArrayMinor[0] += a.AnalyzeInfo.TimeConsumedArrayMinor[i]
   917  					}
   918  				}
   919  				a.AnalyzeInfo.TimeConsumedArrayMinor = a.AnalyzeInfo.TimeConsumedArrayMinor[:1]
   920  			}
   921  
   922  			fmt.Fprintf(buf, " %v_time=[", minorStr)
   923  			sort.Slice(a.AnalyzeInfo.TimeConsumedArrayMinor, func(i, j int) bool {
   924  				return a.AnalyzeInfo.TimeConsumedArrayMinor[i] < a.AnalyzeInfo.TimeConsumedArrayMinor[j]
   925  			})
   926  			if minordop > 4 {
   927  				var totalTime int64
   928  				for i := range a.AnalyzeInfo.TimeConsumedArrayMinor {
   929  					totalTime += a.AnalyzeInfo.TimeConsumedArrayMinor[i]
   930  				}
   931  				fmt.Fprintf(buf,
   932  					"total=%vms,min=%vms,max=%vms,dop=%v]",
   933  					totalTime/MILLION,
   934  					a.AnalyzeInfo.TimeConsumedArrayMinor[0]/MILLION,
   935  					a.AnalyzeInfo.TimeConsumedArrayMinor[len(a.AnalyzeInfo.TimeConsumedArrayMinor)-1]/MILLION,
   936  					majordop)
   937  			} else {
   938  				for i := range a.AnalyzeInfo.TimeConsumedArrayMinor {
   939  					if i != 0 {
   940  						fmt.Fprintf(buf, ",")
   941  					}
   942  					fmt.Fprintf(buf, "%vms", a.AnalyzeInfo.TimeConsumedArrayMinor[i]/MILLION)
   943  				}
   944  				fmt.Fprintf(buf, "]")
   945  			}
   946  		}
   947  	}
   948  
   949  	fmt.Fprintf(buf, " waitTime=%dms", a.AnalyzeInfo.WaitTimeConsumed/MILLION)
   950  	fmt.Fprintf(buf, " inputRows=%d", a.AnalyzeInfo.InputRows)
   951  	fmt.Fprintf(buf, " outputRows=%d", a.AnalyzeInfo.OutputRows)
   952  	if a.AnalyzeInfo.InputSize < MB {
   953  		fmt.Fprintf(buf, " InputSize=%dbytes", a.AnalyzeInfo.InputSize)
   954  	} else if a.AnalyzeInfo.InputSize < 10*GB {
   955  		fmt.Fprintf(buf, " InputSize=%dmb", a.AnalyzeInfo.InputSize/MB)
   956  	} else {
   957  		fmt.Fprintf(buf, " InputSize=%dgb", a.AnalyzeInfo.InputSize/GB)
   958  	}
   959  
   960  	if a.AnalyzeInfo.OutputSize < MB {
   961  		fmt.Fprintf(buf, " OutputSize=%dbytes", a.AnalyzeInfo.OutputSize)
   962  	} else if a.AnalyzeInfo.OutputSize < 10*GB {
   963  		fmt.Fprintf(buf, " OutputSize=%dmb", a.AnalyzeInfo.OutputSize/MB)
   964  	} else {
   965  		fmt.Fprintf(buf, " OutputSize=%dgb", a.AnalyzeInfo.OutputSize/GB)
   966  	}
   967  
   968  	if a.AnalyzeInfo.MemorySize < MB {
   969  		fmt.Fprintf(buf, " MemorySize=%dbytes", a.AnalyzeInfo.MemorySize)
   970  	} else if a.AnalyzeInfo.MemorySize < 10*GB {
   971  		fmt.Fprintf(buf, " MemorySize=%dmb", a.AnalyzeInfo.MemorySize/MB)
   972  	} else {
   973  		fmt.Fprintf(buf, " MemorySize=%dgb", a.AnalyzeInfo.MemorySize/GB)
   974  	}
   975  
   976  	return nil
   977  }
   978  
   979  type CostDescribeImpl struct {
   980  	Stats *plan.Stats
   981  }
   982  
   983  func (c *CostDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
   984  	//var result string
   985  	if c.Stats == nil {
   986  		buf.WriteString(" (cost=0)")
   987  	} else {
   988  		var blockNumStr, hashmapSizeStr string
   989  		if c.Stats.BlockNum > 0 {
   990  			blockNumStr = " blockNum=" + strconv.FormatInt(int64(c.Stats.BlockNum), 10)
   991  		}
   992  		if c.Stats.HashmapStats != nil && c.Stats.HashmapStats.HashmapSize > 0 {
   993  			hashmapSizeStr = " hashmapSize=" + strconv.FormatFloat(c.Stats.HashmapStats.HashmapSize, 'f', 2, 64)
   994  		}
   995  		buf.WriteString(" (cost=" + strconv.FormatFloat(c.Stats.Cost, 'f', 2, 64) +
   996  			" outcnt=" + strconv.FormatFloat(c.Stats.Outcnt, 'f', 2, 64) +
   997  			" selectivity=" + strconv.FormatFloat(c.Stats.Selectivity, 'f', 4, 64) +
   998  			blockNumStr + hashmapSizeStr + ")")
   999  	}
  1000  	return nil
  1001  }
  1002  
  1003  type ExprListDescribeImpl struct {
  1004  	ExprList []*plan.Expr // ProjectList,OnList,FilterList,GroupBy,GroupingSet and so on
  1005  }
  1006  
  1007  func NewExprListDescribeImpl(ExprList []*plan.Expr) *ExprListDescribeImpl {
  1008  	return &ExprListDescribeImpl{
  1009  		ExprList: ExprList,
  1010  	}
  1011  }
  1012  
  1013  func (e *ExprListDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
  1014  	first := true
  1015  	if options.Format == EXPLAIN_FORMAT_TEXT {
  1016  		for _, v := range e.ExprList {
  1017  			if !first {
  1018  				buf.WriteString(", ")
  1019  			}
  1020  			first = false
  1021  			err := describeExpr(ctx, v, options, buf)
  1022  			if err != nil {
  1023  				return err
  1024  				//return result, err
  1025  			}
  1026  		}
  1027  	} else if options.Format == EXPLAIN_FORMAT_JSON {
  1028  		return moerr.NewNYI(ctx, "explain format json")
  1029  	} else if options.Format == EXPLAIN_FORMAT_DOT {
  1030  		return moerr.NewNYI(ctx, "explain format dot")
  1031  	}
  1032  	return nil
  1033  }
  1034  
  1035  type OrderByDescribeImpl struct {
  1036  	OrderBy []*plan.OrderBySpec
  1037  }
  1038  
  1039  func NewOrderByDescribeImpl(OrderBy []*plan.OrderBySpec) *OrderByDescribeImpl {
  1040  	return &OrderByDescribeImpl{
  1041  		OrderBy: OrderBy,
  1042  	}
  1043  }
  1044  
  1045  func (o *OrderByDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
  1046  	//var result string
  1047  	//buf := bytes.NewBuffer(make([]byte, 0, 120))
  1048  	if options.Format == EXPLAIN_FORMAT_TEXT || options.Format == EXPLAIN_FORMAT_JSON {
  1049  		first := true
  1050  		for _, v := range o.OrderBy {
  1051  			if !first {
  1052  				buf.WriteString(", ")
  1053  			}
  1054  			first = false
  1055  			err := describeExpr(ctx, v.Expr, options, buf)
  1056  			if err != nil {
  1057  				return err
  1058  			}
  1059  
  1060  			flagKey := int32(v.Flag)
  1061  			orderbyFlag := plan.OrderBySpec_OrderByFlag_name[flagKey]
  1062  			buf.WriteString(" " + orderbyFlag)
  1063  		}
  1064  	} else if options.Format == EXPLAIN_FORMAT_DOT {
  1065  		return moerr.NewNYI(ctx, "explain format dot")
  1066  	}
  1067  	return nil
  1068  }
  1069  
  1070  type WinSpecDescribeImpl struct {
  1071  	WinSpec *plan.WindowSpec
  1072  }
  1073  
  1074  func (w *WinSpecDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
  1075  	// TODO implement me
  1076  	panic("implement me")
  1077  }
  1078  
  1079  type RowsetDataDescribeImpl struct {
  1080  	RowsetData *plan.RowsetData
  1081  }
  1082  
  1083  func (r *RowsetDataDescribeImpl) GetDescription(ctx context.Context, options *ExplainOptions, buf *bytes.Buffer) error {
  1084  	buf.WriteString("Value:")
  1085  	if r.RowsetData == nil {
  1086  		return nil
  1087  	}
  1088  
  1089  	first := true
  1090  	for index := range r.RowsetData.Cols {
  1091  		if !first {
  1092  			buf.WriteString(", ")
  1093  		}
  1094  		first = false
  1095  		buf.WriteString("\"*VALUES*\".column" + strconv.Itoa(index+1))
  1096  	}
  1097  	return nil
  1098  }