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)