Permalink
Browse files

diff and push: Clean up output and logging

Update vendored version of skeema/tengo to latest (incl cleaner return val for
unsupported DDL), and improve output/logging/exitcode for unsupported DDL.

Improve output/logging for errors and skipped operations.

Add global --debug option. So far only used in logging exit codes, but more
debug output will be added soon.
  • Loading branch information...
evanelias committed Nov 13, 2016
1 parent 2101e13 commit d83468af246a4c0743110a0e7a6767f79769d5b8
Showing with 149 additions and 69 deletions.
  1. +1 −1 Godeps/Godeps.json
  2. +40 −16 diff.go
  3. +6 −4 exec.go
  4. +3 −1 exit.go
  5. +10 −3 log.go
  6. +16 −0 pull.go
  7. +45 −28 push.go
  8. +6 −0 skeema.go
  9. +17 −10 vendor/github.com/skeema/tengo/ddl.go
  10. +5 −6 vendor/github.com/skeema/tengo/table.go
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
56 diff.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/skeema/mycli"
"github.com/skeema/tengo"
)
@@ -41,25 +42,28 @@ func DiffHandler(cfg *mycli.Config) error {
return err
}
var errCount, diffCount int
var errCount, diffCount, unsupportedCount int // total counts, across all targets
mods := tengo.StatementModifiers{
NextAutoInc: tengo.NextAutoIncIfIncreased,
}
for t := range dir.Targets(false, false) {
if hasErrors, firstErr := t.HasErrors(); hasErrors {
fmt.Printf("-- Skipping %s:\n-- %s\n\n", t.Dir, firstErr)
log.Errorf("Skipping %s:", t.Dir)
log.Errorf(" %s", firstErr)
fmt.Println()
errCount++
continue
}
fmt.Printf("-- Diff of %s %s vs %s/*.sql\n", t.Instance, t.SchemaFromDir.Name, t.Dir)
log.Infof("Generating diff of %s %s vs %s/*.sql", t.Instance, t.SchemaFromDir.Name, t.Dir)
diff, err := tengo.NewSchemaDiff(t.SchemaFromInstance, t.SchemaFromDir)
if err != nil {
return err
}
if t.SchemaFromInstance == nil {
// TODO: support CREATE DATABASE schema-level options
fmt.Printf("-- instance: %s\n", t.Instance)
fmt.Printf("%s;\n", t.SchemaFromDir.CreateStatement())
}
if cfg.GetBool("verify") && len(diff.TableDiffs) > 0 {
@@ -70,33 +74,53 @@ func DiffHandler(cfg *mycli.Config) error {
mods.AllowDropTable = t.Dir.Config.GetBool("allow-drop-table")
mods.AllowDropColumn = t.Dir.Config.GetBool("allow-drop-column")
var statementCounter int
var targetStmtCount int
for _, tableDiff := range diff.TableDiffs {
ddl := NewDDLStatement(tableDiff, mods, t)
if ddl == nil {
// skip blank DDL (which may happen due to NextAutoInc modifier)
continue
}
if targetStmtCount++; targetStmtCount == 1 {
fmt.Printf("-- instance: %s\n", t.Instance)
fmt.Printf("USE %s;\n", tengo.EscapeIdentifier(t.SchemaFromDir.Name))
}
diffCount++
if ddl.Err != nil {
log.Errorf("%s. The following DDL statement will be skipped. See --help for more information.", ddl.Err)
errCount++
}
if statementCounter++; statementCounter == 1 {
fmt.Printf("USE %s;\n", tengo.EscapeIdentifier(t.SchemaFromDir.Name))
}
fmt.Printf(ddl.String())
fmt.Println(ddl.String())
}
for _, table := range diff.UnsupportedTables {
log.Warnf("Skipping table %s: unable to generate ALTER TABLE due to use of unsupported features", table.Name)
unsupportedCount++
targetStmtCount++
}
if targetStmtCount == 0 {
log.Info("No differences found")
}
fmt.Println()
}
if errCount > 0 {
var plural string
if errCount > 1 {
plural = "s"
if errCount+unsupportedCount == 0 {
if diffCount > 0 {
return NewExitValue(CodeDifferencesFound, "")
}
return NewExitValue(CodeFatalError, "Skipped %d operation%s due to error%s", errCount, plural, plural)
return nil
}
var plural, reason string
code := CodeFatalError
if errCount+unsupportedCount > 1 {
plural = "s"
}
if diffCount > 0 {
return NewExitValue(CodeDifferencesFound, "")
if errCount == 0 {
code = CodePartialError
reason = "unsupported feature"
} else if unsupportedCount == 0 {
reason = "error"
} else {
reason = "unsupported features or error"
}
return nil
return NewExitValue(code, "Skipped %d operation%s due to %s%s", errCount+unsupportedCount, plural, reason, plural)
}
View
10 exec.go
@@ -72,8 +72,10 @@ func NewDDLStatement(diff tengo.TableDiff, mods tengo.StatementModifiers, target
return ddl
}
// String returns a string representation of ddl. This will be annotated if
// ddl.Err is non-nil and/or if ddl represents an external command.
// String returns a string representation of ddl. If an external command is in
// use, the returned string will be prefixed with "\!", the MySQL CLI command
// shortcut for "system" shellout. If ddl.Err is non-nil, the returned string
// will be commented-out by wrapping in /* ... */ long-style comment.
func (ddl *DDLStatement) String() string {
if ddl == nil {
return ""
@@ -85,9 +87,9 @@ func (ddl *DDLStatement) String() string {
stmt = fmt.Sprintf("%s;", ddl.stmt)
}
if ddl.Err != nil {
return fmt.Sprintf("-- %s. The following DDL statement will be skipped. See --help for more information.\n/* %s*/\n", ddl.Err, stmt)
stmt = fmt.Sprintf("/* %s */", stmt)
}
return fmt.Sprintf("%s\n", stmt)
return stmt
}
// Execute runs the DDL statement, either by running a SQL query against a DB,
View
@@ -54,6 +54,7 @@ func (ev *ExitValue) Error() string {
// if err is nil, exit code 0 will be used; if non-nil then exit code 2.
func Exit(err error) {
if err == nil {
log.Debug("Exit code 0 (SUCCESS)")
os.Exit(0)
}
exitCode := CodeFatalError
@@ -65,8 +66,9 @@ func Exit(err error) {
if exitCode >= CodeFatalError {
log.Error(message)
} else {
log.Info(message)
log.Warn(message)
}
}
log.Debugf("Exit code %d", exitCode)
os.Exit(exitCode)
}
View
13 log.go
@@ -26,7 +26,7 @@ func (f *customFormatter) Format(entry *log.Entry) ([]byte, error) {
b = &bytes.Buffer{}
}
var startColor, endColor string
var startColor, endColor, spacing string
if f.isTerminal {
endColor = "\x1b[0m"
switch entry.Level {
@@ -42,8 +42,15 @@ func (f *customFormatter) Format(entry *log.Entry) ([]byte, error) {
endColor = "" // no color
}
}
levelText := fmt.Sprintf("[%s%s%s]", startColor, strings.ToUpper(entry.Level.String()), endColor)
levelName := strings.ToUpper(entry.Level.String())
if levelName == "WARNING" {
levelName = "WARN"
}
if len(levelName) == 4 { // align level for INFO or WARN; other levels are all 5 chars
spacing = " "
}
levelText := fmt.Sprintf("[%s%s%s]%s", startColor, levelName, endColor, spacing)
fmt.Fprintf(b, "%s %-7s %s\n", entry.Time.Format("2006-01-02 15:04:05"), levelText, entry.Message)
fmt.Fprintf(b, "%s %s %s\n", entry.Time.Format("2006-01-02 15:04:05"), levelText, entry.Message)
return b.Bytes(), nil
}
View
16 pull.go
@@ -134,6 +134,22 @@ func PullHandler(cfg *mycli.Config) error {
}
}
// Tables that use features not supported by tengo diff still need files
// updated. Handle same as AlterTable case, since created/dropped tables don't
// ever end up in UnsupportedTables since they don't do a diff operation.
for _, table := range diff.UnsupportedTables {
sf := SQLFile{
Dir: t.Dir,
FileName: fmt.Sprintf("%s.sql", table.Name),
Contents: table.CreateStatement(),
}
var length int
if length, err = sf.Write(); err != nil {
return fmt.Errorf("Unable to write to %s: %s", sf.Path(), err)
}
fmt.Printf(" Wrote %s (%d bytes) -- updated file to reflect table alterations\n", sf.Path(), length)
}
if dir.Config.GetBool("normalize") {
for _, table := range diff.SameTables {
sf := SQLFile{
View
73 push.go
@@ -39,7 +39,7 @@ func PushHandler(cfg *mycli.Config) error {
return err
}
var errCount int
var errCount, unsupportedCount int // total counts, across all targets
mods := tengo.StatementModifiers{
NextAutoInc: tengo.NextAutoIncIfIncreased,
}
@@ -48,13 +48,15 @@ func PushHandler(cfg *mycli.Config) error {
// use multiple worker goroutines all pulling instances off the channel
for t := range dir.Targets(true, true) {
if hasErrors, firstErr := t.HasErrors(); hasErrors {
fmt.Printf("\nSkipping %s:\n %s\n", t.Dir, firstErr)
log.Errorf("Skipping %s:", t.Dir)
log.Errorf(" %s", firstErr)
fmt.Println()
t.Done()
errCount++
continue
}
fmt.Printf("\nPushing changes from %s/*.sql to %s %s...\n", t.Dir, t.Instance, t.SchemaFromDir.Name)
log.Infof("Pushing changes from %s/*.sql to %s %s", t.Dir, t.Instance, t.SchemaFromDir.Name)
diff, err := tengo.NewSchemaDiff(t.SchemaFromInstance, t.SchemaFromDir)
if err != nil {
t.Done()
@@ -68,11 +70,8 @@ func PushHandler(cfg *mycli.Config) error {
t.Done()
return fmt.Errorf("Error creating schema %s on %s: %s", t.SchemaFromDir.Name, t.Instance, err)
}
fmt.Printf("-- instance: %s\n", t.Instance)
fmt.Printf("%s;\n", t.SchemaFromDir.CreateStatement())
} else if len(diff.TableDiffs) == 0 {
fmt.Println("(nothing to do)")
t.Done()
continue
}
if t.Dir.Config.GetBool("verify") && len(diff.TableDiffs) > 0 {
@@ -84,42 +83,60 @@ func PushHandler(cfg *mycli.Config) error {
mods.AllowDropTable = t.Dir.Config.GetBool("allow-drop-table")
mods.AllowDropColumn = t.Dir.Config.GetBool("allow-drop-column")
var statementCounter int
var targetStmtCount int
for n, tableDiff := range diff.TableDiffs {
ddl := NewDDLStatement(tableDiff, mods, t)
if ddl == nil {
// skip blank DDL (which may happen due to NextAutoInc modifier)
continue
}
statementCounter++
fmt.Printf(ddl.String())
if ddl.Err == nil {
if err := ddl.Execute(); err != nil {
log.Errorf("Error running above statement on %s: %s", t.Instance, err)
skipCount := len(diff.TableDiffs) - n
if skipCount > 1 {
log.Errorf("Skipping %d additional statements on %s %s", skipCount-1, t.Instance, t.SchemaFromDir.Name)
}
errCount += skipCount
break
if targetStmtCount++; targetStmtCount == 1 {
fmt.Printf("-- instance: %s\n", t.Instance)
fmt.Printf("USE %s;\n", tengo.EscapeIdentifier(t.SchemaFromDir.Name))
}
if ddl.Err != nil {
log.Errorf("%s. The following DDL statement will be skipped. See --help for more information.", ddl.Err)
errCount++
}
fmt.Println(ddl.String())
if ddl.Err == nil && ddl.Execute() != nil {
log.Errorf("Error running above statement on %s: %s", t.Instance, ddl.Err)
skipCount := len(diff.TableDiffs) - n
if skipCount > 1 {
log.Warnf("Skipping %d additional statements on %s %s", skipCount-1, t.Instance, t.SchemaFromDir.Name)
}
errCount += skipCount
break
}
}
// If we had diffs but they were all no-ops due to StatementModifiers,
// still display message about no actions taken
if statementCounter == 0 {
fmt.Println("(nothing to do)")
for _, table := range diff.UnsupportedTables {
targetStmtCount++
unsupportedCount++
log.Warnf("Skipping table %s: unable to generate ALTER TABLE due to use of unsupported features", table.Name)
}
if targetStmtCount == 0 {
log.Info("(nothing to do)")
}
fmt.Println()
t.Done()
}
if errCount == 0 {
if errCount+unsupportedCount == 0 {
return nil
}
var plural string
if errCount > 1 {
var plural, reason string
code := CodeFatalError
if errCount+unsupportedCount > 1 {
plural = "s"
}
return NewExitValue(CodePartialError, "Skipped %d operation%s due to error%s", errCount, plural, plural)
if errCount == 0 {
code = CodePartialError
reason = "unsupported feature"
} else if unsupportedCount == 0 {
reason = "error"
} else {
reason = "unsupported features or error"
}
return NewExitValue(code, "Skipped %d operation%s due to %s%s", errCount+unsupportedCount, plural, reason, plural)
}
View
@@ -9,6 +9,7 @@ import (
"strings"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/skeema/mycli"
"golang.org/x/crypto/ssh/terminal"
)
@@ -77,6 +78,10 @@ func AddGlobalConfigFiles(cfg *mycli.Config) {
cfg.MarkDirty()
fmt.Println()
}
if cfg.GetBool("debug") {
log.SetLevel(log.DebugLevel)
}
}
// PromptPassword reads a password from STDIN without echoing the typed
@@ -105,6 +110,7 @@ func main() {
CommandSuite.AddOption(mycli.StringOption("schema", 0, "", "Database schema name").Hidden())
CommandSuite.AddOption(mycli.StringOption("temp-schema", 't', "_skeema_tmp", "Name of temporary schema to use for intermediate operations. Will be created and dropped unless --reuse-temp-schema enabled."))
CommandSuite.AddOption(mycli.BoolOption("reuse-temp-schema", 0, false, "Do not drop temp-schema when done. Useful for running without create/drop database privileges."))
CommandSuite.AddOption(mycli.BoolOption("debug", 0, false, "Enable debug logging"))
cfg, err := mycli.ParseCLI(CommandSuite, os.Args)
if err != nil {
Oops, something went wrong.

0 comments on commit d83468a

Please sign in to comment.