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