package metadata import ( "bufio" "fmt" "strings" "unicode" "github.com/sqlc-dev/sqlc/internal/source" ) type CommentSyntax source.CommentSyntax type Metadata struct { Name string Cmd string Comments []string Params map[string]string Flags map[string]bool Filename string } const ( CmdExec = ":exec" CmdExecResult = ":execresult" CmdExecRows = ":execrows" CmdExecLastId = ":execlastid" CmdMany = ":many" CmdOne = ":one" CmdCopyFrom = ":copyfrom" CmdBatchExec = ":batchexec" CmdBatchMany = ":batchmany" CmdBatchOne = ":batchone" ) // A query name must be a valid Go identifier // // https://golang.org/ref/spec#Identifiers func validateQueryName(name string) error { if len(name) == 0 { return fmt.Errorf("invalid query name: %q", name) } for i, c := range name { isLetter := unicode.IsLetter(c) || c == '_' isDigit := unicode.IsDigit(c) if i == 0 && !isLetter { return fmt.Errorf("invalid query name %q", name) } else if !(isLetter || isDigit) { return fmt.Errorf("invalid query name %q", name) } } return nil } func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string, error) { for _, line := range strings.Split(t, "\n") { var prefix string if strings.HasPrefix(line, "--") { if !commentStyle.Dash { continue } prefix = "--" } if strings.HasPrefix(line, "/*") { if !commentStyle.SlashStar { continue } prefix = "/*" } if strings.HasPrefix(line, "#") { if !commentStyle.Hash { continue } prefix = "#" } if prefix == "" { continue } rest := line[len(prefix):] if !strings.HasPrefix(strings.TrimSpace(rest), "name") { continue } if !strings.Contains(rest, ":") { continue } if !strings.HasPrefix(rest, " name: ") { return "", "", fmt.Errorf("invalid metadata: %s", line) } part := strings.Split(strings.TrimSpace(line), " ") if prefix == "/*" { part = part[:len(part)-1] // removes the trailing "*/" element } if len(part) == 3 { return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line) } if len(part) != 4 { return "", "", fmt.Errorf("invalid query comment: %s", line) } queryName := part[2] queryType := strings.TrimSpace(part[3]) switch queryType { case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne: default: return "", "", fmt.Errorf("invalid query type: %s", queryType) } if err := validateQueryName(queryName); err != nil { return "", "", err } return queryName, queryType, nil } return "", "", nil } func ParseParamsAndFlags(comments []string) (map[string]string, map[string]bool, error) { params := make(map[string]string) flags := make(map[string]bool) for _, line := range comments { s := bufio.NewScanner(strings.NewReader(line)) s.Split(bufio.ScanWords) s.Scan() token := s.Text() if !strings.HasPrefix(token, "@") { continue } switch token { case "@param": s.Scan() name := s.Text() var rest []string for s.Scan() { paramToken := s.Text() rest = append(rest, paramToken) } params[name] = strings.Join(rest, " ") default: flags[token] = true } if s.Err() != nil { return params, flags, s.Err() } } return params, flags, nil }