pax_global_header00006660000000000000000000000064150177733120014520gustar00rootroot0000000000000052 comment=d744156745998478ffa3c5395c3336ae6f0b0c0c mysql-1.6.0/000077500000000000000000000000001501777331200126715ustar00rootroot00000000000000mysql-1.6.0/.github/000077500000000000000000000000001501777331200142315ustar00rootroot00000000000000mysql-1.6.0/.github/dependabot.yml000066400000000000000000000010201501777331200170520ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" mysql-1.6.0/.github/workflows/000077500000000000000000000000001501777331200162665ustar00rootroot00000000000000mysql-1.6.0/.github/workflows/tests.yml000066400000000000000000000010761501777331200201570ustar00rootroot00000000000000name: tests on: push: pull_request: permissions: contents: read jobs: run-tests: strategy: matrix: go: ['1.21', '1.20', '1.19'] platform: [ubuntu-latest] runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - name: Check out code into the Go module directory uses: actions/checkout@v4 # Run build of the application - name: Run build run: go build . - name: Run tests run: go test -race -count=1 -v ./... mysql-1.6.0/.gitignore000066400000000000000000000000571501777331200146630ustar00rootroot00000000000000TODO* documents coverage.txt _book .idea vendormysql-1.6.0/License000066400000000000000000000021111501777331200141710ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013-NOW Jinzhu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mysql-1.6.0/README.md000066400000000000000000000033461501777331200141560ustar00rootroot00000000000000# GORM MySQL Driver ## Quick Start ```go import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) // https://github.com/go-sql-driver/mysql dsn := "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) ``` ## Configuration ```go import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) var datetimePrecision = 2 db, err := gorm.Open(mysql.New(mysql.Config{ DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name, refer https://github.com/go-sql-driver/mysql#dsn-data-source-name DefaultStringSize: 256, // add default size for string fields, by default, will use db type `longtext` for fields without size, not a primary key, no index defined and don't have default values DisableDatetimePrecision: true, // disable datetime precision support, which not supported before MySQL 5.6 DefaultDatetimePrecision: &datetimePrecision, // default datetime precision DontSupportRenameIndex: true, // drop & create index when rename index, rename index not supported before MySQL 5.7, MariaDB DontSupportRenameColumn: true, // use change when rename column, rename rename not supported before MySQL 8, MariaDB SkipInitializeWithVersion: false, // smart configure based on used version }), &gorm.Config{}) ``` ## Customized Driver ```go import ( _ "example.com/my_mysql_driver" "gorm.io/gorm" "gorm.io/driver/mysql" ) db, err := gorm.Open(mysql.New(mysql.Config{ DriverName: "my_mysql_driver_name", DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name, refer https://github.com/go-sql-driver/mysql#dsn-data-source-name }) ``` Checkout [https://gorm.io](https://gorm.io) for details. mysql-1.6.0/error_translator.go000066400000000000000000000011521501777331200166210ustar00rootroot00000000000000package mysql import ( "github.com/go-sql-driver/mysql" "gorm.io/gorm" ) // The error codes to map mysql errors to gorm errors, here is the mysql error codes reference https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html. var errCodes = map[uint16]error{ 1062: gorm.ErrDuplicatedKey, 1451: gorm.ErrForeignKeyViolated, 1452: gorm.ErrForeignKeyViolated, } func (dialector Dialector) Translate(err error) error { if mysqlErr, ok := err.(*mysql.MySQLError); ok { if translatedErr, found := errCodes[mysqlErr.Number]; found { return translatedErr } return mysqlErr } return err } mysql-1.6.0/error_translator_test.go000066400000000000000000000031061501777331200176610ustar00rootroot00000000000000package mysql import ( "errors" "testing" "gorm.io/gorm" "github.com/go-sql-driver/mysql" ) func TestDialector_Translate(t *testing.T) { normalErr := errors.New("normal error") type fields struct { Config *Config } type args struct { err error } tests := []struct { name string fields fields args args want error }{ { name: "it should translate error to ErrDuplicatedKey when the error number is 1062", args: args{err: &mysql.MySQLError{Number: uint16(1062)}}, want: gorm.ErrDuplicatedKey, }, { name: "it should translate error to ErrForeignKeyViolated when the error number is 1451", args: args{err: &mysql.MySQLError{Number: uint16(1451)}}, want: gorm.ErrForeignKeyViolated, }, { name: "it should translate error to ErrForeignKeyViolated when the error number is 1452", args: args{err: &mysql.MySQLError{Number: uint16(1452)}}, want: gorm.ErrForeignKeyViolated, }, { name: "it should not translate the error when the error number is not registered in translated error codes", args: args{err: &mysql.MySQLError{Number: uint16(8888)}}, want: &mysql.MySQLError{Number: uint16(8888)}, }, { name: "it should not translate the error when the error is not a mysql error", args: args{err: normalErr}, want: normalErr, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dialector := Dialector{ Config: tt.fields.Config, } if err := dialector.Translate(tt.args.err); !errors.Is(err, tt.want) { t.Errorf("Translate() got error = %v, want error %v", err, tt.want) } }) } } mysql-1.6.0/go.mod000066400000000000000000000004521501777331200140000ustar00rootroot00000000000000module gorm.io/driver/mysql go 1.18 require ( github.com/go-sql-driver/mysql v1.8.1 gorm.io/gorm v1.30.0 ) require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect golang.org/x/text v0.20.0 // indirect ) mysql-1.6.0/go.sum000066400000000000000000000017241501777331200140300ustar00rootroot00000000000000filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= mysql-1.6.0/migrator.go000066400000000000000000000357701501777331200150600ustar00rootroot00000000000000package mysql import ( "database/sql" "fmt" "strconv" "strings" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/migrator" "gorm.io/gorm/schema" ) const indexSql = ` SELECT TABLE_NAME, COLUMN_NAME, INDEX_NAME, NON_UNIQUE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY INDEX_NAME, SEQ_IN_INDEX` var typeAliasMap = map[string][]string{ "bool": {"tinyint"}, "tinyint": {"bool"}, } type Migrator struct { migrator.Migrator Dialector } func (m Migrator) FullDataTypeOf(field *schema.Field) clause.Expr { expr := m.Migrator.FullDataTypeOf(field) if value, ok := field.TagSettings["COMMENT"]; ok { expr.SQL += " COMMENT " + m.Dialector.Explain("?", value) } return expr } // MigrateColumnUnique migrate column's UNIQUE constraint. // In MySQL, ColumnType's Unique is affected by UniqueIndex, so we have to take care of the UniqueIndex. func (m Migrator) MigrateColumnUnique(value interface{}, field *schema.Field, columnType gorm.ColumnType) error { unique, ok := columnType.Unique() if !ok || field.PrimaryKey { return nil // skip primary key } queryTx, execTx := m.GetQueryAndExecTx() return m.RunWithValue(value, func(stmt *gorm.Statement) error { // We're currently only receiving boolean values on `Unique` tag, // so the UniqueConstraint name is fixed constraint := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName) if unique { // Clean up redundant unique indexes indexes, _ := queryTx.Migrator().GetIndexes(value) for _, index := range indexes { if uni, ok := index.Unique(); !ok || !uni { continue } if columns := index.Columns(); len(columns) != 1 || columns[0] != field.DBName { continue } if name := index.Name(); name == constraint || name == field.UniqueIndex { continue } if err := execTx.Migrator().DropIndex(value, index.Name()); err != nil { return err } } hasConstraint := queryTx.Migrator().HasConstraint(value, constraint) switch { case field.Unique && !hasConstraint: if field.Unique { if err := execTx.Migrator().CreateConstraint(value, constraint); err != nil { return err } } // field isn't Unique but ColumnType's Unique is reported by UniqueConstraint. case !field.Unique && hasConstraint: if err := execTx.Migrator().DropConstraint(value, constraint); err != nil { return err } if field.UniqueIndex != "" { if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { return err } } } if field.UniqueIndex != "" && !queryTx.Migrator().HasIndex(value, field.UniqueIndex) { if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { return err } } } else { if field.Unique { if err := execTx.Migrator().CreateConstraint(value, constraint); err != nil { return err } } if field.UniqueIndex != "" && !queryTx.Migrator().HasIndex(value, field.UniqueIndex) { if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { return err } } } return nil }) } func (m Migrator) AddColumn(value interface{}, name string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { // avoid using the same name field f := stmt.Schema.LookUpField(name) if f == nil { return fmt.Errorf("failed to look up field with name: %s", name) } if !f.IgnoreMigration { fieldType := m.FullDataTypeOf(f) columnName := clause.Column{Name: f.DBName} values := []interface{}{m.CurrentTable(stmt), columnName, fieldType} var alterSql strings.Builder alterSql.WriteString("ALTER TABLE ? ADD ? ?") if f.PrimaryKey || strings.Contains(strings.ToLower(fieldType.SQL), "auto_increment") { alterSql.WriteString(", ADD PRIMARY KEY (?)") values = append(values, columnName) } return m.DB.Exec(alterSql.String(), values...).Error } return nil }) } func (m Migrator) AlterColumn(value interface{}, field string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { if stmt.Schema != nil { if field := stmt.Schema.LookUpField(field); field != nil { fullDataType := m.FullDataTypeOf(field) if m.Dialector.DontSupportRenameColumnUnique { fullDataType.SQL = strings.Replace(fullDataType.SQL, " UNIQUE ", " ", 1) } return m.DB.Exec( "ALTER TABLE ? MODIFY COLUMN ? ?", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fullDataType, ).Error } } return fmt.Errorf("failed to look up field with name: %s", field) }) } func (m Migrator) TiDBVersion() (isTiDB bool, major, minor, patch int, err error) { // TiDB version string looks like: // "5.7.25-TiDB-v6.5.0" or "5.7.25-TiDB-v6.4.0-serverless" tidbVersionArray := strings.Split(m.Dialector.ServerVersion, "-") if len(tidbVersionArray) < 3 || tidbVersionArray[1] != "TiDB" { // It isn't TiDB return } rawVersion := strings.TrimPrefix(tidbVersionArray[2], "v") realVersionArray := strings.Split(rawVersion, ".") if major, err = strconv.Atoi(realVersionArray[0]); err != nil { err = fmt.Errorf("failed to parse the version of TiDB, the major version is: %s", realVersionArray[0]) return } if minor, err = strconv.Atoi(realVersionArray[1]); err != nil { err = fmt.Errorf("failed to parse the version of TiDB, the minor version is: %s", realVersionArray[1]) return } if patch, err = strconv.Atoi(realVersionArray[2]); err != nil { err = fmt.Errorf("failed to parse the version of TiDB, the patch version is: %s", realVersionArray[2]) return } isTiDB = true return } func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { if !m.Dialector.DontSupportRenameColumn { return m.Migrator.RenameColumn(value, oldName, newName) } var field *schema.Field if stmt.Schema != nil { if f := stmt.Schema.LookUpField(oldName); f != nil { oldName = f.DBName field = f } if f := stmt.Schema.LookUpField(newName); f != nil { newName = f.DBName field = f } } if field != nil { return m.DB.Exec( "ALTER TABLE ? CHANGE ? ? ?", m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName}, m.FullDataTypeOf(field), ).Error } return fmt.Errorf("failed to look up field with name: %s", newName) }) } func (m Migrator) DropConstraint(value interface{}, name string) error { if !m.Dialector.Config.DontSupportDropConstraint { return m.Migrator.DropConstraint(value, name) } return m.RunWithValue(value, func(stmt *gorm.Statement) error { constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name) if constraint != nil { name = constraint.GetName() switch constraint.(type) { case *schema.Constraint: return m.DB.Exec("ALTER TABLE ? DROP FOREIGN KEY ?", clause.Table{Name: table}, clause.Column{Name: name}).Error case *schema.CheckConstraint: return m.DB.Exec("ALTER TABLE ? DROP CHECK ?", clause.Table{Name: table}, clause.Column{Name: name}).Error } } if m.HasIndex(value, name) { return m.DB.Exec("ALTER TABLE ? DROP INDEX ?", clause.Table{Name: table}, clause.Column{Name: name}).Error } return nil }) } func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error { if !m.Dialector.DontSupportRenameIndex { return m.RunWithValue(value, func(stmt *gorm.Statement) error { return m.DB.Exec( "ALTER TABLE ? RENAME INDEX ? TO ?", m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName}, ).Error }) } return m.RunWithValue(value, func(stmt *gorm.Statement) error { err := m.DropIndex(value, oldName) if err != nil { return err } if stmt.Schema != nil { if idx := stmt.Schema.LookIndex(newName); idx == nil { if idx = stmt.Schema.LookIndex(oldName); idx != nil { opts := m.BuildIndexOptions(idx.Fields, stmt) values := []interface{}{clause.Column{Name: newName}, m.CurrentTable(stmt), opts} createIndexSQL := "CREATE " if idx.Class != "" { createIndexSQL += idx.Class + " " } createIndexSQL += "INDEX ? ON ??" if idx.Type != "" { createIndexSQL += " USING " + idx.Type } return m.DB.Exec(createIndexSQL, values...).Error } } } return m.CreateIndex(value, newName) }) } func (m Migrator) DropTable(values ...interface{}) error { values = m.ReorderModels(values, false) return m.DB.Connection(func(tx *gorm.DB) error { tx.Exec("SET FOREIGN_KEY_CHECKS = 0;") for i := len(values) - 1; i >= 0; i-- { if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error { return tx.Exec("DROP TABLE IF EXISTS ? CASCADE", m.CurrentTable(stmt)).Error }); err != nil { return err } } return tx.Exec("SET FOREIGN_KEY_CHECKS = 1;").Error }) } // ColumnTypes column types return columnTypes,error func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) { columnTypes := make([]gorm.ColumnType, 0) err := m.RunWithValue(value, func(stmt *gorm.Statement) error { var ( currentDatabase, table = m.CurrentSchema(stmt, stmt.Table) columnTypeSQL = "SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale " rows, err = m.DB.Session(&gorm.Session{}).Table(table).Limit(1).Rows() ) if err != nil { return err } rawColumnTypes, err := rows.ColumnTypes() if err != nil { return err } if err := rows.Close(); err != nil { return err } if !m.DisableDatetimePrecision { columnTypeSQL += ", datetime_precision " } columnTypeSQL += "FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ORDINAL_POSITION" columns, rowErr := m.DB.Table(table).Raw(columnTypeSQL, currentDatabase, table).Rows() if rowErr != nil { return rowErr } defer columns.Close() for columns.Next() { var ( column migrator.ColumnType datetimePrecision sql.NullInt64 extraValue sql.NullString columnKey sql.NullString values = []interface{}{ &column.NameValue, &column.DefaultValueValue, &column.NullableValue, &column.DataTypeValue, &column.LengthValue, &column.ColumnTypeValue, &columnKey, &extraValue, &column.CommentValue, &column.DecimalSizeValue, &column.ScaleValue, } ) if !m.DisableDatetimePrecision { values = append(values, &datetimePrecision) } if scanErr := columns.Scan(values...); scanErr != nil { return scanErr } column.PrimaryKeyValue = sql.NullBool{Bool: false, Valid: true} column.UniqueValue = sql.NullBool{Bool: false, Valid: true} switch columnKey.String { case "PRI": column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true} case "UNI": column.UniqueValue = sql.NullBool{Bool: true, Valid: true} } if strings.Contains(extraValue.String, "auto_increment") { column.AutoIncrementValue = sql.NullBool{Bool: true, Valid: true} } // only trim paired single-quotes s := column.DefaultValueValue.String for (len(s) >= 3 && s[0] == '\'' && s[len(s)-1] == '\'' && s[len(s)-2] != '\\') || (len(s) == 2 && s == "''") { s = s[1 : len(s)-1] } column.DefaultValueValue.String = s if m.Dialector.DontSupportNullAsDefaultValue { // rewrite mariadb default value like other version if column.DefaultValueValue.Valid && column.DefaultValueValue.String == "NULL" { column.DefaultValueValue.Valid = false column.DefaultValueValue.String = "" } } if datetimePrecision.Valid { column.DecimalSizeValue = datetimePrecision } for _, c := range rawColumnTypes { if c.Name() == column.NameValue.String { column.SQLColumnType = c break } } columnTypes = append(columnTypes, column) } return nil }) return columnTypes, err } func (m Migrator) CurrentDatabase() (name string) { baseName := m.Migrator.CurrentDatabase() m.DB.Raw( "SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE ? ORDER BY SCHEMA_NAME=? DESC,SCHEMA_NAME limit 1", baseName+"%", baseName).Scan(&name) return } func (m Migrator) GetTables() (tableList []string, err error) { err = m.DB.Raw("SELECT TABLE_NAME FROM information_schema.tables where TABLE_SCHEMA=?", m.CurrentDatabase()). Scan(&tableList).Error return } func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) { indexes := make([]gorm.Index, 0) err := m.RunWithValue(value, func(stmt *gorm.Statement) error { result := make([]*Index, 0) schema, table := m.CurrentSchema(stmt, stmt.Table) scanErr := m.DB.Table(table).Raw(indexSql, schema, table).Scan(&result).Error if scanErr != nil { return scanErr } indexMap, indexNames := groupByIndexName(result) for _, name := range indexNames { idx := indexMap[name] if len(idx) == 0 { continue } tempIdx := &migrator.Index{ TableName: idx[0].TableName, NameValue: idx[0].IndexName, PrimaryKeyValue: sql.NullBool{ Bool: idx[0].IndexName == "PRIMARY", Valid: true, }, UniqueValue: sql.NullBool{ Bool: idx[0].NonUnique == 0, Valid: true, }, } for _, x := range idx { tempIdx.ColumnList = append(tempIdx.ColumnList, x.ColumnName) } indexes = append(indexes, tempIdx) } return nil }) return indexes, err } // Index table index info type Index struct { TableName string `gorm:"column:TABLE_NAME"` ColumnName string `gorm:"column:COLUMN_NAME"` IndexName string `gorm:"column:INDEX_NAME"` NonUnique int32 `gorm:"column:NON_UNIQUE"` } func groupByIndexName(indexList []*Index) (map[string][]*Index, []string) { columnIndexMap := make(map[string][]*Index, len(indexList)) indexNames := make([]string, 0, len(indexList)) for _, idx := range indexList { if _, ok := columnIndexMap[idx.IndexName]; !ok { indexNames = append(indexNames, idx.IndexName) } columnIndexMap[idx.IndexName] = append(columnIndexMap[idx.IndexName], idx) } return columnIndexMap, indexNames } func (m Migrator) CurrentSchema(stmt *gorm.Statement, table string) (string, string) { if tables := strings.Split(table, `.`); len(tables) == 2 { return tables[0], tables[1] } m.DB = m.DB.Table(table) return m.CurrentDatabase(), table } func (m Migrator) GetTypeAliases(databaseTypeName string) []string { return typeAliasMap[databaseTypeName] } // TableType table type return tableType,error func (m Migrator) TableType(value interface{}) (tableType gorm.TableType, err error) { var table migrator.TableType err = m.RunWithValue(value, func(stmt *gorm.Statement) error { var ( values = []interface{}{ &table.SchemaValue, &table.NameValue, &table.TypeValue, &table.CommentValue, } currentDatabase, tableName = m.CurrentSchema(stmt, stmt.Table) tableTypeSQL = "SELECT table_schema, table_name, table_type, table_comment FROM information_schema.tables WHERE table_schema = ? AND table_name = ?" ) row := m.DB.Table(tableName).Raw(tableTypeSQL, currentDatabase, tableName).Row() if scanErr := row.Scan(values...); scanErr != nil { return scanErr } return nil }) return table, err } mysql-1.6.0/mysql.go000066400000000000000000000343141501777331200143720ustar00rootroot00000000000000package mysql import ( "context" "database/sql" "fmt" "math" "regexp" "strconv" "strings" "time" "github.com/go-sql-driver/mysql" "gorm.io/gorm" "gorm.io/gorm/callbacks" "gorm.io/gorm/clause" "gorm.io/gorm/logger" "gorm.io/gorm/migrator" "gorm.io/gorm/schema" "gorm.io/gorm/utils" ) const ( DefaultDriverName = "mysql" 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 // As of MySQL 8.0.19, ALTER TABLE permits more general (and SQL standard) syntax // for dropping and altering existing constraints of any type. // see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html DontSupportDropConstraint 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 Open(dsn string) gorm.Dialector { dsnConf, _ := mysql.ParseDSN(dsn) return &Dialector{Config: &Config{DSN: dsn, DSNConfig: dsnConf}} } func New(config Config) gorm.Dialector { switch { case config.DSN == "" && config.DSNConfig != nil: config.DSN = config.DSNConfig.FormatDSN() case config.DSN != "" && config.DSNConfig == nil: config.DSNConfig, _ = mysql.ParseDSN(config.DSN) } return &Dialector{Config: &config} } func (dialector Dialector) Name() string { return DefaultDriverName } // NowFunc return now func func (dialector Dialector) NowFunc(n int) func() time.Time { return func() time.Time { round := time.Second / time.Duration(math.Pow10(n)) return time.Now().Round(round) } } func (dialector Dialector) Apply(config *gorm.Config) error { if config.NowFunc != nil { return nil } if dialector.DefaultDatetimePrecision == nil { dialector.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. config.NowFunc = dialector.NowFunc(*dialector.DefaultDatetimePrecision) return nil } func (dialector Dialector) Initialize(db *gorm.DB) (err error) { if dialector.DriverName == "" { dialector.DriverName = DefaultDriverName } if dialector.DefaultDatetimePrecision == nil { dialector.DefaultDatetimePrecision = &defaultDatetimePrecision } if dialector.Conn != nil { db.ConnPool = dialector.Conn } else { db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN) if err != nil { return err } } withReturning := false if !dialector.Config.SkipInitializeWithVersion { err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion) if err != nil { return err } if strings.Contains(dialector.ServerVersion, "MariaDB") { dialector.Config.DontSupportRenameIndex = true dialector.Config.DontSupportRenameColumn = true dialector.Config.DontSupportForShareClause = true dialector.Config.DontSupportNullAsDefaultValue = true withReturning = checkVersion(dialector.ServerVersion, "10.5") } else if strings.HasPrefix(dialector.ServerVersion, "5.6.") { dialector.Config.DontSupportRenameIndex = true dialector.Config.DontSupportRenameColumn = true dialector.Config.DontSupportForShareClause = true dialector.Config.DontSupportDropConstraint = true } else if strings.HasPrefix(dialector.ServerVersion, "5.7.") { dialector.Config.DontSupportRenameColumn = true dialector.Config.DontSupportForShareClause = true dialector.Config.DontSupportDropConstraint = true } else if strings.HasPrefix(dialector.ServerVersion, "5.") { dialector.Config.DisableDatetimePrecision = true dialector.Config.DontSupportRenameIndex = true dialector.Config.DontSupportRenameColumn = true dialector.Config.DontSupportForShareClause = true dialector.Config.DontSupportDropConstraint = true } if strings.Contains(dialector.ServerVersion, "TiDB") { dialector.Config.DontSupportRenameColumnUnique = true } } // register callbacks callbackConfig := &callbacks.Config{ CreateClauses: CreateClauses, QueryClauses: QueryClauses, UpdateClauses: UpdateClauses, DeleteClauses: DeleteClauses, } if !dialector.Config.DisableWithReturning && withReturning { if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") { callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING") } if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") { callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING") } if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") { callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING") } } callbacks.RegisterDefaultCallbacks(db, callbackConfig) for k, v := range dialector.ClauseBuilders() { if _, ok := db.ClauseBuilders[k]; !ok { db.ClauseBuilders[k] = v } } 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 Dialector) ClauseBuilders() map[string]clause.ClauseBuilder { clauseBuilders := map[string]clause.ClauseBuilder{ ClauseOnConflict: func(c clause.Clause, builder clause.Builder) { onConflict, ok := c.Expression.(clause.OnConflict) if !ok { c.Build(builder) return } builder.WriteString("ON DUPLICATE KEY UPDATE ") if len(onConflict.DoUpdates) == 0 { if s := builder.(*gorm.Statement).Schema; s != nil { var column clause.Column onConflict.DoNothing = false if s.PrioritizedPrimaryField != nil { column = clause.Column{Name: s.PrioritizedPrimaryField.DBName} } else if len(s.DBNames) > 0 { column = clause.Column{Name: s.DBNames[0]} } if column.Name != "" { onConflict.DoUpdates = []clause.Assignment{{Column: column, Value: column}} } builder.(*gorm.Statement).AddClause(onConflict) } } for idx, assignment := range onConflict.DoUpdates { if idx > 0 { builder.WriteByte(',') } builder.WriteQuoted(assignment.Column) builder.WriteByte('=') if column, ok := assignment.Value.(clause.Column); ok && column.Table == "excluded" { column.Table = "" builder.WriteString("VALUES(") builder.WriteQuoted(column) builder.WriteByte(')') } else { builder.AddVar(builder, assignment.Value) } } }, ClauseValues: func(c clause.Clause, builder clause.Builder) { if values, ok := c.Expression.(clause.Values); ok && len(values.Columns) == 0 { builder.WriteString("VALUES()") return } c.Build(builder) }, } if dialector.Config.DontSupportForShareClause { clauseBuilders[ClauseFor] = func(c clause.Clause, builder clause.Builder) { if values, ok := c.Expression.(clause.Locking); ok && strings.EqualFold(values.Strength, "SHARE") { builder.WriteString("LOCK IN SHARE MODE") return } c.Build(builder) } } return clauseBuilders } func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression { return clause.Expr{SQL: "DEFAULT"} } func (dialector Dialector) Migrator(db *gorm.DB) gorm.Migrator { return Migrator{ Migrator: migrator.Migrator{ Config: migrator.Config{ DB: db, Dialector: dialector, }, }, Dialector: dialector, } } func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) { writer.WriteByte('?') } func (dialector Dialector) QuoteTo(writer clause.Writer, str string) { var ( underQuoted, selfQuoted bool continuousBacktick int8 shiftDelimiter int8 ) for _, v := range []byte(str) { switch v { case '`': continuousBacktick++ if continuousBacktick == 2 { writer.WriteString("``") continuousBacktick = 0 } case '.': if continuousBacktick > 0 || !selfQuoted { shiftDelimiter = 0 underQuoted = false continuousBacktick = 0 writer.WriteByte('`') } writer.WriteByte(v) continue default: if shiftDelimiter-continuousBacktick <= 0 && !underQuoted { writer.WriteByte('`') underQuoted = true if selfQuoted = continuousBacktick > 0; selfQuoted { continuousBacktick -= 1 } } for ; continuousBacktick > 0; continuousBacktick -= 1 { writer.WriteString("``") } writer.WriteByte(v) } shiftDelimiter++ } if continuousBacktick > 0 && !selfQuoted { writer.WriteString("``") } writer.WriteByte('`') } type localTimeInterface interface { In(loc *time.Location) time.Time } func (dialector Dialector) Explain(sql string, vars ...interface{}) string { if dialector.DSNConfig != nil && dialector.DSNConfig.Loc != nil { for i, v := range vars { if p, ok := v.(localTimeInterface); ok { func(i int, t localTimeInterface) { defer func() { recover() }() vars[i] = t.In(dialector.DSNConfig.Loc) }(i, p) } } } return logger.ExplainSQL(sql, nil, `'`, vars...) } func (dialector Dialector) DataTypeOf(field *schema.Field) string { switch field.DataType { case schema.Bool: return "boolean" case schema.Int, schema.Uint: return dialector.getSchemaIntAndUnitType(field) case schema.Float: return dialector.getSchemaFloatType(field) case schema.String: return dialector.getSchemaStringType(field) case schema.Time: return dialector.getSchemaTimeType(field) case schema.Bytes: return dialector.getSchemaBytesType(field) default: return dialector.getSchemaCustomType(field) } } func (dialector Dialector) getSchemaFloatType(field *schema.Field) string { if field.Precision > 0 { return fmt.Sprintf("decimal(%d, %d)", field.Precision, field.Scale) } if field.Size <= 32 { return "float" } return "double" } func (dialector Dialector) getSchemaStringType(field *schema.Field) string { size := field.Size if size == 0 { if dialector.DefaultStringSize > 0 { size = int(dialector.DefaultStringSize) } else { hasIndex := field.TagSettings["INDEX"] != "" || field.TagSettings["UNIQUE"] != "" // TEXT, GEOMETRY or JSON column can't have a default value if field.PrimaryKey || field.HasDefaultValue || hasIndex { size = 191 // utf8mb4 } } } if size >= 65536 && size <= int(math.Pow(2, 24)) { return "mediumtext" } if size > int(math.Pow(2, 24)) || size <= 0 { return "longtext" } return fmt.Sprintf("varchar(%d)", size) } func (dialector Dialector) getSchemaTimeType(field *schema.Field) string { if !dialector.DisableDatetimePrecision && field.Precision == 0 && field.TagSettings["PRECISION"] == "" { field.Precision = *dialector.DefaultDatetimePrecision } var precision string if field.Precision > 0 { precision = fmt.Sprintf("(%d)", field.Precision) } if field.NotNull || field.PrimaryKey { return "datetime" + precision } return "datetime" + precision + " NULL" } func (dialector Dialector) getSchemaBytesType(field *schema.Field) string { if field.Size > 0 && field.Size < 65536 { return fmt.Sprintf("varbinary(%d)", field.Size) } if field.Size >= 65536 && field.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(field *schema.Field) (bool, string) { if field.PrimaryKey && field.HasDefaultValue && strings.ToLower(strings.TrimSpace(field.DefaultValue)) == AutoRandomTag { field.DefaultValue = "" sqlType := "bigint" if field.DataType == schema.Uint { sqlType += " unsigned" } sqlType += " auto_random" return true, sqlType } return false, "" } func (dialector Dialector) getSchemaIntAndUnitType(field *schema.Field) string { if autoRandom, typeString := autoRandomType(field); autoRandom { return typeString } constraint := func(sqlType string) string { if field.DataType == schema.Uint { sqlType += " unsigned" } if field.AutoIncrement { sqlType += " AUTO_INCREMENT" } return sqlType } switch { case field.Size <= 8: return constraint("tinyint") case field.Size <= 16: return constraint("smallint") case field.Size <= 24: return constraint("mediumint") case field.Size <= 32: return constraint("int") default: return constraint("bigint") } } func (dialector Dialector) getSchemaCustomType(field *schema.Field) string { sqlType := string(field.DataType) if field.AutoIncrement && !strings.Contains(strings.ToLower(sqlType), " auto_increment") { sqlType += " AUTO_INCREMENT" } return sqlType } func (dialector Dialector) SavePoint(tx *gorm.DB, name string) error { return tx.Exec("SAVEPOINT " + name).Error } func (dialector Dialector) RollbackTo(tx *gorm.DB, name string) error { return tx.Exec("ROLLBACK TO SAVEPOINT " + name).Error } // checkVersion newer or equal returns true, old returns false func checkVersion(newVersion, oldVersion string) bool { if newVersion == oldVersion { return true } var ( versionTrimmerRegexp = regexp.MustCompile(`^(\d+).*$`) newVersions = strings.Split(newVersion, ".") oldVersions = strings.Split(oldVersion, ".") ) for idx, nv := range newVersions { if len(oldVersions) <= idx { return true } nvi, _ := strconv.Atoi(versionTrimmerRegexp.ReplaceAllString(nv, "$1")) ovi, _ := strconv.Atoi(versionTrimmerRegexp.ReplaceAllString(oldVersions[idx], "$1")) if nvi == ovi { continue } return nvi > ovi } return false } mysql-1.6.0/mysql_test.go000066400000000000000000000047571501777331200154410ustar00rootroot00000000000000package mysql import ( "bytes" "testing" "github.com/go-sql-driver/mysql" ) func TestNew(t *testing.T) { dialector := New(Config{DSN: "gorm:gorm@tcp(127.0.0.1:9910)/gorm?charset=utf8&parseTime=True&loc=Local"}) d, ok := dialector.(*Dialector) if !ok { t.Fatal("dialector is not *Dialector") } if d.DSNConfig == nil { t.Error("dialector.DSNConfig is nil") } dialector = New(Config{DSNConfig: mysql.NewConfig()}) d, ok = dialector.(*Dialector) if !ok { t.Fatal("dialector is not *Dialector") } if d.DSN == "" { t.Error("dialector.DSN is empty") } } func TestDialector_QuoteTo(t *testing.T) { testdatas := []struct { raw string expect string }{ {"datadase.tableUser", "`datadase`.`tableUser`"}, {"datadase.table`User", "`datadase`.`table``User`"}, {"`a`.`b`", "`a`.`b`"}, {"`a`.b`", "`a`.`b```"}, {"a.`b`", "`a`.`b`"}, {"`a`.b`c", "`a`.`b``c`"}, {"`a`.`b`c`", "`a`.`b``c`"}, {"`a`.b", "`a`.`b`"}, {"`ab`", "`ab`"}, {"`a``b`", "`a``b`"}, {"`a```b`", "`a````b`"}, {"a`b", "`a``b`"}, {"ab", "`ab`"}, {"`a.b`", "`a.b`"}, {"a.b", "`a`.`b`"}, } dailor := Open("") for _, item := range testdatas { buf := &bytes.Buffer{} dailor.QuoteTo(buf, item.raw) if buf.String() != item.expect { t.Errorf("quote %q fail, got %q, expect %q", item.raw, buf.String(), item.expect) } } } // BenchmarkDialector_QuoteTo // Result: // goos: darwin // goarch: amd64 // pkg: gorm.io/driver/mysql // cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz // BenchmarkDialector_QuoteTo 9184232 113.2 ns/op // BenchmarkDialector_QuoteTo-2 9782818 112.3 ns/op // BenchmarkDialector_QuoteTo-4 10726722 109.0 ns/op // BenchmarkDialector_QuoteTo-8 9656778 113.1 ns/op // BenchmarkDialector_QuoteTo-12 10729615 112.7 ns/op func BenchmarkDialector_QuoteTo(b *testing.B) { dailor := Open("") buf := &bytes.Buffer{} b.ResetTimer() for i := 0; i < b.N; i++ { dailor.QuoteTo(buf, "datadase.table`User") buf.Reset() } } func TestCheckVersion(t *testing.T) { versions := map[string]string{ "5.6.1": "5.6", "5.10.2": "5.6", "5.10": "5.6", "10.6.26-MariaDB-1:10.4.26+maria~ubu2004": "10.6", "10.6.26-MariaDB-1:10.4.26+maria~ubu2005": "10.6.3", "10.4.26-MariaDB-1:10.4.26+maria~ubu2004": "5.6", } for k, v := range versions { if !checkVersion(k, v) || checkVersion(v, k) { t.Fatalf("returns %v when comparing %v, %v", checkVersion(k, v), k, v) } } }