package mysql

import (
	
	
	
	
	
	
	
	

	

	
	
	
	
	
	
	
)

const (
	AutoRandomTag = "auto_random()" // Treated as an auto_random field for tidb
)

type Config struct {
	DriverName                    string
	ServerVersion                 string
	DSN                           string
	DSNConfig                     *mysql.Config
	Conn                          gorm.ConnPool
	SkipInitializeWithVersion     bool
	DefaultStringSize             uint
	DefaultDatetimePrecision      *int
	DisableWithReturning          bool
	DisableDatetimePrecision      bool
	DontSupportRenameIndex        bool
	DontSupportRenameColumn       bool
	DontSupportForShareClause     bool
	DontSupportNullAsDefaultValue bool
	DontSupportRenameColumnUnique bool
}

type Dialector struct {
	*Config
}

var (
	// CreateClauses create clauses
	CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
	// QueryClauses query clauses
	QueryClauses = []string{}
	// UpdateClauses update clauses
	UpdateClauses = []string{"UPDATE", "SET", "WHERE", "ORDER BY", "LIMIT"}
	// DeleteClauses delete clauses
	DeleteClauses = []string{"DELETE", "FROM", "WHERE", "ORDER BY", "LIMIT"}

	defaultDatetimePrecision = 3
)

func ( string) gorm.Dialector {
	,  := mysql.ParseDSN()
	return &Dialector{Config: &Config{DSN: , DSNConfig: }}
}

func ( Config) gorm.Dialector {
	switch {
	case .DSN == "" && .DSNConfig != nil:
		.DSN = .DSNConfig.FormatDSN()
	case .DSN != "" && .DSNConfig == nil:
		.DSNConfig, _ = mysql.ParseDSN(.DSN)
	}
	return &Dialector{Config: &}
}

func ( Dialector) () string {
	return "mysql"
}

// NowFunc return now func
func ( Dialector) ( int) func() time.Time {
	return func() time.Time {
		 := time.Second / time.Duration(math.Pow10())
		return time.Now().Round()
	}
}

func ( Dialector) ( *gorm.Config) error {
	if .NowFunc != nil {
		return nil
	}

	if .DefaultDatetimePrecision == nil {
		.DefaultDatetimePrecision = &defaultDatetimePrecision
	}
	// while maintaining the readability of the code, separate the business logic from
	// the general part and leave it to the function to do it here.
	.NowFunc = .NowFunc(*.DefaultDatetimePrecision)
	return nil
}

func ( Dialector) ( *gorm.DB) ( error) {
	if .DriverName == "" {
		.DriverName = "mysql"
	}

	if .DefaultDatetimePrecision == nil {
		.DefaultDatetimePrecision = &defaultDatetimePrecision
	}

	if .Conn != nil {
		.ConnPool = .Conn
	} else {
		.ConnPool,  = sql.Open(.DriverName, .DSN)
		if  != nil {
			return 
		}
	}

	 := false
	if !.Config.SkipInitializeWithVersion {
		 = .ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&.ServerVersion)
		if  != nil {
			return 
		}

		if strings.Contains(.ServerVersion, "MariaDB") {
			.Config.DontSupportRenameIndex = true
			.Config.DontSupportRenameColumn = true
			.Config.DontSupportForShareClause = true
			.Config.DontSupportNullAsDefaultValue = true
			 = checkVersion(.ServerVersion, "10.5")
		} else if strings.HasPrefix(.ServerVersion, "5.6.") {
			.Config.DontSupportRenameIndex = true
			.Config.DontSupportRenameColumn = true
			.Config.DontSupportForShareClause = true
		} else if strings.HasPrefix(.ServerVersion, "5.7.") {
			.Config.DontSupportRenameColumn = true
			.Config.DontSupportForShareClause = true
		} else if strings.HasPrefix(.ServerVersion, "5.") {
			.Config.DisableDatetimePrecision = true
			.Config.DontSupportRenameIndex = true
			.Config.DontSupportRenameColumn = true
			.Config.DontSupportForShareClause = true
		}

		if strings.Contains(.ServerVersion, "TiDB") {
			.Config.DontSupportRenameColumnUnique = true
		}
	}

	// register callbacks
	 := &callbacks.Config{
		CreateClauses: CreateClauses,
		QueryClauses:  QueryClauses,
		UpdateClauses: UpdateClauses,
		DeleteClauses: DeleteClauses,
	}

	if !.Config.DisableWithReturning &&  {
		.LastInsertIDReversed = true

		if !utils.Contains(.CreateClauses, "RETURNING") {
			.CreateClauses = append(.CreateClauses, "RETURNING")
		}

		if !utils.Contains(.UpdateClauses, "RETURNING") {
			.UpdateClauses = append(.UpdateClauses, "RETURNING")
		}

		if !utils.Contains(.DeleteClauses, "RETURNING") {
			.DeleteClauses = append(.DeleteClauses, "RETURNING")
		}
	}

	callbacks.RegisterDefaultCallbacks(, )

	for ,  := range .ClauseBuilders() {
		.ClauseBuilders[] = 
	}
	return
}

const (
	// ClauseOnConflict for clause.ClauseBuilder ON CONFLICT key
	ClauseOnConflict = "ON CONFLICT"
	// ClauseValues for clause.ClauseBuilder VALUES key
	ClauseValues = "VALUES"
	// ClauseFor for clause.ClauseBuilder FOR key
	ClauseFor = "FOR"
)

func ( Dialector) () map[string]clause.ClauseBuilder {
	 := map[string]clause.ClauseBuilder{
		ClauseOnConflict: func( clause.Clause,  clause.Builder) {
			,  := .Expression.(clause.OnConflict)
			if ! {
				.Build()
				return
			}

			.WriteString("ON DUPLICATE KEY UPDATE ")
			if len(.DoUpdates) == 0 {
				if  := .(*gorm.Statement).Schema;  != nil {
					var  clause.Column
					.DoNothing = false

					if .PrioritizedPrimaryField != nil {
						 = clause.Column{Name: .PrioritizedPrimaryField.DBName}
					} else if len(.DBNames) > 0 {
						 = clause.Column{Name: .DBNames[0]}
					}

					if .Name != "" {
						.DoUpdates = []clause.Assignment{{Column: , Value: }}
					}

					.(*gorm.Statement).AddClause()
				}
			}

			for ,  := range .DoUpdates {
				if  > 0 {
					.WriteByte(',')
				}

				.WriteQuoted(.Column)
				.WriteByte('=')
				if ,  := .Value.(clause.Column);  && .Table == "excluded" {
					.Table = ""
					.WriteString("VALUES(")
					.WriteQuoted()
					.WriteByte(')')
				} else {
					.AddVar(, .Value)
				}
			}
		},
		ClauseValues: func( clause.Clause,  clause.Builder) {
			if ,  := .Expression.(clause.Values);  && len(.Columns) == 0 {
				.WriteString("VALUES()")
				return
			}
			.Build()
		},
	}

	if .Config.DontSupportForShareClause {
		[ClauseFor] = func( clause.Clause,  clause.Builder) {
			if ,  := .Expression.(clause.Locking);  && strings.EqualFold(.Strength, "SHARE") {
				.WriteString("LOCK IN SHARE MODE")
				return
			}
			.Build()
		}
	}

	return 
}

func ( Dialector) ( *schema.Field) clause.Expression {
	return clause.Expr{SQL: "DEFAULT"}
}

func ( Dialector) ( *gorm.DB) gorm.Migrator {
	return Migrator{
		Migrator: migrator.Migrator{
			Config: migrator.Config{
				DB:        ,
				Dialector: ,
			},
		},
		Dialector: ,
	}
}

func ( Dialector) ( clause.Writer,  *gorm.Statement,  interface{}) {
	.WriteByte('?')
}

func ( Dialector) ( clause.Writer,  string) {
	var (
		,  bool
		      int8
		          int8
	)

	for ,  := range []byte() {
		switch  {
		case '`':
			++
			if  == 2 {
				.WriteString("``")
				 = 0
			}
		case '.':
			if  > 0 || ! {
				 = 0
				 = false
				 = 0
				.WriteByte('`')
			}
			.WriteByte()
			continue
		default:
			if - <= 0 && ! {
				.WriteByte('`')
				 = true
				if  =  > 0;  {
					 -= 1
				}
			}

			for ;  > 0;  -= 1 {
				.WriteString("``")
			}

			.WriteByte()
		}
		++
	}

	if  > 0 && ! {
		.WriteString("``")
	}
	.WriteByte('`')
}

type localTimeInterface interface {
	In(loc *time.Location) time.Time
}

func ( Dialector) ( string,  ...interface{}) string {
	if .DSNConfig != nil && .DSNConfig.Loc != nil {
		for ,  := range  {
			if ,  := .(localTimeInterface);  {
				func( int,  localTimeInterface) {
					defer func() {
						recover()
					}()
					[] = .In(.DSNConfig.Loc)
				}(, )
			}
		}
	}
	return logger.ExplainSQL(, nil, `'`, ...)
}

