github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/parser/help.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package parser
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"io"
    17  	"sort"
    18  	"strings"
    19  	"text/tabwriter"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/base"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    26  	"github.com/cockroachdb/errors"
    27  )
    28  
    29  // HelpMessage describes a contextual help message.
    30  type HelpMessage struct {
    31  	// Command is set if the message is about a statement.
    32  	Command string
    33  	// Function is set if the message is about a built-in function.
    34  	Function string
    35  
    36  	// HelpMessageBody contains the details of the message.
    37  	HelpMessageBody
    38  }
    39  
    40  // String implements the fmt.String interface.
    41  func (h *HelpMessage) String() string {
    42  	var buf bytes.Buffer
    43  	buf.WriteString("help:\n")
    44  	h.Format(&buf)
    45  	return buf.String()
    46  }
    47  
    48  // Format prints out details about the message onto the specified output stream.
    49  func (h *HelpMessage) Format(w io.Writer) {
    50  	if h.Command != "" {
    51  		fmt.Fprintf(w, "Command:     %s\n", h.Command)
    52  	}
    53  	if h.Function != "" {
    54  		fmt.Fprintf(w, "Function:    %s\n", h.Function)
    55  	}
    56  	if h.ShortDescription != "" {
    57  		fmt.Fprintf(w, "Description: %s\n", h.ShortDescription)
    58  	}
    59  	if h.Category != "" {
    60  		fmt.Fprintf(w, "Category:    %s\n", h.Category)
    61  	}
    62  	if h.Command != "" {
    63  		fmt.Fprintln(w, "Syntax:")
    64  	}
    65  	fmt.Fprintln(w, strings.TrimSpace(h.Text))
    66  	if h.SeeAlso != "" {
    67  		fmt.Fprintf(w, "\nSee also:\n  %s\n", h.SeeAlso)
    68  	}
    69  }
    70  
    71  // helpWith is to be used in parser actions to mark the parser "in
    72  // error", with the error set to a contextual help message about the
    73  // current statement.
    74  func helpWith(sqllex sqlLexer, helpText string) int {
    75  	scan := sqllex.(*lexer)
    76  	if helpText == "" {
    77  		scan.lastError = pgerror.WithCandidateCode(errors.New("help upon syntax error"), pgcode.Syntax)
    78  		scan.populateHelpMsg("help:\n" + AllHelp)
    79  		return 1
    80  	}
    81  	msg := HelpMessage{Command: helpText, HelpMessageBody: HelpMessages[helpText]}
    82  	scan.SetHelp(msg)
    83  	// We return non-zero to indicate to the caller of Parse() that the
    84  	// parse was unsuccessful.
    85  	return 1
    86  }
    87  
    88  // helpWithFunction is to be used in parser actions to mark the parser
    89  // "in error", with the error set to a contextual help message about
    90  // the current built-in function.
    91  func helpWithFunction(sqllex sqlLexer, f tree.ResolvableFunctionReference) int {
    92  	d, err := f.Resolve(sessiondata.SearchPath{})
    93  	if err != nil {
    94  		return 1
    95  	}
    96  
    97  	msg := HelpMessage{
    98  		Function: f.String(),
    99  		HelpMessageBody: HelpMessageBody{
   100  			Category: d.Category,
   101  			SeeAlso:  base.DocsURL("functions-and-operators.html"),
   102  		},
   103  	}
   104  
   105  	var buf bytes.Buffer
   106  	w := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0)
   107  
   108  	// Each function definition contains one or more overloads. We need
   109  	// to extract them all; moreover each overload may have a different
   110  	// documentation, so we need to also combine the descriptions
   111  	// together.
   112  	lastInfo := ""
   113  	for i, overload := range d.Definition {
   114  		b := overload.(*tree.Overload)
   115  		if b.Info != "" && b.Info != lastInfo {
   116  			if i > 0 {
   117  				fmt.Fprintln(w, "---")
   118  			}
   119  			fmt.Fprintf(w, "\n%s\n\n", b.Info)
   120  			fmt.Fprintln(w, "Signature")
   121  		}
   122  		lastInfo = b.Info
   123  
   124  		simplifyRet := d.Class == tree.GeneratorClass
   125  		fmt.Fprintf(w, "%s%s\n", d.Name, b.Signature(simplifyRet))
   126  	}
   127  	_ = w.Flush()
   128  	msg.Text = buf.String()
   129  
   130  	sqllex.(*lexer).SetHelp(msg)
   131  	return 1
   132  }
   133  
   134  func helpWithFunctionByName(sqllex sqlLexer, s string) int {
   135  	un := &tree.UnresolvedName{NumParts: 1, Parts: tree.NameParts{s}}
   136  	return helpWithFunction(sqllex, tree.ResolvableFunctionReference{FunctionReference: un})
   137  }
   138  
   139  const (
   140  	hGroup        = ""
   141  	hDDL          = "schema manipulation"
   142  	hDML          = "data manipulation"
   143  	hTxn          = "transaction control"
   144  	hPriv         = "privileges and security"
   145  	hMisc         = "miscellaneous"
   146  	hCfg          = "configuration"
   147  	hExperimental = "experimental"
   148  	hCCL          = "enterprise features"
   149  )
   150  
   151  // HelpMessageBody defines the body of a help text. The messages are
   152  // structured to facilitate future help navigation functionality.
   153  type HelpMessageBody struct {
   154  	Category         string
   155  	ShortDescription string
   156  	Text             string
   157  	SeeAlso          string
   158  }
   159  
   160  // HelpMessages is the registry of all help messages, keyed by the
   161  // top-level statement that they document. The key is intended for use
   162  // via the \h client-side command.
   163  var HelpMessages = func(h map[string]HelpMessageBody) map[string]HelpMessageBody {
   164  	appendSeeAlso := func(newItem, prevItems string) string {
   165  		// "See also" items start with no indentation, and then use two
   166  		// space indentation from the 2nd item onward.
   167  		if prevItems != "" {
   168  			return newItem + "\n  " + prevItems
   169  		}
   170  		return newItem
   171  	}
   172  	reformatSeeAlso := func(seeAlso string) string {
   173  		return strings.Replace(
   174  			strings.Replace(seeAlso, ", ", "\n  ", -1),
   175  			"WEBDOCS", base.DocsURLBase, -1)
   176  	}
   177  	srcMsg := h["<SOURCE>"]
   178  	srcMsg.SeeAlso = reformatSeeAlso(strings.TrimSpace(srcMsg.SeeAlso))
   179  	selectMsg := h["<SELECTCLAUSE>"]
   180  	selectMsg.SeeAlso = reformatSeeAlso(strings.TrimSpace(selectMsg.SeeAlso))
   181  	for k, m := range h {
   182  		m = h[k]
   183  		m.ShortDescription = strings.TrimSpace(m.ShortDescription)
   184  		m.Text = strings.TrimSpace(m.Text)
   185  		m.SeeAlso = strings.TrimSpace(m.SeeAlso)
   186  
   187  		// If the description contains <source>, append the <source> help.
   188  		if strings.Contains(m.Text, "<source>") && k != "<SOURCE>" {
   189  			m.Text = strings.TrimSpace(m.Text) + "\n\n" + strings.TrimSpace(srcMsg.Text)
   190  			m.SeeAlso = appendSeeAlso(srcMsg.SeeAlso, m.SeeAlso)
   191  		}
   192  		// Ditto for <selectclause>.
   193  		if strings.Contains(m.Text, "<selectclause>") && k != "<SELECTCLAUSE>" {
   194  			m.Text = strings.TrimSpace(m.Text) + "\n\n" + strings.TrimSpace(selectMsg.Text)
   195  			m.SeeAlso = appendSeeAlso(selectMsg.SeeAlso, m.SeeAlso)
   196  		}
   197  		// If the description contains <tablename>, mention SHOW TABLES in "See Also".
   198  		if strings.Contains(m.Text, "<tablename>") {
   199  			m.SeeAlso = appendSeeAlso("SHOW TABLES", m.SeeAlso)
   200  		}
   201  		m.SeeAlso = reformatSeeAlso(m.SeeAlso)
   202  		h[k] = m
   203  	}
   204  	return h
   205  }(helpMessages)
   206  
   207  // AllHelp contains an overview of all statements with help messages.
   208  // For example, displayed in the CLI shell with \h without additional parameters.
   209  var AllHelp = func(h map[string]HelpMessageBody) string {
   210  	// Aggregate the help items.
   211  	cmds := make(map[string][]string)
   212  	for c, details := range h {
   213  		if details.Category == "" {
   214  			continue
   215  		}
   216  		cmds[details.Category] = append(cmds[details.Category], c)
   217  	}
   218  
   219  	// Ensure the result is deterministic.
   220  	var categories []string
   221  	for c, l := range cmds {
   222  		categories = append(categories, c)
   223  		sort.Strings(l)
   224  	}
   225  	sort.Strings(categories)
   226  
   227  	// Compile the final help index.
   228  	var buf bytes.Buffer
   229  	w := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0)
   230  	for _, cat := range categories {
   231  		fmt.Fprintf(w, "%s:\n", strings.Title(cat))
   232  		for _, item := range cmds[cat] {
   233  			fmt.Fprintf(w, "\t\t%s\t%s\n", item, h[item].ShortDescription)
   234  		}
   235  		fmt.Fprintln(w)
   236  	}
   237  	_ = w.Flush()
   238  	return buf.String()
   239  }(helpMessages)