forked from skeema/skeema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cmd_format.go
143 lines (129 loc) · 5.23 KB
/
cmd_format.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"strings"
log "github.com/sirupsen/logrus"
"github.com/skeema/mybase"
"github.com/skeema/skeema/internal/dumper"
"github.com/skeema/skeema/internal/fs"
"github.com/skeema/skeema/internal/tengo"
"github.com/skeema/skeema/internal/workspace"
)
func init() {
summary := "Normalize format of filesystem representation of database objects"
desc := "Reformats the filesystem representation of database objects to match the canonical " +
"format shown in SHOW CREATE.\n\n" +
"This command relies on accessing database instances to test the SQL DDL in a " +
"temporary location. See the --workspace option for more information.\n\n" +
"You may optionally pass an environment name as a CLI arg. This will affect " +
"which section of .skeema config files is used for workspace selection. For " +
"example, running `skeema format staging` will " +
"apply config directives from the [staging] section of config files, as well as " +
"any sectionless directives at the top of the file. If no environment name is " +
"supplied, the default is \"production\".\n\n" +
"An exit code of 0 will be returned if all files were already formatted properly; " +
"1 if some files were not already in the correct format; or 2+ if any errors " +
"occurred."
cmd := mybase.NewCommand("format", summary, desc, FormatHandler)
cmd.AddOption(mybase.BoolOption("write", 0, true, "Update files to correct format"))
cmd.AddOption(mybase.BoolOption("strip-partitioning", 0, false, "Remove PARTITION BY clauses from *.sql files"))
workspace.AddCommandOptions(cmd)
cmd.AddArg("environment", "production", false)
CommandSuite.AddSubCommand(cmd)
}
// FormatHandler is the handler method for `skeema format`
func FormatHandler(cfg *mybase.Config) error {
dir, err := fs.ParseDir(".", cfg)
if err != nil {
return err
}
// formatWalker returns the "worst" (highest) exit code it encounters. We care
// about the exit code, but not the error message, since any error will already
// have been logged. (Multiple errors may have been encountered along the way,
// and it's simpler to log them when they occur, rather than needlessly
// collecting them.)
err = formatWalker(dir, 5)
return NewExitValue(ExitCode(err), "")
}
func formatWalker(dir *fs.Dir, maxDepth int) error {
if dir.ParseError != nil {
log.Warnf("Skipping %s: %s", dir.Path, dir.ParseError)
return NewExitValue(CodeBadConfig, "")
}
if dir.Config.GetBool("write") {
log.Infof("Reformatting %s", dir)
} else {
log.Infof("Checking format of %s", dir)
}
result := formatDir(dir)
if ExitCode(result) > CodeDifferencesFound {
log.Errorf("Skipping %s: %s", dir, result)
return result // don't walk subdirs if something fatal happened here
}
subdirs, err := dir.Subdirs()
if err != nil {
log.Errorf("Cannot list subdirs of %s: %s", dir, err)
return err
} else if len(subdirs) > 0 && maxDepth <= 0 {
log.Errorf("Not walking subdirs of %s: max depth reached", dir)
return result
}
for _, sub := range subdirs {
err := formatWalker(sub, maxDepth-1)
result = HighestExitCode(result, err)
}
return result
}
// formatDir reformats SQL statements in all logical schemas in dir. This
// function does not recurse into subdirs.
func formatDir(dir *fs.Dir) error {
var totalReformatCount int
// Get workspace options for dir. This involves connecting to the first defined
// instance, so that any auto-detect-related settings work properly. However,
// with workspace=docker we can ignore connection errors; we'll get reasonable
// defaults from workspace.OptionsForDir if inst is nil as long as flavor is set.
var wsOpts workspace.Options
if len(dir.LogicalSchemas) > 0 {
inst, err := dir.FirstInstance()
if wsType, _ := dir.Config.GetEnum("workspace", "temp-schema", "docker"); wsType != "docker" || !dir.Config.Changed("flavor") {
if err != nil {
return NewExitValue(CodeBadConfig, err.Error())
} else if inst == nil {
return NewExitValue(CodeBadConfig, "This command needs either a host (with workspace=temp-schema) or flavor (with workspace=docker), but one is not configured for environment %q", dir.Config.Get("environment"))
}
}
if wsOpts, err = workspace.OptionsForDir(dir, inst); err != nil {
return NewExitValue(CodeBadConfig, err.Error())
}
// TODO: support multiple logical schemas per dir
logicalSchema := dir.LogicalSchemas[0]
wsSchema, err := workspace.ExecLogicalSchema(logicalSchema, wsOpts)
if err != nil {
return err
}
for _, stmtErr := range wsSchema.Failures {
message := strings.Replace(stmtErr.Err.Error(), "Error executing DDL in workspace: ", "", 1)
log.Errorf("%s: %s", stmtErr.Location(), message)
totalReformatCount++
}
dumpOpts := dumper.Options{
IncludeAutoInc: true,
CountOnly: !dir.Config.GetBool("write"),
}
if dir.Config.GetBool("strip-partitioning") {
dumpOpts.Partitioning = tengo.PartitioningRemove
}
dumpOpts.IgnoreKeys(wsSchema.FailedKeys())
reformatCount, err := dumper.DumpSchema(wsSchema.Schema, dir, dumpOpts)
if err != nil {
return err
}
totalReformatCount += reformatCount
}
for _, stmt := range dir.UnparsedStatements {
log.Debugf("%s: unable to parse statement", stmt.Location())
}
if totalReformatCount > 0 {
return NewExitValue(CodeDifferencesFound, "")
}
return nil
}