Merge branch 'master' into lunny/test_max_id
This commit is contained in:
commit
2db82f95e9
|
@ -13,11 +13,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: setup go
|
- name: setup go
|
||||||
uses: https://github.com/actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '>=1.20.1'
|
go-version: '>=1.20.1'
|
||||||
- name: Use Go Action
|
- name: Use Go Action
|
||||||
id: use-go-action
|
id: use-go-action
|
||||||
uses: actions/release-action@main
|
uses: https://gitea.com/actions/release-action@main
|
||||||
with:
|
with:
|
||||||
api_key: '${{secrets.RELEASE_TOKEN}}'
|
api_key: '${{secrets.RELEASE_TOKEN}}'
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test cockroach
|
- name: test cockroach
|
||||||
env:
|
env:
|
||||||
TEST_COCKROACH_HOST: "cockroach:26257"
|
TEST_COCKROACH_HOST: "cockroach:26257"
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test mariadb
|
- name: test mariadb
|
||||||
env:
|
env:
|
||||||
TEST_MYSQL_HOST: mariadb
|
TEST_MYSQL_HOST: mariadb
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test mssql
|
- name: test mssql
|
||||||
env:
|
env:
|
||||||
TEST_MSSQL_HOST: mssql
|
TEST_MSSQL_HOST: mssql
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test mysql utf8mb4
|
- name: test mysql utf8mb4
|
||||||
env:
|
env:
|
||||||
TEST_MYSQL_HOST: mysql
|
TEST_MYSQL_HOST: mysql
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test mysql8
|
- name: test mysql8
|
||||||
env:
|
env:
|
||||||
TEST_MYSQL_HOST: mysql8
|
TEST_MYSQL_HOST: mysql8
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test postgres
|
- name: test postgres
|
||||||
env:
|
env:
|
||||||
TEST_PGSQL_HOST: pgsql
|
TEST_PGSQL_HOST: pgsql
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: vet
|
- name: vet
|
||||||
run: make vet
|
run: make vet
|
||||||
- name: format check
|
- name: format check
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.20
|
go-version: 1.20
|
||||||
- uses: https://github.com/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: test tidb
|
- name: test tidb
|
||||||
env:
|
env:
|
||||||
TEST_TIDB_HOST: "tidb:4000"
|
TEST_TIDB_HOST: "tidb:4000"
|
||||||
|
|
|
@ -16,11 +16,21 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConversionFrom is an inteface to allow retrieve data from database
|
||||||
|
type ConversionFrom interface {
|
||||||
|
FromDB([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConversionTo is an interface to allow store data to database
|
||||||
|
type ConversionTo interface {
|
||||||
|
ToDB() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Conversion is an interface. A type implements Conversion will according
|
// Conversion is an interface. A type implements Conversion will according
|
||||||
// the custom method to fill into database and retrieve from database.
|
// the custom method to fill into database and retrieve from database.
|
||||||
type Conversion interface {
|
type Conversion interface {
|
||||||
FromDB([]byte) error
|
ConversionFrom
|
||||||
ToDB() ([]byte, error)
|
ConversionTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNilPtr represents an error
|
// ErrNilPtr represents an error
|
||||||
|
|
|
@ -28,14 +28,19 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
return &dt, nil
|
return &dt, nil
|
||||||
} else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' {
|
} else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' {
|
||||||
|
if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation)
|
dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
dt.IsZero()
|
|
||||||
return &dt, nil
|
return &dt, nil
|
||||||
} else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' {
|
} else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' {
|
||||||
|
if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
dt, err := time.Parse(time.RFC3339, s)
|
dt, err := time.Parse(time.RFC3339, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -43,6 +48,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
return &dt, nil
|
return &dt, nil
|
||||||
} else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' {
|
} else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' {
|
||||||
|
if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
|
||||||
|
strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
dt, err := time.Parse(time.RFC3339Nano, s)
|
dt, err := time.Parse(time.RFC3339Nano, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -50,6 +59,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
return &dt, nil
|
return &dt, nil
|
||||||
} else if len(s) >= 21 && s[19] == '.' {
|
} else if len(s) >= 21 && s[19] == '.' {
|
||||||
|
if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
|
||||||
|
strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
layout := "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20)
|
layout := "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20)
|
||||||
dt, err := time.ParseInLocation(layout, s, originalLocation)
|
dt, err := time.ParseInLocation(layout, s, originalLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,11 +81,11 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
return &dt, nil
|
return &dt, nil
|
||||||
} else if len(s) == 8 && s[2] == ':' && s[5] == ':' {
|
} else if len(s) == 8 && s[2] == ':' && s[5] == ':' {
|
||||||
currentDate := time.Now()
|
|
||||||
dt, err := time.ParseInLocation("15:04:05", s, originalLocation)
|
dt, err := time.ParseInLocation("15:04:05", s, originalLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
currentDate := time.Now()
|
||||||
// add current date for correct time locations
|
// add current date for correct time locations
|
||||||
dt = dt.AddDate(currentDate.Year(), int(currentDate.Month()), currentDate.Day())
|
dt = dt.AddDate(currentDate.Year(), int(currentDate.Month()), currentDate.Day())
|
||||||
dt = dt.In(convertedLocation)
|
dt = dt.In(convertedLocation)
|
||||||
|
@ -82,6 +95,9 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
|
||||||
} else {
|
} else {
|
||||||
i, err := strconv.ParseInt(s, 10, 64)
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if i == 0 {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
tm := time.Unix(i, 0).In(convertedLocation)
|
tm := time.Unix(i, 0).In(convertedLocation)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
}
|
}
|
||||||
|
@ -108,6 +124,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
|
||||||
if !t.Valid {
|
if !t.Valid {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if utils.IsTimeZero(t.Time) {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
z, _ := t.Time.Zone()
|
z, _ := t.Time.Zone()
|
||||||
if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() {
|
if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() {
|
||||||
tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(),
|
tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(),
|
||||||
|
@ -117,6 +136,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
|
||||||
tm := t.Time.In(uiLoc)
|
tm := t.Time.In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
case *time.Time:
|
case *time.Time:
|
||||||
|
if utils.IsTimeZero(*t) {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
z, _ := t.Zone()
|
z, _ := t.Zone()
|
||||||
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
|
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
|
||||||
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
|
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
|
||||||
|
@ -126,6 +148,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
|
||||||
tm := t.In(uiLoc)
|
tm := t.In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
case time.Time:
|
case time.Time:
|
||||||
|
if utils.IsTimeZero(t) {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
z, _ := t.Zone()
|
z, _ := t.Zone()
|
||||||
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
|
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
|
||||||
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
|
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
|
||||||
|
@ -135,12 +160,21 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
|
||||||
tm := t.In(uiLoc)
|
tm := t.In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
case int:
|
case int:
|
||||||
|
if t == 0 {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
tm := time.Unix(int64(t), 0).In(uiLoc)
|
tm := time.Unix(int64(t), 0).In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
case int64:
|
case int64:
|
||||||
|
if t == 0 {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
tm := time.Unix(t, 0).In(uiLoc)
|
tm := time.Unix(t, 0).In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
case *sql.NullInt64:
|
case *sql.NullInt64:
|
||||||
|
if t.Int64 == 0 {
|
||||||
|
return &time.Time{}, nil
|
||||||
|
}
|
||||||
tm := time.Unix(t.Int64, 0).In(uiLoc)
|
tm := time.Unix(t.Int64, 0).In(uiLoc)
|
||||||
return &tm, nil
|
return &tm, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,11 +320,7 @@ func (db *mssql) SQLType(c *schemas.Column) string {
|
||||||
res += "(MAX)"
|
res += "(MAX)"
|
||||||
}
|
}
|
||||||
case schemas.TimeStamp, schemas.DateTime:
|
case schemas.TimeStamp, schemas.DateTime:
|
||||||
if c.Length > 3 {
|
return "DATETIME2"
|
||||||
res = "DATETIME2"
|
|
||||||
} else {
|
|
||||||
return schemas.DateTime
|
|
||||||
}
|
|
||||||
case schemas.TimeStampz:
|
case schemas.TimeStampz:
|
||||||
res = "DATETIMEOFFSET"
|
res = "DATETIMEOFFSET"
|
||||||
c.Length = 7
|
c.Length = 7
|
||||||
|
|
|
@ -121,6 +121,7 @@ type EngineInterface interface {
|
||||||
ShowSQL(show ...bool)
|
ShowSQL(show ...bool)
|
||||||
Sync(...interface{}) error
|
Sync(...interface{}) error
|
||||||
Sync2(...interface{}) error
|
Sync2(...interface{}) error
|
||||||
|
SyncWithOptions(SyncOptions, ...interface{}) (*SyncResult, error)
|
||||||
StoreEngine(storeEngine string) *Session
|
StoreEngine(storeEngine string) *Session
|
||||||
TableInfo(bean interface{}) (*schemas.Table, error)
|
TableInfo(bean interface{}) (*schemas.Table, error)
|
||||||
TableName(interface{}, ...bool) string
|
TableName(interface{}, ...bool) string
|
||||||
|
|
|
@ -644,6 +644,23 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string,
|
||||||
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
||||||
} else if v, ok := arg.(*time.Time); ok && v != nil {
|
} else if v, ok := arg.(*time.Time); ok && v != nil {
|
||||||
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
||||||
|
} else if v, ok := arg.(convert.ConversionTo); ok {
|
||||||
|
r, err := v.ToDB()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
// for nvarchar column on mssql, bytes have to be converted as ucs-2 external of driver
|
||||||
|
// for binary column, a string will be converted as bytes directly. So we have to
|
||||||
|
// convert bytes as string
|
||||||
|
if statement.dialect.URI().DBType == schemas.MSSQL {
|
||||||
|
newArgs = append(newArgs, string(r))
|
||||||
|
} else {
|
||||||
|
newArgs = append(newArgs, r)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newArgs = append(newArgs, nil)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newArgs = append(newArgs, arg)
|
newArgs = append(newArgs, arg)
|
||||||
}
|
}
|
||||||
|
@ -690,11 +707,8 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond {
|
||||||
if col.SQLType.IsNumeric() {
|
if col.SQLType.IsNumeric() {
|
||||||
cond = builder.Eq{colName: 0}
|
cond = builder.Eq{colName: 0}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.
|
|
||||||
if statement.dialect.URI().DBType != schemas.MSSQL {
|
|
||||||
cond = builder.Eq{colName: utils.ZeroTime1}
|
cond = builder.Eq{colName: utils.ZeroTime1}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if col.Nullable {
|
if col.Nullable {
|
||||||
cond = cond.Or(builder.IsNull{colName})
|
cond = cond.Or(builder.IsNull{colName})
|
||||||
|
|
|
@ -471,7 +471,8 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
|
||||||
}
|
}
|
||||||
|
|
||||||
if col.IsDeleted {
|
if col.IsDeleted {
|
||||||
arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, time.Time{})
|
zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, session.engine.DatabaseTZ)
|
||||||
|
arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, zeroTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
32
sync.go
32
sync.go
|
@ -13,6 +13,10 @@ import (
|
||||||
|
|
||||||
type SyncOptions struct {
|
type SyncOptions struct {
|
||||||
WarnIfDatabaseColumnMissed bool
|
WarnIfDatabaseColumnMissed bool
|
||||||
|
// IgnoreConstrains will not add, delete or update unique constrains
|
||||||
|
IgnoreConstrains bool
|
||||||
|
// IgnoreIndices will not add or delete indices
|
||||||
|
IgnoreIndices bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncResult struct{}
|
type SyncResult struct{}
|
||||||
|
@ -49,6 +53,8 @@ func (session *Session) Sync2(beans ...interface{}) error {
|
||||||
func (session *Session) Sync(beans ...interface{}) error {
|
func (session *Session) Sync(beans ...interface{}) error {
|
||||||
_, err := session.SyncWithOptions(SyncOptions{
|
_, err := session.SyncWithOptions(SyncOptions{
|
||||||
WarnIfDatabaseColumnMissed: false,
|
WarnIfDatabaseColumnMissed: false,
|
||||||
|
IgnoreConstrains: false,
|
||||||
|
IgnoreIndices: false,
|
||||||
}, beans...)
|
}, beans...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -103,15 +109,20 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opts.IgnoreConstrains {
|
||||||
err = session.createUniques(bean)
|
err = session.createUniques(bean)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.IgnoreIndices {
|
||||||
err = session.createIndexes(bean)
|
err = session.createIndexes(bean)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,9 +219,12 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indices found in orig table
|
||||||
foundIndexNames := make(map[string]bool)
|
foundIndexNames := make(map[string]bool)
|
||||||
|
// indices to be added
|
||||||
addedNames := make(map[string]*schemas.Index)
|
addedNames := make(map[string]*schemas.Index)
|
||||||
|
|
||||||
|
// drop indices that exist in orig and new table schema but are not equal
|
||||||
for name, index := range table.Indexes {
|
for name, index := range table.Indexes {
|
||||||
var oriIndex *schemas.Index
|
var oriIndex *schemas.Index
|
||||||
for name2, index2 := range oriTable.Indexes {
|
for name2, index2 := range oriTable.Indexes {
|
||||||
|
@ -221,8 +235,7 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oriIndex != nil {
|
if oriIndex != nil && oriIndex.Type != index.Type {
|
||||||
if oriIndex.Type != index.Type {
|
|
||||||
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex)
|
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex)
|
||||||
_, err = session.exec(sql)
|
_, err = session.exec(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -230,15 +243,23 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
||||||
}
|
}
|
||||||
oriIndex = nil
|
oriIndex = nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if oriIndex == nil {
|
if oriIndex == nil {
|
||||||
addedNames[name] = index
|
addedNames[name] = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drop all indices that do not exist in new schema or have changed
|
||||||
for name2, index2 := range oriTable.Indexes {
|
for name2, index2 := range oriTable.Indexes {
|
||||||
if _, ok := foundIndexNames[name2]; !ok {
|
if _, ok := foundIndexNames[name2]; !ok {
|
||||||
|
// ignore based on there type
|
||||||
|
if (index2.Type == schemas.IndexType && opts.IgnoreIndices) ||
|
||||||
|
(index2.Type == schemas.UniqueType && opts.IgnoreConstrains) {
|
||||||
|
// make sure we do not add a index with same name later
|
||||||
|
delete(addedNames, name2)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, index2)
|
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, index2)
|
||||||
_, err = session.exec(sql)
|
_, err = session.exec(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -247,12 +268,13 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add new indices because either they did not exist before or were dropped to update them
|
||||||
for name, index := range addedNames {
|
for name, index := range addedNames {
|
||||||
if index.Type == schemas.UniqueType {
|
if index.Type == schemas.UniqueType && !opts.IgnoreConstrains {
|
||||||
session.statement.RefTable = table
|
session.statement.RefTable = table
|
||||||
session.statement.SetTableName(tbNameWithSchema)
|
session.statement.SetTableName(tbNameWithSchema)
|
||||||
err = session.addUnique(tbNameWithSchema, name)
|
err = session.addUnique(tbNameWithSchema, name)
|
||||||
} else if index.Type == schemas.IndexType {
|
} else if index.Type == schemas.IndexType && !opts.IgnoreIndices {
|
||||||
session.statement.RefTable = table
|
session.statement.RefTable = table
|
||||||
session.statement.SetTableName(tbNameWithSchema)
|
session.statement.SetTableName(tbNameWithSchema)
|
||||||
err = session.addIndex(tbNameWithSchema, name)
|
err = session.addIndex(tbNameWithSchema, name)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"xorm.io/xorm"
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -645,3 +646,101 @@ func TestCollate(t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SyncWithOpts1 struct {
|
||||||
|
Id int64
|
||||||
|
Index int `xorm:"index"`
|
||||||
|
Unique int `xorm:"unique"`
|
||||||
|
Group1 int `xorm:"index(ttt)"`
|
||||||
|
Group2 int `xorm:"index(ttt)"`
|
||||||
|
UniGroup1 int `xorm:"unique(lll)"`
|
||||||
|
UniGroup2 int `xorm:"unique(lll)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SyncWithOpts1) TableName() string {
|
||||||
|
return "sync_with_opts"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncWithOpts2 struct {
|
||||||
|
Id int64
|
||||||
|
Index int `xorm:"index"`
|
||||||
|
Unique int `xorm:""`
|
||||||
|
Group1 int `xorm:"index(ttt)"`
|
||||||
|
Group2 int `xorm:"index(ttt)"`
|
||||||
|
UniGroup1 int `xorm:""`
|
||||||
|
UniGroup2 int `xorm:"unique(lll)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SyncWithOpts2) TableName() string {
|
||||||
|
return "sync_with_opts"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncWithOpts3 struct {
|
||||||
|
Id int64
|
||||||
|
Index int `xorm:""`
|
||||||
|
Unique int `xorm:"unique"`
|
||||||
|
Group1 int `xorm:""`
|
||||||
|
Group2 int `xorm:"index(ttt)"`
|
||||||
|
UniGroup1 int `xorm:"unique(lll)"`
|
||||||
|
UniGroup2 int `xorm:"unique(lll)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SyncWithOpts3) TableName() string {
|
||||||
|
return "sync_with_opts"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncWithOptions(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareEngine())
|
||||||
|
|
||||||
|
// ignore indices and constrains
|
||||||
|
result, err := testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreIndices: true, IgnoreConstrains: true}, &SyncWithOpts1{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Len(t, getIndicesOfBeanFromDB(t, &SyncWithOpts1{}), 0)
|
||||||
|
|
||||||
|
// only ignore indices
|
||||||
|
result, err = testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreConstrains: true}, &SyncWithOpts2{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
indices := getIndicesOfBeanFromDB(t, &SyncWithOpts1{})
|
||||||
|
assert.Len(t, indices, 2)
|
||||||
|
assert.ElementsMatch(t, []string{"ttt", "index"}, getKeysFromMap(indices))
|
||||||
|
|
||||||
|
// only ignore constrains
|
||||||
|
result, err = testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreIndices: true}, &SyncWithOpts3{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
indices = getIndicesOfBeanFromDB(t, &SyncWithOpts1{})
|
||||||
|
assert.Len(t, indices, 4)
|
||||||
|
assert.ElementsMatch(t, []string{"ttt", "index", "unique", "lll"}, getKeysFromMap(indices))
|
||||||
|
|
||||||
|
tableInfoFromStruct, _ := testEngine.TableInfo(&SyncWithOpts1{})
|
||||||
|
assert.ElementsMatch(t, getKeysFromMap(tableInfoFromStruct.Indexes), getKeysFromMap(getIndicesOfBeanFromDB(t, &SyncWithOpts1{})))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndicesOfBeanFromDB(t *testing.T, bean interface{}) map[string]*schemas.Index {
|
||||||
|
dbm, err := testEngine.DBMetas()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tName := testEngine.TableName(bean)
|
||||||
|
var tSchema *schemas.Table
|
||||||
|
for _, t := range dbm {
|
||||||
|
if t.Name == tName {
|
||||||
|
tSchema = t
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !assert.NotNil(t, tSchema) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tSchema.Indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeysFromMap(m map[string]*schemas.Index) []string {
|
||||||
|
var ss []string
|
||||||
|
for k := range m {
|
||||||
|
ss = append(ss, k)
|
||||||
|
}
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
|
@ -1209,3 +1209,80 @@ func TestInsertMultipleMap(t *testing.T) {
|
||||||
Name: "xiaolunwen",
|
Name: "xiaolunwen",
|
||||||
}, res[1])
|
}, res[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInsertNotDeleted(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareEngine())
|
||||||
|
zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, testEngine.GetTZDatabase())
|
||||||
|
type TestInsertNotDeletedStructNotRight struct {
|
||||||
|
ID uint64 `xorm:"'ID' pk autoincr"`
|
||||||
|
DeletedAt time.Time `xorm:"'DELETED_AT' deleted notnull"`
|
||||||
|
}
|
||||||
|
// notnull tag will be ignored
|
||||||
|
err := testEngine.Sync(new(TestInsertNotDeletedStructNotRight))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
type TestInsertNotDeletedStruct struct {
|
||||||
|
ID uint64 `xorm:"'ID' pk autoincr"`
|
||||||
|
DeletedAt time.Time `xorm:"'DELETED_AT' deleted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, testEngine.Sync(new(TestInsertNotDeletedStruct)))
|
||||||
|
|
||||||
|
var v1 TestInsertNotDeletedStructNotRight
|
||||||
|
_, err = testEngine.Insert(&v1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var v2 TestInsertNotDeletedStructNotRight
|
||||||
|
has, err := testEngine.Get(&v2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, has)
|
||||||
|
assert.Equal(t, v2.DeletedAt.In(testEngine.GetTZDatabase()).Format("2006-01-02 15:04:05"), zeroTime.Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
|
var v3 TestInsertNotDeletedStruct
|
||||||
|
_, err = testEngine.Insert(&v3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var v4 TestInsertNotDeletedStruct
|
||||||
|
has, err = testEngine.Get(&v4)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, has)
|
||||||
|
assert.Equal(t, v4.DeletedAt.In(testEngine.GetTZDatabase()).Format("2006-01-02 15:04:05"), zeroTime.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyAutoTimeFields1 struct {
|
||||||
|
Id int64
|
||||||
|
Dt time.Time `xorm:"created DATETIME"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MyAutoTimeFields1) TableName() string {
|
||||||
|
return "my_auto_time_fields"
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyAutoTimeFields2 struct {
|
||||||
|
Id int64
|
||||||
|
Dt time.Time `xorm:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MyAutoTimeFields2) TableName() string {
|
||||||
|
return "my_auto_time_fields"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoTimeFields(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareEngine())
|
||||||
|
|
||||||
|
assertSync(t, new(MyAutoTimeFields1))
|
||||||
|
|
||||||
|
_, err := testEngine.Insert(&MyAutoTimeFields1{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var res []MyAutoTimeFields2
|
||||||
|
assert.NoError(t, testEngine.Find(&res))
|
||||||
|
assert.EqualValues(t, 1, len(res))
|
||||||
|
|
||||||
|
_, err = testEngine.Insert(&MyAutoTimeFields2{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
res = []MyAutoTimeFields2{}
|
||||||
|
assert.NoError(t, testEngine.Find(&res))
|
||||||
|
assert.EqualValues(t, 2, len(res))
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"xorm.io/xorm/convert"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,3 +67,48 @@ func TestExecTime(t *testing.T) {
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), uet.Created.Format("2006-01-02 15:04:05"))
|
assert.EqualValues(t, now.In(testEngine.GetTZLocation()).Format("2006-01-02 15:04:05"), uet.Created.Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConversionData struct {
|
||||||
|
MyData string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ convert.Conversion = new(ConversionData)
|
||||||
|
|
||||||
|
func (c ConversionData) ToDB() ([]byte, error) {
|
||||||
|
return []byte(c.MyData), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConversionData) FromDB(bs []byte) error {
|
||||||
|
if bs != nil {
|
||||||
|
c.MyData = string(bs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecCustomTypes(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareEngine())
|
||||||
|
|
||||||
|
type UserinfoExec struct {
|
||||||
|
Uid int
|
||||||
|
Name string
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, testEngine.Sync2(new(UserinfoExec)))
|
||||||
|
|
||||||
|
res, err := testEngine.Exec("INSERT INTO "+testEngine.TableName("`userinfo_exec`", true)+" (uid, name,data) VALUES (?, ?, ?)",
|
||||||
|
1, "user", ConversionData{"data"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
cnt, err := res.RowsAffected()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, cnt)
|
||||||
|
|
||||||
|
results, err := testEngine.QueryString("select * from " + testEngine.TableName("userinfo_exec", true))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, len(results))
|
||||||
|
id, err := strconv.Atoi(results[0]["uid"])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, id)
|
||||||
|
assert.Equal(t, "user", results[0]["name"])
|
||||||
|
assert.EqualValues(t, "data", results[0]["data"])
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ package tests
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"xorm.io/xorm/convert"
|
||||||
"xorm.io/xorm/internal/utils"
|
"xorm.io/xorm/internal/utils"
|
||||||
"xorm.io/xorm/names"
|
"xorm.io/xorm/names"
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
|
@ -1201,8 +1201,10 @@ func TestTagTime(t *testing.T) {
|
||||||
has, err = testEngine.Table("tag_u_t_c_struct").Cols("created").Get(&tm)
|
has, err = testEngine.Table("tag_u_t_c_struct").Cols("created").Get(&tm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"),
|
|
||||||
strings.ReplaceAll(strings.ReplaceAll(tm, "T", " "), "Z", ""))
|
tmTime, err := convert.String2Time(tm, time.UTC, time.UTC)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"), tmTime.Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTagAutoIncr(t *testing.T) {
|
func TestTagAutoIncr(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue