Skip to content

Commit 0bb7feb

Browse files
authored
Merge branch 'go-gorm:master' into master
2 parents 288cf62 + 6825faf commit 0bb7feb

File tree

3 files changed

+224
-21
lines changed

3 files changed

+224
-21
lines changed

.github/workflows/tests.yml

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,50 @@ jobs:
1111
run-tests:
1212
strategy:
1313
matrix:
14-
go: ['1.21', '1.20', '1.19']
15-
platform: [ubuntu-latest]
14+
go: [ '1.21', '1.20', '1.19' ]
15+
platform: [ ubuntu-latest ]
1616
runs-on: ubuntu-latest
17+
18+
env:
19+
MSSQL_DB: gorm
20+
MSSQL_USER: gorm
21+
MSSQL_PASSWORD: LoremIpsum86
22+
1723
services:
1824
mssql:
19-
image: mcmoe/mssqldocker:latest
25+
image: mcr.microsoft.com/mssql/server:2022-latest
2026
env:
2127
ACCEPT_EULA: Y
22-
SA_PASSWORD: LoremIpsum86
23-
MSSQL_DB: gorm
24-
MSSQL_USER: gorm
25-
MSSQL_PASSWORD: LoremIpsum86
28+
MSSQL_SA_PASSWORD: ${{ env.MSSQL_PASSWORD }}
29+
MSSQL_PID: Developer
2630
ports:
2731
- 9930:1433
32+
options: >-
33+
--health-cmd="/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P ${MSSQL_SA_PASSWORD} -N -C -l 30 -Q \"SELECT 1\" || exit 1"
34+
--health-start-period 10s
35+
--health-interval 5s
36+
--health-timeout 10s
37+
--health-retries 10
2838
2939
steps:
30-
- name: Set up Go 1.x
31-
uses: actions/setup-go@v4
32-
with:
33-
go-version: ${{ matrix.go }}
40+
- name: Set up Go 1.x
41+
uses: actions/setup-go@v5
42+
with:
43+
go-version: ${{ matrix.go }}
44+
cache: false
45+
46+
- name: Check out code into the Go module directory
47+
uses: actions/checkout@v4
3448

35-
- name: Check out code into the Go module directory
36-
uses: actions/checkout@v4
49+
- name: Create gorm database and user in SQL Server
50+
run: |
51+
docker exec $(docker ps --filter "name=sqlserver" --format "{{.Names}}") \
52+
/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P ${MSSQL_PASSWORD} -N -C -l 30 \
53+
-Q "CREATE DATABASE ${MSSQL_DB}; CREATE LOGIN ${MSSQL_USER} WITH PASSWORD='${MSSQL_PASSWORD}'; CREATE USER ${MSSQL_USER} FOR LOGIN ${MSSQL_USER}; ALTER SERVER ROLE sysadmin ADD MEMBER [${MSSQL_USER}];"
3754
38-
# Run build of the application
39-
- name: Run build
40-
run: go build .
55+
# Run build of the application
56+
- name: Run build
57+
run: go build .
4158

42-
- name: Run tests
43-
run: go test -race -count=1 -v ./...
59+
- name: Run tests
60+
run: go test -race -count=1 -v ./...

migrator.go

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"gorm.io/gorm"
1010
"gorm.io/gorm/clause"
11+
"gorm.io/gorm/logger"
1112
"gorm.io/gorm/migrator"
1213
"gorm.io/gorm/schema"
1314
)
@@ -36,6 +37,58 @@ func (m Migrator) GetTables() (tableList []string, err error) {
3637
return tableList, m.DB.Raw("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG = ?", m.CurrentDatabase()).Scan(&tableList).Error
3738
}
3839

