func (ddl *DDLStatement) String() string {
	if ddl.IsShellOut() {
		return fmt.Sprintf("\\! %s\n", ddl.shellOut)
	}
	return fs.AddDelimiter(ddl.stmt)
}
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.
package applier
import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	log "github.com/sirupsen/logrus"
	"github.com/skeema/mybase"
	"github.com/skeema/skeema/internal/fs"
	"github.com/skeema/skeema/internal/util"
	"github.com/skeema/tengo"
)
type DDLStatement struct {
	stmt     string
	shellOut *util.ShellOut
	instance      *tengo.Instance
	schemaName    string
	connectParams string
}
func NewDDLStatement(diff tengo.ObjectDiff, mods tengo.StatementModifiers, target *Target) (ddl *DDLStatement, err error) {
	ddl = &DDLStatement{
		instance:   target.Instance,
		schemaName: target.SchemaName,
	}
	if diff.ObjectKey().Type == tengo.ObjectTypeDatabase {
		ddl.schemaName = ""
	}
	var tableSize int64
	if needTableSize(diff, target.Dir.Config) {
		if tableSize, err = getTableSize(target, diff.ObjectKey().Name); err != nil {
			return nil, err
		}
		if safeBelowSize, err := target.Dir.Config.GetBytes("safe-below-size"); err != nil {
			return nil, err
		} else if tableSize < int64(safeBelowSize) {
			mods.AllowUnsafe = true
			log.Debugf("Allowing unsafe operations for %s: size=%d < safe-below-size=%d", diff.ObjectKey(), tableSize, safeBelowSize)
		}
	}
	wrapper, err := getWrapper(target.Dir.Config, diff, tableSize, &mods)
	if err != nil {
		return nil, err
	}
	if ddl.stmt, err = diff.Statement(mods); tengo.IsForbiddenDiff(err) {
		terminalWidth, _ := util.TerminalWidth(int(os.Stderr.Fd()))
		commentedOutStmt := "  # " + util.WrapStringWithPadding(ddl.stmt, terminalWidth-29, "  # ")
		errorText := fmt.Sprintf("Preventing execution of unsafe or potentially destructive statement:\n%s\nUse --allow-unsafe or --safe-below-size to permit this operation. For more information, see Safety Options section of --help.", commentedOutStmt)
		return nil, errors.New(errorText)  
	} else if err != nil {
		return nil, err
	} else if ddl.stmt == "" {
		return nil, nil
	}
	if wrapper == "" {
		ddl.connectParams = getConnectParams(diff, target.Dir.Config)
	} else {
		var socket, port, connOpts string
		if ddl.instance.SocketPath != "" {
			socket = ddl.instance.SocketPath
		} else {
			port = strconv.Itoa(ddl.instance.Port)
		}
		if connOpts, err = util.RealConnectOptions(target.Dir.Config.Get("connect-options")); err != nil {
			return nil, ConfigError(err.Error())
		}
		variables := map[string]string{
			"HOST":        ddl.instance.Host,
			"PORT":        port,
			"SOCKET":      socket,
			"SCHEMA":      ddl.schemaName,
			"USER":        target.Dir.Config.Get("user"),
			"PASSWORD":    target.Dir.Config.Get("password"),
			"ENVIRONMENT": target.Dir.Config.Get("environment"),
			"DDL":         ddl.stmt,
			"CLAUSES":     "",  
			"NAME":        diff.ObjectKey().Name,
			"TABLE":       "",  
			"SIZE":        strconv.FormatInt(tableSize, 10),
			"TYPE":        diff.DiffType().String(),
			"CLASS":       diff.ObjectKey().Type.Caps(),
			"CONNOPTS":    connOpts,
			"DIRNAME":     target.Dir.BaseName(),
			"DIRPATH":     target.Dir.Path,
		}
		if diff.ObjectKey().Type == tengo.ObjectTypeTable {
			td := diff.(*tengo.TableDiff)
			variables["CLAUSES"], _ = td.Clauses(mods)
			variables["TABLE"] = variables["NAME"]
		}
		if ddl.shellOut, err = util.NewInterpolatedShellOut(wrapper, variables); err != nil {
			errorText := fmt.Sprintf("A fatal error occurred with pre-processing a DDL statement: %s.", err)
			return nil, errors.New(errorText)
		}
	}
	return ddl, nil
}
func needTableSize(diff tengo.ObjectDiff, config *mybase.Config) bool {
	if diff.ObjectKey().Type != tengo.ObjectTypeTable {
		return false
	}
	if diff.DiffType() == tengo.DiffTypeCreate {
		return false
	}
	for _, opt := range []string{"safe-below-size", "alter-wrapper-min-size"} {
		if config.Changed(opt) {
			return true
		}
	}
	for _, opt := range []string{"alter-wrapper", "ddl-wrapper"} {
		if strings.Contains(strings.ToUpper(config.Get(opt)), "{SIZE}") {
			return true
		}
	}
	return false
}
func getTableSize(target *Target, tableName string) (int64, error) {
	hasRows, err := target.Instance.TableHasRows(target.SchemaName, tableName)
	if !hasRows || err != nil {
		return 0, err
	}
	return target.Instance.TableSize(target.SchemaName, tableName)
}
func getWrapper(config *mybase.Config, diff tengo.ObjectDiff, tableSize int64, mods *tengo.StatementModifiers) (string, error) {
	wrapper := config.Get("ddl-wrapper")
	if diff.ObjectKey().Type == tengo.ObjectTypeTable && diff.DiffType() == tengo.DiffTypeAlter && config.Changed("alter-wrapper") {
		minSize, err := config.GetBytes("alter-wrapper-min-size")
		if err != nil {
			return "", ConfigError(err.Error())
		}
		if tableSize >= int64(minSize) {
			wrapper = config.Get("alter-wrapper")
			if minSize > 0 {
				log.Debugf("Using alter-wrapper for %s: size=%d >= alter-wrapper-min-size=%d", diff.ObjectKey(), tableSize, minSize)
				if mods.AlgorithmClause != "" || mods.LockClause != "" {
					log.Debug("Ignoring --alter-algorithm and --alter-lock for generating DDL for alter-wrapper")
					mods.AlgorithmClause = ""
					mods.LockClause = ""
				}
			}
		} else {
			log.Debugf("Skipping alter-wrapper for %s: size=%d < alter-wrapper-min-size=%d", diff.ObjectKey(), tableSize, minSize)
		}
	}
	return wrapper, nil
}
func getConnectParams(diff tengo.ObjectDiff, config *mybase.Config) string {
	if td, ok := diff.(*tengo.TableDiff); ok && td.Type == tengo.DiffTypeAlter {
		if config.GetBool("foreign-key-checks") {
			_, addFKs := td.SplitAddForeignKeys()
			if addFKs != nil {
				return "readTimeout=0&foreign_key_checks=1"
			}
		}
		return "readTimeout=0"
	} else if ok && td.Type == tengo.DiffTypeDrop {
		return "readTimeout=0"
	}
	return ""
}
func (ddl *DDLStatement) IsShellOut() bool {
	return (ddl.shellOut != nil)
}