fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s

This commit is contained in:
Hein
2026-06-23 13:17:16 +02:00
parent 0227912325
commit 1adf50e3db
2436 changed files with 1078758 additions and 114 deletions
+21
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.
+31
View File
@@ -0,0 +1,31 @@
# GORM SQL Server Driver
## USAGE
```go
import (
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
// github.com/microsoft/go-mssqldb
dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
```
## Azure AD Auth
```go
import (
"github.com/microsoft/go-mssqldb/azuread"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
// github.com/microsoft/go-mssqldb
dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
dialector := &sqlserver.Dialector{Config: &sqlserver.Config{DSN: dsn, DriverName: azuread.DriverName}}
db, err := gorm.Open(dialector, &gorm.Config{})
```
Checkout [https://gorm.io](https://gorm.io) for details.
+234
View File
@@ -0,0 +1,234 @@
package sqlserver
import (
"reflect"
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
"gorm.io/gorm/clause"
)
func Create(db *gorm.DB) {
if db.Error != nil {
return
}
if db.Statement.Schema != nil && !db.Statement.Unscoped {
for _, c := range db.Statement.Schema.CreateClauses {
db.Statement.AddClause(c)
}
}
hasOutput := false
if db.Statement.SQL.String() == "" {
var (
values = callbacks.ConvertToCreateValues(db.Statement)
c = db.Statement.Clauses["ON CONFLICT"]
onConflict, hasConflict = c.Expression.(clause.OnConflict)
)
if hasConflict {
if len(db.Statement.Schema.PrimaryFields) > 0 {
columnsMap := map[string]bool{}
for _, column := range values.Columns {
columnsMap[column.Name] = true
}
for _, field := range db.Statement.Schema.PrimaryFields {
if _, ok := columnsMap[field.DBName]; !ok {
hasConflict = false
}
}
} else {
hasConflict = false
}
}
if hasConflict {
hasOutput = MergeCreate(db, onConflict, values)
} else {
setIdentityInsert := false
if db.Statement.Schema != nil {
if field := db.Statement.Schema.PrioritizedPrimaryField; field != nil && field.AutoIncrement {
switch db.Statement.ReflectValue.Kind() {
case reflect.Struct:
_, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue)
setIdentityInsert = !isZero
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
obj := db.Statement.ReflectValue.Index(i)
if reflect.Indirect(obj).Kind() == reflect.Struct {
_, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue.Index(i))
setIdentityInsert = !isZero
}
break
}
}
if setIdentityInsert {
db.Statement.WriteString("SET IDENTITY_INSERT ")
db.Statement.WriteQuoted(db.Statement.Table)
db.Statement.WriteString(" ON;")
}
}
}
db.Statement.AddClauseIfNotExists(clause.Insert{})
db.Statement.Build("INSERT")
db.Statement.WriteByte(' ')
db.Statement.AddClause(values)
if values, ok := db.Statement.Clauses["VALUES"].Expression.(clause.Values); ok {
if len(values.Columns) > 0 {
db.Statement.WriteByte('(')
for idx, column := range values.Columns {
if idx > 0 {
db.Statement.WriteByte(',')
}
db.Statement.WriteQuoted(column)
}
db.Statement.WriteByte(')')
hasOutput = outputInserted(db)
db.Statement.WriteString(" VALUES ")
for idx, value := range values.Values {
if idx > 0 {
db.Statement.WriteByte(',')
}
db.Statement.WriteByte('(')
db.Statement.AddVar(db.Statement, value...)
db.Statement.WriteByte(')')
}
db.Statement.WriteString(";")
} else {
db.Statement.WriteString("DEFAULT VALUES;")
}
}
if setIdentityInsert {
db.Statement.WriteString("SET IDENTITY_INSERT ")
db.Statement.WriteQuoted(db.Statement.Table)
db.Statement.WriteString(" OFF;")
}
}
}
if !db.DryRun && db.Error == nil {
if db.Statement.Schema != nil && hasOutput {
rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
if db.AddError(err) == nil {
defer rows.Close()
gorm.Scan(rows, db, gorm.ScanUpdate|gorm.ScanOnConflictDoNothing)
if db.Statement.Result != nil {
db.Statement.Result.RowsAffected = db.RowsAffected
}
}
} else {
result, err := db.Statement.ConnPool.ExecContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
if db.AddError(err) == nil {
db.RowsAffected, _ = result.RowsAffected()
if db.Statement.Result != nil {
db.Statement.Result.Result = result
db.Statement.Result.RowsAffected = db.RowsAffected
}
}
}
}
}
func MergeCreate(db *gorm.DB, onConflict clause.OnConflict, values clause.Values) bool {
db.Statement.WriteString("MERGE INTO ")
db.Statement.WriteQuoted(db.Statement.Table)
db.Statement.WriteString(" USING (VALUES")
for idx, value := range values.Values {
if idx > 0 {
db.Statement.WriteByte(',')
}
db.Statement.WriteByte('(')
db.Statement.AddVar(db.Statement, value...)
db.Statement.WriteByte(')')
}
db.Statement.WriteString(") AS excluded (")
for idx, column := range values.Columns {
if idx > 0 {
db.Statement.WriteByte(',')
}
db.Statement.WriteQuoted(column.Name)
}
db.Statement.WriteString(") ON ")
var where clause.Where
for _, field := range db.Statement.Schema.PrimaryFields {
where.Exprs = append(where.Exprs, clause.Eq{
Column: clause.Column{Table: db.Statement.Table, Name: field.DBName},
Value: clause.Column{Table: "excluded", Name: field.DBName},
})
}
where.Build(db.Statement)
if len(onConflict.DoUpdates) > 0 {
db.Statement.WriteString(" WHEN MATCHED THEN UPDATE SET ")
onConflict.DoUpdates.Build(db.Statement)
}
db.Statement.WriteString(" WHEN NOT MATCHED THEN INSERT (")
written := false
for _, column := range values.Columns {
if db.Statement.Schema.PrioritizedPrimaryField == nil || !db.Statement.Schema.PrioritizedPrimaryField.AutoIncrement || db.Statement.Schema.PrioritizedPrimaryField.DBName != column.Name {
if written {
db.Statement.WriteByte(',')
}
written = true
db.Statement.WriteQuoted(column.Name)
}
}
db.Statement.WriteString(") VALUES (")
written = false
for _, column := range values.Columns {
if db.Statement.Schema.PrioritizedPrimaryField == nil || !db.Statement.Schema.PrioritizedPrimaryField.AutoIncrement || db.Statement.Schema.PrioritizedPrimaryField.DBName != column.Name {
if written {
db.Statement.WriteByte(',')
}
written = true
db.Statement.WriteQuoted(clause.Column{
Table: "excluded",
Name: column.Name,
})
}
}
db.Statement.WriteString(")")
hasOutput := outputInserted(db)
db.Statement.WriteString(";")
return hasOutput
}
func outputInserted(db *gorm.DB) (hasOutput bool) {
if db.Statement.Schema != nil && len(db.Statement.Schema.FieldsWithDefaultDBValue) > 0 {
for _, field := range db.Statement.Schema.FieldsWithDefaultDBValue {
if hasOutput {
db.Statement.WriteString(",")
}
if field.Readable {
if !hasOutput {
db.Statement.WriteString(" OUTPUT INSERTED.")
hasOutput = true
} else {
db.Statement.WriteString(" INSERTED.")
}
db.Statement.AddVar(db.Statement, clause.Column{Name: field.DBName})
}
}
}
return hasOutput
}
+31
View File
@@ -0,0 +1,31 @@
package sqlserver
import (
"github.com/microsoft/go-mssqldb"
"gorm.io/gorm"
)
// The error codes to map mssql errors to gorm errors, here is a reference about error codes for mssql https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16
var errCodes = map[int32]error{
2627: gorm.ErrDuplicatedKey,
2601: gorm.ErrDuplicatedKey,
547: gorm.ErrForeignKeyViolated,
}
type ErrMessage struct {
Number int32 `json:"Number"`
Message string `json:"Message"`
}
// Translate it will translate the error to native gorm errors.
func (dialector Dialector) Translate(err error) error {
if mssqlErr, ok := err.(mssql.Error); ok {
if translatedErr, found := errCodes[mssqlErr.Number]; found {
return translatedErr
}
return err
}
return err
}
+601
View File
@@ -0,0 +1,601 @@
package sqlserver
import (
"database/sql"
"fmt"
"regexp"
"strings"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
"gorm.io/gorm/migrator"
"gorm.io/gorm/schema"
)
const indexSQL = `
SELECT
col.name AS column_name,
i.name AS index_name,
i.is_unique,
i.is_primary_key
FROM
sys.indexes i
LEFT JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
LEFT JOIN sys.all_columns col ON col.column_id = ic.column_id AND col.object_id = ic.object_id
WHERE
i.name IS NOT NULL
AND i.is_unique_constraint = 0
AND i.object_id = OBJECT_ID(?)
`
type Migrator struct {
migrator.Migrator
}
func (m Migrator) GetTables() (tableList []string, err error) {
return tableList, m.DB.Raw("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG = ?", m.CurrentDatabase()).Scan(&tableList).Error
}
func (m Migrator) CreateTable(values ...interface{}) (err error) {
if err = m.Migrator.CreateTable(values...); err != nil {
return
}
for _, value := range m.ReorderModels(values, false) {
if err = m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
if stmt.Schema == nil {
return
}
for _, fieldName := range stmt.Schema.DBNames {
field := stmt.Schema.FieldsByDBName[fieldName]
if _, ok := field.TagSettings["COMMENT"]; !ok {
continue
}
if err = m.setColumnComment(stmt, field, true); err != nil {
return
}
}
return
}); err != nil {
return
}
}
return
}
func (m Migrator) setColumnComment(stmt *gorm.Statement, field *schema.Field, add bool) error {
schemaName := m.getTableSchemaName(stmt.Schema)
commentExpr := gorm.Expr(strings.ReplaceAll(field.Comment, "'", "''"))
// add field comment
if add {
return m.DB.Exec(
"EXEC sp_addextendedproperty 'MS_Description', N'?', 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?",
commentExpr, schemaName, stmt.Table, field.DBName,
).Error
}
// update field comment
return m.DB.Exec(
"EXEC sp_updateextendedproperty 'MS_Description', N'?', 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?",
commentExpr, schemaName, stmt.Table, field.DBName,
).Error
}
func (m Migrator) getTableSchemaName(schema *schema.Schema) string {
// return the schema name if it is explicitly provided in the table name
// otherwise return default schema name
schemaName := getTableSchemaName(schema)
if schemaName == "" {
schemaName = m.DefaultSchema()
}
return schemaName
}
func getTableSchemaName(schema *schema.Schema) string {
// return the schema name if it is explicitly provided in the table name
// otherwise return a sql wildcard -> use any table_schema
if schema == nil || !strings.Contains(schema.Table, ".") {
return ""
}
_, schemaName, _ := splitFullQualifiedName(schema.Table)
return schemaName
}
func splitFullQualifiedName(name string) (string, string, string) {
nameParts := strings.Split(name, ".")
if len(nameParts) == 1 { // [table_name]
return "", "", nameParts[0]
} else if len(nameParts) == 2 { // [table_schema].[table_name]
return "", nameParts[0], nameParts[1]
} else if len(nameParts) == 3 { // [table_catalog].[table_schema].[table_name]
return nameParts[0], nameParts[1], nameParts[2]
}
return "", "", ""
}
func getFullQualifiedTableName(stmt *gorm.Statement) string {
fullQualifiedTableName := stmt.Table
if schemaName := getTableSchemaName(stmt.Schema); schemaName != "" {
fullQualifiedTableName = schemaName + "." + fullQualifiedTableName
}
return fullQualifiedTableName
}
func (m Migrator) HasTable(value interface{}) bool {
var count int
_ = m.RunWithValue(value, func(stmt *gorm.Statement) error {
schemaName := getTableSchemaName(stmt.Schema)
if schemaName == "" {
schemaName = "%"
}
return m.DB.Raw(
"SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ? AND TABLE_CATALOG = ? and TABLE_SCHEMA like ? AND TABLE_TYPE = ?",
stmt.Table, m.CurrentDatabase(), schemaName, "BASE TABLE",
).Row().Scan(&count)
})
return count > 0
}
func (m Migrator) DropTable(values ...interface{}) error {
values = m.ReorderModels(values, false)
for i := len(values) - 1; i >= 0; i-- {
tx := m.DB.Session(&gorm.Session{})
if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error {
type constraint struct {
Name string
Parent string
}
var constraints []constraint
err := tx.Raw("SELECT name, OBJECT_NAME(parent_object_id) as parent FROM sys.foreign_keys WHERE referenced_object_id = object_id(?)", getFullQualifiedTableName(stmt)).Scan(&constraints).Error
for _, c := range constraints {
if err == nil {
err = tx.Exec("ALTER TABLE ? DROP CONSTRAINT ?;", gorm.Expr(c.Parent), gorm.Expr(c.Name)).Error
}
}
if err == nil {
err = tx.Exec("DROP TABLE IF EXISTS ?", clause.Table{Name: stmt.Table}).Error
}
return err
}); err != nil {
return err
}
}
return nil
}
func (m Migrator) RenameTable(oldName, newName interface{}) error {
var oldTable, newTable string
if v, ok := oldName.(string); ok {
oldTable = v
} else {
stmt := &gorm.Statement{DB: m.DB}
if err := stmt.Parse(oldName); err == nil {
oldTable = stmt.Table
} else {
return err
}
}
if v, ok := newName.(string); ok {
newTable = v
} else {
stmt := &gorm.Statement{DB: m.DB}
if err := stmt.Parse(newName); err == nil {
newTable = stmt.Table
} else {
return err
}
}
return m.DB.Exec(
"sp_rename @objname = ?, @newname = ?;",
clause.Table{Name: oldTable}, clause.Table{Name: newTable},
).Error
}
func (m Migrator) AddColumn(value interface{}, name string) error {
if err := m.Migrator.AddColumn(value, name); err != nil {
return err
}
return m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
if stmt.Schema != nil {
if field := stmt.Schema.LookUpField(name); field != nil {
if _, ok := field.TagSettings["COMMENT"]; !ok {
return
}
if err = m.setColumnComment(stmt, field, true); err != nil {
return
}
}
}
return
})
}
func (m Migrator) HasColumn(value interface{}, field string) bool {
var count int64
_ = m.RunWithValue(value, func(stmt *gorm.Statement) error {
currentDatabase := m.DB.Migrator().CurrentDatabase()
name := field
if stmt.Schema != nil {
if field := stmt.Schema.LookUpField(field); field != nil {
name = field.DBName
}
}
return m.DB.Raw(
"SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?",
currentDatabase, stmt.Table, name,
).Row().Scan(&count)
})
return count > 0
}
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 {
fieldType := clause.Expr{SQL: m.DataTypeOf(field)}
if field.NotNull {
fieldType.SQL += " NOT NULL"
} else {
fieldType.SQL += " NULL"
}
return m.DB.Exec(
"ALTER TABLE ? ALTER COLUMN ? ?",
clause.Table{Name: getFullQualifiedTableName(stmt)}, clause.Column{Name: field.DBName}, fieldType,
).Error
}
}
return fmt.Errorf("failed to look up field with name: %s", field)
})
}
func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
if stmt.Schema != nil {
if field := stmt.Schema.LookUpField(oldName); field != nil {
oldName = field.DBName
}
if field := stmt.Schema.LookUpField(newName); field != nil {
newName = field.DBName
}
}
return m.DB.Exec(
"sp_rename @objname = ?, @newname = ?, @objtype = 'COLUMN';",
fmt.Sprintf("%s.%s", stmt.Table, oldName), clause.Column{Name: newName},
).Error
})
}
func (m Migrator) GetColumnComment(stmt *gorm.Statement, fieldDBName string) (comment sql.NullString) {
queryTx := m.DB.Session(&gorm.Session{Logger: m.DB.Logger.LogMode(logger.Warn)})
if m.DB.DryRun {
queryTx.DryRun = false
}
queryTx.Raw("SELECT value FROM [?].sys.fn_listextendedproperty('MS_Description', 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?)",
gorm.Expr(m.CurrentDatabase()), m.getTableSchemaName(stmt.Schema), stmt.Table, fieldDBName).Scan(&comment)
return
}
func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
if err := m.Migrator.MigrateColumn(value, field, columnType); err != nil {
return err
}
return m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
comment := m.GetColumnComment(stmt, field.DBName)
if field.Comment != comment.String {
if comment.Valid {
err = m.setColumnComment(stmt, field, false)
} else {
err = m.setColumnComment(stmt, field, true)
}
}
return
})
}
var defaultValueTrimRegexp = regexp.MustCompile("^\\('?([^']*)'?\\)$")
// ColumnTypes return columnTypes []gorm.ColumnType and execErr error
func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
columnTypes := make([]gorm.ColumnType, 0)
execErr := m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
rows, err := m.DB.Session(&gorm.Session{}).Table(getFullQualifiedTableName(stmt)).Limit(1).Rows()
if err != nil {
return err
}
rawColumnTypes, _ := rows.ColumnTypes()
_ = rows.Close()
{
_, schemaName, tableName := splitFullQualifiedName(stmt.Table)
query := strings.TrimSpace(`
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, c.IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, AUTO_INCREMENT = c2.is_identity
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN sys.tables t ON c.TABLE_NAME = t.[name]
LEFT JOIN sys.columns c2 ON t.object_id = c2.object_id AND c2.[name] = c.COLUMN_NAME
WHERE TABLE_CATALOG = ? AND TABLE_NAME = ?`)
queryParameters := []interface{}{m.CurrentDatabase(), tableName}
if schemaName != "" {
query += " AND TABLE_SCHEMA = ?"
queryParameters = append(queryParameters, schemaName)
}
var (
columnTypeSQL = query
columns, rowErr = m.DB.Raw(columnTypeSQL, queryParameters...).Rows()
)
if rowErr != nil {
return rowErr
}
for columns.Next() {
var (
column = migrator.ColumnType{
PrimaryKeyValue: sql.NullBool{Valid: true},
UniqueValue: sql.NullBool{Valid: true},
}
datetimePrecision sql.NullInt64
radixValue sql.NullInt64
nullableValue sql.NullString
autoIncrementValue sql.NullBool
values = []interface{}{
&column.NameValue, &column.ColumnTypeValue, &column.DefaultValueValue, &nullableValue, &column.LengthValue, &column.DecimalSizeValue, &radixValue, &column.ScaleValue, &datetimePrecision, &autoIncrementValue,
}
)
if scanErr := columns.Scan(values...); scanErr != nil {
return scanErr
}
if nullableValue.Valid {
column.NullableValue = sql.NullBool{Bool: strings.EqualFold(nullableValue.String, "YES"), Valid: true}
}
if datetimePrecision.Valid {
column.DecimalSizeValue = datetimePrecision
}
if autoIncrementValue.Valid && autoIncrementValue.Bool {
column.AutoIncrementValue = autoIncrementValue
}
if column.DefaultValueValue.Valid {
matches := defaultValueTrimRegexp.FindStringSubmatch(column.DefaultValueValue.String)
for len(matches) > 1 {
column.DefaultValueValue.String = matches[1]
matches = defaultValueTrimRegexp.FindStringSubmatch(column.DefaultValueValue.String)
}
}
for _, c := range rawColumnTypes {
if c.Name() == column.NameValue.String {
column.SQLColumnType = c
break
}
}
columnTypes = append(columnTypes, column)
}
_ = columns.Close()
}
{
_, schemaName, tableName := splitFullQualifiedName(stmt.Table)
query := "SELECT c.COLUMN_NAME, t.CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS t JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE c ON c.CONSTRAINT_NAME=t.CONSTRAINT_NAME WHERE t.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE') AND c.TABLE_CATALOG = ? AND c.TABLE_NAME = ?"
queryParameters := []interface{}{m.CurrentDatabase(), tableName}
if schemaName != "" {
query += " AND c.TABLE_SCHEMA = ?"
queryParameters = append(queryParameters, schemaName)
}
columnTypeRows, err := m.DB.Raw(query, queryParameters...).Rows()
if err != nil {
return err
}
for columnTypeRows.Next() {
var name, columnType string
_ = columnTypeRows.Scan(&name, &columnType)
for idx, c := range columnTypes {
mc := c.(migrator.ColumnType)
if mc.NameValue.String == name {
switch columnType {
case "PRIMARY KEY":
mc.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
case "UNIQUE":
mc.UniqueValue = sql.NullBool{Bool: true, Valid: true}
}
columnTypes[idx] = mc
break
}
}
}
_ = columnTypeRows.Close()
}
return
})
return columnTypes, execErr
}
func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
if option.Query == nil {
return gorm.ErrSubQueryRequired
}
sql := new(strings.Builder)
sql.WriteString("CREATE ")
if option.Replace {
sql.WriteString("OR ALTER ")
}
sql.WriteString("VIEW ")
m.QuoteTo(sql, name)
sql.WriteString(" AS ")
m.DB.Statement.AddVar(sql, option.Query)
if option.CheckOption != "" {
sql.WriteString(" ")
sql.WriteString(option.CheckOption)
}
return m.DB.Exec(m.Explain(sql.String(), m.DB.Statement.Vars...)).Error
}
func (m Migrator) CreateIndex(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
var idx *schema.Index
if stmt.Schema != nil {
idx = stmt.Schema.LookIndex(name)
}
if idx == nil {
return fmt.Errorf("failed to create index with name %s", name)
}
opts := m.BuildIndexOptions(idx.Fields, stmt)
values := []interface{}{clause.Column{Name: idx.Name}, m.CurrentTable(stmt), opts}
createIndexSQL := "CREATE "
if idx.Class != "" {
createIndexSQL += idx.Class + " "
}
createIndexSQL += "INDEX ? ON ??"
if idx.Where != "" {
createIndexSQL += " WHERE " + idx.Where
}
if idx.Option != "" {
createIndexSQL += " " + idx.Option
}
return m.DB.Exec(createIndexSQL, values...).Error
})
}
func (m Migrator) HasIndex(value interface{}, name string) bool {
var count int
_ = m.RunWithValue(value, func(stmt *gorm.Statement) error {
if stmt.Schema != nil {
if idx := stmt.Schema.LookIndex(name); idx != nil {
name = idx.Name
}
}
return m.DB.Raw(
"SELECT count(*) FROM sys.indexes WHERE name=? AND object_id=OBJECT_ID(?)",
name, getFullQualifiedTableName(stmt),
).Row().Scan(&count)
})
return count > 0
}
func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
return m.DB.Exec(
"sp_rename @objname = ?, @newname = ?, @objtype = 'INDEX';",
fmt.Sprintf("%s.%s", stmt.Table, oldName), clause.Column{Name: newName},
).Error
})
}
type Index struct {
ColumnName string `gorm:"column:column_name"`
IndexName string `gorm:"column:index_name"`
IsUnique sql.NullBool `gorm:"column:is_unique"`
IsPrimaryKey sql.NullBool `gorm:"column:is_primary_key"`
}
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)
if err := m.DB.Raw(indexSQL, stmt.Table).Scan(&result).Error; err != nil {
return err
}
indexMap := make(map[string]*migrator.Index)
for _, r := range result {
idx, ok := indexMap[r.IndexName]
if !ok {
idx = &migrator.Index{
TableName: stmt.Table,
NameValue: r.IndexName,
ColumnList: nil,
PrimaryKeyValue: r.IsPrimaryKey,
UniqueValue: r.IsUnique,
}
}
idx.ColumnList = append(idx.ColumnList, r.ColumnName)
indexMap[r.IndexName] = idx
}
for _, idx := range indexMap {
indexes = append(indexes, idx)
}
return nil
})
return indexes, err
}
func (m Migrator) HasConstraint(value interface{}, name string) bool {
var count int64
_ = m.RunWithValue(value, func(stmt *gorm.Statement) error {
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
if constraint != nil {
name = constraint.GetName()
}
tableCatalog, tableSchema, tableName := splitFullQualifiedName(table)
if tableCatalog == "" {
tableCatalog = m.CurrentDatabase()
}
if tableSchema == "" {
tableSchema = "%"
}
return m.DB.Raw(
`SELECT count(*) FROM (
SELECT C.name, T.name as table_name FROM sys.check_constraints as C
INNER JOIN sys.tables as T on C.parent_object_id=T.object_id
INNER JOIN INFORMATION_SCHEMA.TABLES as I on I.TABLE_NAME = T.name
WHERE C.name = ? AND I.TABLE_NAME = ? AND I.TABLE_SCHEMA like ? AND I.TABLE_CATALOG = ?
UNION
SELECT FK.name, T.name as table_name FROM sys.foreign_keys as FK
INNER JOIN sys.tables as T on FK.parent_object_id=T.object_id
INNER JOIN INFORMATION_SCHEMA.TABLES as I on I.TABLE_NAME = T.name
WHERE FK.name = ? AND I.TABLE_NAME = ? AND I.TABLE_SCHEMA like ? AND I.TABLE_CATALOG = ?
) as constraints;`,
name, tableName, tableSchema, tableCatalog,
name, tableName, tableSchema, tableCatalog,
).Row().Scan(&count)
})
return count > 0
}
func (m Migrator) CurrentDatabase() (name string) {
_ = m.DB.Raw("SELECT DB_NAME() AS [Current Database]").Row().Scan(&name)
return
}
func (m Migrator) DefaultSchema() (name string) {
_ = m.DB.Raw("SELECT SCHEMA_NAME() AS [Default Schema]").Row().Scan(&name)
return
}
+245
View File
@@ -0,0 +1,245 @@
package sqlserver
import (
"database/sql"
"fmt"
"regexp"
"strconv"
"strings"
_ "github.com/microsoft/go-mssqldb"
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
"gorm.io/gorm/migrator"
"gorm.io/gorm/schema"
)
type Config struct {
DriverName string
DSN string
DefaultStringSize int
Conn gorm.ConnPool
}
type Dialector struct {
*Config
}
func (dialector Dialector) Name() string {
return "sqlserver"
}
func Open(dsn string) gorm.Dialector {
return &Dialector{Config: &Config{DSN: dsn}}
}
func New(config Config) gorm.Dialector {
return &Dialector{Config: &config}
}
func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
// register callbacks
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT"},
QueryClauses: []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"},
UpdateClauses: []string{"UPDATE", "SET", "RETURNING", "FROM", "WHERE"},
DeleteClauses: []string{"DELETE", "FROM", "RETURNING", "WHERE"},
})
db.Callback().Create().Replace("gorm:create", Create)
db.Callback().Update().Replace("gorm:update", Update)
if dialector.DriverName == "" {
dialector.DriverName = "sqlserver"
}
if dialector.Conn != nil {
db.ConnPool = dialector.Conn
} else {
db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
if err != nil {
return err
}
}
for k, v := range dialector.ClauseBuilders() {
db.ClauseBuilders[k] = v
}
return
}
func (dialector Dialector) ClauseBuilders() map[string]clause.ClauseBuilder {
return map[string]clause.ClauseBuilder{
"LIMIT": func(c clause.Clause, builder clause.Builder) {
if limit, ok := c.Expression.(clause.Limit); ok {
if stmt, ok := builder.(*gorm.Statement); ok {
if _, ok := stmt.Clauses["ORDER BY"]; !ok {
if stmt.Schema != nil && stmt.Schema.PrioritizedPrimaryField != nil {
builder.WriteString("ORDER BY ")
builder.WriteQuoted(stmt.Schema.PrioritizedPrimaryField.DBName)
builder.WriteByte(' ')
} else {
builder.WriteString("ORDER BY (SELECT NULL) ")
}
}
}
if limit.Offset > 0 {
builder.WriteString("OFFSET ")
builder.WriteString(strconv.Itoa(limit.Offset))
builder.WriteString(" ROWS")
}
if limit.Limit != nil && *limit.Limit >= 0 {
if limit.Offset == 0 {
builder.WriteString("OFFSET 0 ROW")
}
builder.WriteString(" FETCH NEXT ")
builder.WriteString(strconv.Itoa(*limit.Limit))
builder.WriteString(" ROWS ONLY")
}
}
},
"RETURNING": func(c clause.Clause, builder clause.Builder) {
if returning, ok := c.Expression.(clause.Returning); ok {
if stmt, ok := builder.(*gorm.Statement); ok {
var outputTable string
if _, ok := stmt.Clauses["UPDATE"]; ok {
outputTable = "INSERTED"
} else if _, ok := stmt.Clauses["DELETE"]; ok {
outputTable = "DELETED"
}
if outputTable != "" {
stmt.WriteString("OUTPUT ")
if len(returning.Columns) > 0 {
columns := []clause.Column{}
for _, column := range returning.Columns {
column.Table = outputTable
columns = append(columns, column)
}
returning.Columns = columns
returning.Build(stmt)
} else {
stmt.WriteString(outputTable + ".*")
}
}
}
}
},
}
}
func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression {
return clause.Expr{SQL: "NULL"}
}
func (dialector Dialector) Migrator(db *gorm.DB) gorm.Migrator {
return Migrator{migrator.Migrator{Config: migrator.Config{
DB: db,
Dialector: dialector,
CreateIndexAfterCreateTable: true,
}}}
}
func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
writer.WriteString("@p")
writer.WriteString(strconv.Itoa(len(stmt.Vars)))
}
func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
writer.WriteByte('"')
if strings.Contains(str, ".") {
for idx, str := range strings.Split(str, ".") {
if idx > 0 {
writer.WriteString(`."`)
}
writer.WriteString(str)
writer.WriteByte('"')
}
} else {
writer.WriteString(str)
writer.WriteByte('"')
}
}
var numericPlaceholder = regexp.MustCompile("@p(\\d+)")
func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
for idx, v := range vars {
if b, ok := v.(bool); ok {
if b {
vars[idx] = 1
} else {
vars[idx] = 0
}
}
}
return logger.ExplainSQL(sql, numericPlaceholder, `'`, vars...)
}
func (dialector Dialector) DataTypeOf(field *schema.Field) string {
switch field.DataType {
case schema.Bool:
return "bit"
case schema.Int, schema.Uint:
var sqlType string
switch {
case field.Size < 16:
sqlType = "smallint"
case field.Size < 31:
sqlType = "int"
default:
sqlType = "bigint"
}
if field.AutoIncrement {
return sqlType + " IDENTITY(1,1)"
}
return sqlType
case schema.Float:
if field.Precision > 0 {
if field.Scale > 0 {
return fmt.Sprintf("decimal(%d, %d)", field.Precision, field.Scale)
}
return fmt.Sprintf("decimal(%d)", field.Precision)
}
return "float"
case schema.String:
size := field.Size
hasIndex := field.TagSettings["INDEX"] != "" || field.TagSettings["UNIQUE"] != ""
if (field.PrimaryKey || hasIndex) && size == 0 {
if dialector.DefaultStringSize > 0 {
size = dialector.DefaultStringSize
} else {
size = 256
}
}
if size > 0 && size <= 4000 {
return fmt.Sprintf("nvarchar(%d)", size)
}
return "nvarchar(MAX)"
case schema.Time:
if field.Precision > 0 {
return fmt.Sprintf("datetimeoffset(%d)", field.Precision)
}
return "datetimeoffset"
case schema.Bytes:
return "varbinary(MAX)"
}
return string(field.DataType)
}
func (dialectopr Dialector) SavePoint(tx *gorm.DB, name string) error {
tx.Exec("SAVE TRANSACTION " + name)
return nil
}
func (dialectopr Dialector) RollbackTo(tx *gorm.DB, name string) error {
tx.Exec("ROLLBACK TRANSACTION " + name)
return nil
}
+18
View File
@@ -0,0 +1,18 @@
package sqlserver
import (
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
)
var updateFunc = callbacks.Update(&callbacks.Config{
UpdateClauses: []string{"UPDATE", "SET", "RETURNING", "FROM", "WHERE"},
})
func Update(db *gorm.DB) {
if db.Statement.Schema != nil && db.Statement.Schema.PrioritizedPrimaryField != nil && db.Statement.Schema.PrioritizedPrimaryField.AutoIncrement {
db.Statement.Omits = append(db.Statement.Omits, db.Statement.Schema.PrioritizedPrimaryField.DBName)
}
updateFunc(db)
}