123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- package cmd
- import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "runtime/trace"
- "strings"
- "github.com/kyleconroy/sqlc/internal/codegen/golang"
- "github.com/kyleconroy/sqlc/internal/codegen/kotlin"
- "github.com/kyleconroy/sqlc/internal/codegen/python"
- "github.com/kyleconroy/sqlc/internal/compiler"
- "github.com/kyleconroy/sqlc/internal/config"
- "github.com/kyleconroy/sqlc/internal/debug"
- "github.com/kyleconroy/sqlc/internal/multierr"
- "github.com/kyleconroy/sqlc/internal/opts"
- "github.com/kyleconroy/sqlc/internal/plugin"
- )
- const errMessageNoVersion = `The configuration file must have a version number.
- Set the version to 1 at the top of sqlc.json:
- {
- "version": "1"
- ...
- }
- `
- const errMessageUnknownVersion = `The configuration file has an invalid version number.
- The only supported version is "1".
- `
- const errMessageNoPackages = `No packages are configured`
- func printFileErr(stderr io.Writer, dir string, fileErr *multierr.FileError) {
- filename := strings.TrimPrefix(fileErr.Filename, dir+"/")
- fmt.Fprintf(stderr, "%s:%d:%d: %s\n", filename, fileErr.Line, fileErr.Column, fileErr.Err)
- }
- type outPair struct {
- Gen config.SQLGen
- config.SQL
- }
- func readConfig(stderr io.Writer, dir, filename string) (string, *config.Config, error) {
- configPath := ""
- if filename != "" {
- configPath = filepath.Join(dir, filename)
- } else {
- var yamlMissing, jsonMissing bool
- yamlPath := filepath.Join(dir, "sqlc.yaml")
- jsonPath := filepath.Join(dir, "sqlc.json")
- if _, err := os.Stat(yamlPath); os.IsNotExist(err) {
- yamlMissing = true
- }
- if _, err := os.Stat(jsonPath); os.IsNotExist(err) {
- jsonMissing = true
- }
- if yamlMissing && jsonMissing {
- fmt.Fprintln(stderr, "error parsing configuration files. sqlc.yaml or sqlc.json: file does not exist")
- return "", nil, errors.New("config file missing")
- }
- if !yamlMissing && !jsonMissing {
- fmt.Fprintln(stderr, "error: both sqlc.json and sqlc.yaml files present")
- return "", nil, errors.New("sqlc.json and sqlc.yaml present")
- }
- configPath = yamlPath
- if yamlMissing {
- configPath = jsonPath
- }
- }
- base := filepath.Base(configPath)
- blob, err := os.ReadFile(configPath)
- if err != nil {
- fmt.Fprintf(stderr, "error parsing %s: file does not exist\n", base)
- return "", nil, err
- }
- conf, err := config.ParseConfig(bytes.NewReader(blob))
- if err != nil {
- switch err {
- case config.ErrMissingVersion:
- fmt.Fprintf(stderr, errMessageNoVersion)
- case config.ErrUnknownVersion:
- fmt.Fprintf(stderr, errMessageUnknownVersion)
- case config.ErrNoPackages:
- fmt.Fprintf(stderr, errMessageNoPackages)
- }
- fmt.Fprintf(stderr, "error parsing %s: %s\n", base, err)
- return "", nil, err
- }
- return configPath, &conf, nil
- }
- func Generate(ctx context.Context, e Env, dir, filename string, stderr io.Writer) (map[string]string, error) {
- configPath, conf, err := readConfig(stderr, dir, filename)
- if err != nil {
- return nil, err
- }
- base := filepath.Base(configPath)
- if err := config.Validate(conf); err != nil {
- fmt.Fprintf(stderr, "error validating %s: %s\n", base, err)
- return nil, err
- }
- output := map[string]string{}
- errored := false
- var pairs []outPair
- for _, sql := range conf.SQL {
- if sql.Gen.Go != nil {
- pairs = append(pairs, outPair{
- SQL: sql,
- Gen: config.SQLGen{Go: sql.Gen.Go},
- })
- }
- if sql.Gen.Kotlin != nil {
- pairs = append(pairs, outPair{
- SQL: sql,
- Gen: config.SQLGen{Kotlin: sql.Gen.Kotlin},
- })
- }
- if sql.Gen.Python != nil {
- if !e.ExperimentalFeatures {
- fmt.Fprintf(stderr, "error parsing %s: unknown target langauge \"python\"\n", base)
- return nil, fmt.Errorf("unknown target language \"python\"")
- }
- pairs = append(pairs, outPair{
- SQL: sql,
- Gen: config.SQLGen{Python: sql.Gen.Python},
- })
- }
- }
- for _, sql := range pairs {
- combo := config.Combine(*conf, sql.SQL)
- // TODO: This feels like a hack that will bite us later
- joined := make([]string, 0, len(sql.Schema))
- for _, s := range sql.Schema {
- joined = append(joined, filepath.Join(dir, s))
- }
- sql.Schema = joined
- joined = make([]string, 0, len(sql.Queries))
- for _, q := range sql.Queries {
- joined = append(joined, filepath.Join(dir, q))
- }
- sql.Queries = joined
- var name, lang string
- parseOpts := opts.Parser{
- Debug: debug.Debug,
- }
- if sql.Gen.Go != nil {
- name = combo.Go.Package
- lang = "golang"
- } else if sql.Gen.Kotlin != nil {
- if sql.Engine == config.EnginePostgreSQL {
- parseOpts.UsePositionalParameters = true
- }
- lang = "kotlin"
- name = combo.Kotlin.Package
- } else if sql.Gen.Python != nil {
- lang = "python"
- name = combo.Python.Package
- }
- var packageRegion *trace.Region
- if debug.Traced {
- packageRegion = trace.StartRegion(ctx, "package")
- trace.Logf(ctx, "", "name=%s dir=%s language=%s", name, dir, lang)
- }
- result, failed := parse(ctx, e, name, dir, sql.SQL, combo, parseOpts, stderr)
- if failed {
- if packageRegion != nil {
- packageRegion.End()
- }
- errored = true
- break
- }
- var region *trace.Region
- if debug.Traced {
- region = trace.StartRegion(ctx, "codegen")
- }
- var files map[string]string
- var resp *plugin.CodeGenResponse
- var out string
- switch {
- case sql.Gen.Go != nil:
- out = combo.Go.Out
- files, err = golang.Generate(result, combo)
- case sql.Gen.Kotlin != nil:
- out = combo.Kotlin.Out
- resp, err = kotlin.Generate(codeGenRequest(result, combo))
- case sql.Gen.Python != nil:
- out = combo.Python.Out
- resp, err = python.Generate(codeGenRequest(result, combo))
- default:
- panic("missing language backend")
- }
- if region != nil {
- region.End()
- }
- if resp != nil {
- files = map[string]string{}
- for _, file := range resp.Files {
- files[file.Name] = string(file.Contents)
- }
- }
- if err != nil {
- fmt.Fprintf(stderr, "# package %s\n", name)
- fmt.Fprintf(stderr, "error generating code: %s\n", err)
- errored = true
- if packageRegion != nil {
- packageRegion.End()
- }
- continue
- }
- for n, source := range files {
- filename := filepath.Join(dir, out, n)
- output[filename] = source
- }
- if packageRegion != nil {
- packageRegion.End()
- }
- }
- if errored {
- return nil, fmt.Errorf("errored")
- }
- return output, nil
- }
- func parse(ctx context.Context, e Env, name, dir string, sql config.SQL, combo config.CombinedSettings, parserOpts opts.Parser, stderr io.Writer) (*compiler.Result, bool) {
- if debug.Traced {
- defer trace.StartRegion(ctx, "parse").End()
- }
- c := compiler.NewCompiler(sql, combo)
- if err := c.ParseCatalog(sql.Schema); err != nil {
- fmt.Fprintf(stderr, "# package %s\n", name)
- if parserErr, ok := err.(*multierr.Error); ok {
- for _, fileErr := range parserErr.Errs() {
- printFileErr(stderr, dir, fileErr)
- }
- } else {
- fmt.Fprintf(stderr, "error parsing schema: %s\n", err)
- }
- return nil, true
- }
- if parserOpts.Debug.DumpCatalog {
- debug.Dump(c.Catalog())
- }
- if err := c.ParseQueries(sql.Queries, parserOpts); err != nil {
- fmt.Fprintf(stderr, "# package %s\n", name)
- if parserErr, ok := err.(*multierr.Error); ok {
- for _, fileErr := range parserErr.Errs() {
- printFileErr(stderr, dir, fileErr)
- }
- } else {
- fmt.Fprintf(stderr, "error parsing queries: %s\n", err)
- }
- return nil, true
- }
- return c.Result(), false
- }
|