func ( Dialector) ( *schema.Field) string {
	switch .DataType {
	case schema.Bool:
		return "boolean"
	case schema.Int, schema.Uint:
		return .getSchemaIntAndUnitType()
	case schema.Float:
		return .getSchemaFloatType()
	case schema.String:
		return .getSchemaStringType()
	case schema.Time:
		return .getSchemaTimeType()
	case schema.Bytes:
		return .getSchemaBytesType()
	default:
		return .getSchemaCustomType()
	}
}

func ( Dialector) ( *schema.Field) string {
	if .Precision > 0 {
		return fmt.Sprintf("decimal(%d, %d)", .Precision, .Scale)
	}

	if .Size <= 32 {
		return "float"
	}

	return "double"
}

func ( Dialector) ( *schema.Field) string {
	 := .Size
	if  == 0 {
		if .DefaultStringSize > 0 {
			 = int(.DefaultStringSize)
		} else {
			 := .TagSettings["INDEX"] != "" || .TagSettings["UNIQUE"] != ""
			// TEXT, GEOMETRY or JSON column can't have a default value
			if .PrimaryKey || .HasDefaultValue ||  {
				 = 191 // utf8mb4
			}
		}
	}

	if  >= 65536 &&  <= int(math.Pow(2, 24)) {
		return "mediumtext"
	}

	if  > int(math.Pow(2, 24)) ||  <= 0 {
		return "longtext"
	}

	return fmt.Sprintf("varchar(%d)", )
}

func ( Dialector) ( *schema.Field) string {
	if !.DisableDatetimePrecision && .Precision == 0 {
		.Precision = *.DefaultDatetimePrecision
	}

	var  string
	if .Precision > 0 {
		 = fmt.Sprintf("(%d)", .Precision)
	}

	if .NotNull || .PrimaryKey {
		return "datetime" + 
	}
	return "datetime" +  + " NULL"
}

func ( Dialector) ( *schema.Field) string {
	if .Size > 0 && .Size < 65536 {
		return fmt.Sprintf("varbinary(%d)", .Size)
	}

	if .Size >= 65536 && .Size <= int(math.Pow(2, 24)) {
		return "mediumblob"
	}

	return "longblob"
}

// autoRandomType
// field.DataType MUST be `schema.Int` or `schema.Uint`
// Judgement logic:
// 1. Is PrimaryKey;
// 2. Has default value;
// 3. Default value is "auto_random()";
// 4. IGNORE the field.Size, it MUST be bigint;
// 5. CLEAR the default tag, and return true;
// 6. Otherwise, return false.
func autoRandomType( *schema.Field) (bool, string) {
	if .PrimaryKey && .HasDefaultValue &&
		strings.ToLower(strings.TrimSpace(.DefaultValue)) == AutoRandomTag {
		.DefaultValue = ""

		 := "bigint"
		if .DataType == schema.Uint {
			 += " unsigned"
		}
		 += " auto_random"
		return true, 
	}

	return false, ""
}

func ( Dialector) ( *schema.Field) string {
	if ,  := autoRandomType();  {
		return 
	}

	 := func( string) string {
		if .DataType == schema.Uint {
			 += " unsigned"
		}
		if .AutoIncrement {
			 += " AUTO_INCREMENT"
		}
		return 
	}

	switch {
	case .Size <= 8:
		return ("tinyint")
	case .Size <= 16:
		return ("smallint")
	case .Size <= 24:
		return ("mediumint")
	case .Size <= 32:
		return ("int")
	default:
		return ("bigint")
	}
}

func ( Dialector) ( *schema.Field) string {
	 := string(.DataType)

	if .AutoIncrement && !strings.Contains(strings.ToLower(), " auto_increment") {
		 += " AUTO_INCREMENT"
	}

	return 
}

func ( Dialector) ( *gorm.DB,  string) error {
	return .Exec("SAVEPOINT " + ).Error
}

func ( Dialector) ( *gorm.DB,  string) error {
	return .Exec("ROLLBACK TO SAVEPOINT " + ).Error
}

// checkVersion newer or equal returns true, old returns false
func checkVersion(,  string) bool {
	if  ==  {
		return true
	}

	var (
		 = regexp.MustCompile(`^(\d+).*$`)

		 = strings.Split(, ".")
		 = strings.Split(, ".")
	)
	for ,  := range  {
		if len() <=  {
			return true
		}

		,  := strconv.Atoi(.ReplaceAllString(, "$1"))
		,  := strconv.Atoi(.ReplaceAllString([], "$1"))
		if  ==  {
			continue
		}
		return  > 
	}

	return false
}