init repo

This commit is contained in:
liuyuanchuang
2025-12-10 18:33:37 +08:00
commit 48e63894eb
2408 changed files with 1053045 additions and 0 deletions

6
vendor/gorm.io/driver/mysql/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,6 @@
TODO*
documents
coverage.txt
_book
.idea
vendor

21
vendor/gorm.io/driver/mysql/License generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
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.

52
vendor/gorm.io/driver/mysql/README.md generated vendored Normal file
View File

@@ -0,0 +1,52 @@
# 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.

25
vendor/gorm.io/driver/mysql/error_translator.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package 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
}

518
vendor/gorm.io/driver/mysql/migrator.go generated vendored Normal file
View File

@@ -0,0 +1,518 @@
package 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 != "" {
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
}

540
vendor/gorm.io/driver/mysql/mysql.go generated vendored Normal file
View File

@@ -0,0 +1,540 @@
package 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() {
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
}