40+
func (m Migrator) CreateTable(values ...interface{}) (err error) {
41+
if err = m.Migrator.CreateTable(values...); err != nil {
42+
return
43+
}
44+
for _, value := range m.ReorderModels(values, false) {
45+
if err = m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
46+
if stmt.Schema == nil {
47+
return
48+
}
49+
for _, fieldName := range stmt.Schema.DBNames {
50+
field := stmt.Schema.FieldsByDBName[fieldName]
51+
if field.Comment == "" {
52+
continue
53+
}
54+
if err = m.setColumnComment(stmt, field, true); err != nil {
55+
return
56+
}
57+
}
58+
return
59+
}); err != nil {
60+
return
61+
}
62+
}
63+
return
64+
}
65+
66+
func (m Migrator) setColumnComment(stmt *gorm.Statement, field *schema.Field, add bool) error {
67+
schemaName := m.getTableSchemaName(stmt.Schema)
68+
// add field comment
69+
if add {
70+
return m.DB.Exec(
71+
"EXEC sp_addextendedproperty 'MS_Description', ?, 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?",
72+
field.Comment, schemaName, stmt.Table, field.DBName,
73+
).Error
74+
}
75+
// update field comment
76+
return m.DB.Exec(
77+
"EXEC sp_updateextendedproperty 'MS_Description', ?, 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?",
78+
field.Comment, schemaName, stmt.Table, field.DBName,
79+
).Error
80+
}
81+
82+
func (m Migrator) getTableSchemaName(schema *schema.Schema) string {
83+
// return the schema name if it is explicitly provided in the table name
84+
// otherwise return default schema name
85+
schemaName := getTableSchemaName(schema)
86+
if schemaName == "" {
87+
schemaName = m.DefaultSchema()
88+
}
89+
return schemaName
90+
}
91+
3992
func getTableSchemaName(schema *schema.Schema) string {
4093
// return the schema name if it is explicitly provided in the table name
4194
// otherwise return a sql wildcard -> use any table_schema
@@ -141,6 +194,26 @@ func (m Migrator) RenameTable(oldName, newName interface{}) error {
141194
).Error
142195
}
143196

197+
func (m Migrator) AddColumn(value interface{}, name string) error {
198+
if err := m.Migrator.AddColumn(value, name); err != nil {
199+
return err
200+
}
201+
202+
return m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
203+
if stmt.Schema != nil {
204+
if field := stmt.Schema.LookUpField(name); field != nil {
205+
if field.Comment == "" {
206+
return
207+
}
208+
if err = m.setColumnComment(stmt, field, true); err != nil {
209+
return
210+
}
211+
}
212+
}
213+
return
214+
})
215+
}
216+
144217
func (m Migrator) HasColumn(value interface{}, field string) bool {
145218
var count int64
146219
m.RunWithValue(value, func(stmt *gorm.Statement) error {
@@ -200,6 +273,38 @@ func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error
200273
})
201274
}
202275

276+
func (m Migrator) GetColumnComment(stmt *gorm.Statement, fieldDBName string) (description string) {
277+
queryTx := m.DB.Session(&gorm.Session{Logger: m.DB.Logger.LogMode(logger.Warn)})
278+
if m.DB.DryRun {
279+
queryTx.DryRun = false
280+
}
281+
var comment sql.NullString
282+
queryTx.Raw("SELECT value FROM ?.sys.fn_listextendedproperty('MS_Description', 'SCHEMA', ?, 'TABLE', ?, 'COLUMN', ?)",
283+
gorm.Expr(m.CurrentDatabase()), m.getTableSchemaName(stmt.Schema), stmt.Table, fieldDBName).Scan(&comment)
284+
if comment.Valid {
285+
description = comment.String
286+
}
287+
return
288+
}
289+
290+
func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
291+
if err := m.Migrator.MigrateColumn(value, field, columnType); err != nil {
292+
return err
293+
}
294+
295+
return m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
296+
description := m.GetColumnComment(stmt, field.DBName)
297+
if field.Comment != description {
298+
if description == "" {
299+
err = m.setColumnComment(stmt, field, true)
300+
} else {
301+
err = m.setColumnComment(stmt, field, false)
302+
}
303+
}
304+
return
305+
})
306+
}
307+
203308
var defaultValueTrimRegexp = regexp.MustCompile("^\\('?([^']*)'?\\)$")
204309

205310
// ColumnTypes return columnTypes []gorm.ColumnType and execErr error
@@ -215,9 +320,20 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
215320
rows.Close()
216321

217322
{
323+
_, schemaName, tableName := splitFullQualifiedName(stmt.Table)
324+
325+
query := "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = ? AND TABLE_NAME = ?"
326+
327+
queryParameters := []interface{}{m.CurrentDatabase(), tableName}
328+
329+
if schemaName != "" {
330+
query += " AND TABLE_SCHEMA = ?"
331+
queryParameters = append(queryParameters, schemaName)
332+
}
333+
218334
var (
219-
columnTypeSQL = "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = ? AND TABLE_NAME = ?"
220-
columns, rowErr = m.DB.Raw(columnTypeSQL, m.CurrentDatabase(), stmt.Table).Rows()
335+
columnTypeSQL = query
336+
columns, rowErr = m.DB.Raw(columnTypeSQL, queryParameters...).Rows()
221337
)
222338

223339
if rowErr != nil {
@@ -272,7 +388,17 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
272388
}
273389

274390
{
275-
columnTypeRows, err := m.DB.Raw("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 = ?", m.CurrentDatabase(), stmt.Table).Rows()
391+
_, schemaName, tableName := splitFullQualifiedName(stmt.Table)
392+
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 = ?"
393+
394+
queryParameters := []interface{}{m.CurrentDatabase(), tableName}
395+
396+
if schemaName != "" {
397+
query += " AND c.TABLE_SCHEMA = ?"
398+
queryParameters = append(queryParameters, schemaName)
399+
}
400+
401+
columnTypeRows, err := m.DB.Raw(query, queryParameters...).Rows()
276402
if err != nil {
277403
return err
278404
}

migrator_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,63 @@ func testGetMigrateColumns(db *gorm.DB, dst interface{}) (columnsWithDefault, co
188188
}
189189
return
190190
}
191+
192+
type TestTableFieldComment struct {
193+
ID string `gorm:"column:id;primaryKey"`
194+
Name string `gorm:"column:name;comment:姓名"`
195+
Age uint `gorm:"column:age;comment:年龄"`
196+
}
197+
198+
func (*TestTableFieldComment) TableName() string { return "test_table_field_comment" }
199+
200+
type TestTableFieldCommentUpdate struct {
201+
ID string `gorm:"column:id;primaryKey"`
202+
Name string `gorm:"column:name;comment:姓名"`
203+
Age uint `gorm:"column:age;comment:周岁"`
204+
Birthday *time.Time `gorm:"column:birthday;comment:生日"`
205+
}
206+
207+
func (*TestTableFieldCommentUpdate) TableName() string { return "test_table_field_comment" }
208+
209+
func TestMigrator_MigrateColumnComment(t *testing.T) {
210+
db, err := gorm.Open(sqlserver.Open(sqlserverDSN))
211+
if err != nil {
212+
t.Error(err)
213+
}
214+
migrator := db.Debug().Migrator()
215+
216+
tableModel := new(TestTableFieldComment)
217+
defer func() {
218+
if err = migrator.DropTable(tableModel); err != nil {
219+
t.Errorf("couldn't drop table %q, got error: %v", tableModel.TableName(), err)
220+
}
221+
}()
222+
223+
if err = migrator.AutoMigrate(tableModel); err != nil {
224+
t.Fatal(err)
225+
}
226+
tableModelUpdate := new(TestTableFieldCommentUpdate)
227+
if err = migrator.AutoMigrate(tableModelUpdate); err != nil {
228+
t.Error(err)
229+
}
230+
231+
if m, ok := migrator.(sqlserver.Migrator); ok {
232+
stmt := db.Model(tableModelUpdate).Find(nil).Statement
233+
if stmt == nil || stmt.Schema == nil {
234+
t.Fatal("expected Statement.Schema, got nil")
235+
}
236+
237+
wantComments := []string{"", "姓名", "周岁", "生日"}
238+
gotComments := make([]string, len(stmt.Schema.DBNames))
239+
240+
for i, fieldDBName := range stmt.Schema.DBNames {
241+
comment := m.GetColumnComment(stmt, fieldDBName)
242+
gotComments[i] = comment
243+
}
244+
245+
if !reflect.DeepEqual(wantComments, gotComments) {
246+
t.Fatalf("expected comments %#v, got %#v", wantComments, gotComments)
247+
}
248+
t.Logf("got comments: %#v", gotComments)
249+
}
250+
}

0 commit comments

Comments
 (0)