Skip to content

Commit addd7bf

Browse files
authored
Merge pull request #31 from openark/zero-date
Support zero date and zero in date, via dedicated command line flag
2 parents 06fd4d5 + ad7aeb2 commit addd7bf

File tree

10 files changed

+99
-17
lines changed

10 files changed

+99
-17
lines changed

doc/command-line-flags.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ A more in-depth discussion of various `gh-ost` command line flags: implementatio
66

77
Add this flag when executing on Aliyun RDS.
88

9+
### allow-zero-in-date
10+
11+
Allows the user to make schema changes that include a zero date or zero in date (e.g. adding a `datetime default '0000-00-00 00:00:00'` column), even if global `sql_mode` on MySQL has `NO_ZERO_IN_DATE,NO_ZERO_DATE`.
12+
913
### azure
1014

1115
Add this flag when executing on Azure Database for MySQL.

go/base/context.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"sync/atomic"
1616
"time"
1717

18-
"github.com/satori/go.uuid"
18+
uuid "github.com/satori/go.uuid"
1919

2020
"github.com/github/gh-ost/go/mysql"
2121
"github.com/github/gh-ost/go/sql"
@@ -90,6 +90,7 @@ type MigrationContext struct {
9090
AssumeRBR bool
9191
SkipForeignKeyChecks bool
9292
SkipStrictMode bool
93+
AllowZeroInDate bool
9394
NullableUniqueKeyAllowed bool
9495
ApproveRenamedColumns bool
9596
SkipRenamedColumns bool

go/cmd/gh-ost/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func main() {
7777
flag.BoolVar(&migrationContext.DiscardForeignKeys, "discard-foreign-keys", false, "DANGER! This flag will migrate a table that has foreign keys and will NOT create foreign keys on the ghost table, thus your altered table will have NO foreign keys. This is useful for intentional dropping of foreign keys")
7878
flag.BoolVar(&migrationContext.SkipForeignKeyChecks, "skip-foreign-key-checks", false, "set to 'true' when you know for certain there are no foreign keys on your table, and wish to skip the time it takes for gh-ost to verify that")
7979
flag.BoolVar(&migrationContext.SkipStrictMode, "skip-strict-mode", false, "explicitly tell gh-ost binlog applier not to enforce strict sql mode")
80+
flag.BoolVar(&migrationContext.AllowZeroInDate, "allow-zero-in-date", false, "explicitly tell gh-ost binlog applier to ignore NO_ZERO_IN_DATE,NO_ZERO_DATE in sql_mode")
8081
flag.BoolVar(&migrationContext.AliyunRDS, "aliyun-rds", false, "set to 'true' when you execute on Aliyun RDS.")
8182
flag.BoolVar(&migrationContext.GoogleCloudPlatform, "gcp", false, "set to 'true' when you execute on a 1st generation Google Cloud Platform (GCP).")
8283
flag.BoolVar(&migrationContext.AzureMySQL, "azure", false, "set to 'true' when you execute on Azure Database on MySQL.")

go/logic/applier.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,24 @@ func (this *Applier) validateAndReadTimeZone() error {
117117
return nil
118118
}
119119

120+
// generateSqlModeQuery return a `sql_mode = ...` query, to be wrapped with a `set session` or `set global`,
121+
// based on gh-ost configuration:
122+
// - User may skip strict mode
123+
// - User may allow zero dats or zero in dates
124+
func (this *Applier) generateSqlModeQuery() string {
125+
sqlModeAddendum := `,NO_AUTO_VALUE_ON_ZERO`
126+
if !this.migrationContext.SkipStrictMode {
127+
sqlModeAddendum = fmt.Sprintf("%s,STRICT_ALL_TABLES", sqlModeAddendum)
128+
}
129+
sqlModeQuery := fmt.Sprintf("CONCAT(@@session.sql_mode, ',%s')", sqlModeAddendum)
130+
if this.migrationContext.AllowZeroInDate {
131+
sqlModeQuery = fmt.Sprintf("REPLACE(REPLACE(%s, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', '')", sqlModeQuery)
132+
}
133+
sqlModeQuery = fmt.Sprintf("sql_mode = %s", sqlModeQuery)
134+
135+
return sqlModeQuery
136+
}
137+
120138
// readTableColumns reads table columns on applier
121139
func (this *Applier) readTableColumns() (err error) {
122140
this.migrationContext.Log.Infof("Examining table structure on applier")
@@ -201,11 +219,33 @@ func (this *Applier) AlterGhost() error {
201219
sql.EscapeName(this.migrationContext.GetGhostTableName()),
202220
)
203221
this.migrationContext.Log.Debugf("ALTER statement: %s", query)
204-
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
205-
return err
206-
}
207-
this.migrationContext.Log.Infof("Ghost table altered")
208-
return nil
222+
223+
err := func() error {
224+
tx, err := this.db.Begin()
225+
if err != nil {
226+
return err
227+
}
228+
defer tx.Rollback()
229+
230+
sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone)
231+
sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery())
232+
233+
if _, err := tx.Exec(sessionQuery); err != nil {
234+
return err
235+
}
236+
if _, err := tx.Exec(query); err != nil {
237+
return err
238+
}
239+
this.migrationContext.Log.Infof("Ghost table altered")
240+
if err := tx.Commit(); err != nil {
241+
// Neither SET SESSION nor ALTER are really transactional, so strictly speaking
242+
// there's no need to commit; but let's do this the legit way anyway.
243+
return err
244+
}
245+
return nil
246+
}()
247+
248+
return err
209249
}
210250

211251
// AlterGhost applies `alter` statement on ghost table
@@ -511,12 +551,9 @@ func (this *Applier) ApplyIterationInsertQuery() (chunkSize int64, rowsAffected
511551
return nil, err
512552
}
513553
defer tx.Rollback()
554+
514555
sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone)
515-
sqlModeAddendum := `,NO_AUTO_VALUE_ON_ZERO`
516-
if !this.migrationContext.SkipStrictMode {
517-
sqlModeAddendum = fmt.Sprintf("%s,STRICT_ALL_TABLES", sqlModeAddendum)
518-
}
519-
sessionQuery = fmt.Sprintf("%s, sql_mode = CONCAT(@@session.sql_mode, ',%s')", sessionQuery, sqlModeAddendum)
556+
sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery())
520557

521558
if _, err := tx.Exec(sessionQuery); err != nil {
522559
return nil, err
@@ -1029,12 +1066,7 @@ func (this *Applier) ApplyDMLEventQueries(dmlEvents [](*binlog.BinlogDMLEvent))
10291066
}
10301067

10311068
sessionQuery := "SET SESSION time_zone = '+00:00'"
1032-
1033-
sqlModeAddendum := `,NO_AUTO_VALUE_ON_ZERO`
1034-
if !this.migrationContext.SkipStrictMode {
1035-
sqlModeAddendum = fmt.Sprintf("%s,STRICT_ALL_TABLES", sqlModeAddendum)
1036-
}
1037-
sessionQuery = fmt.Sprintf("%s, sql_mode = CONCAT(@@session.sql_mode, ',%s')", sessionQuery, sqlModeAddendum)
1069+
sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery())
10381070

10391071
if _, err := tx.Exec(sessionQuery); err != nil {
10401072
return rollback(err)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
drop table if exists gh_ost_test;
2+
create table gh_ost_test (
3+
id int unsigned auto_increment,
4+
i int not null,
5+
dt datetime,
6+
primary key(id)
7+
) auto_increment=1;
8+
9+
drop event if exists gh_ost_test;
10+
delimiter ;;
11+
create event gh_ost_test
12+
on schedule every 1 second
13+
starts current_timestamp
14+
ends current_timestamp + interval 60 second
15+
on completion not preserve
16+
enable
17+
do
18+
begin
19+
insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30');
20+
end ;;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--allow-zero-in-date --alter="change column dt dt datetime not null default '1970-00-00 00:00:00'"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
drop table if exists gh_ost_test;
2+
create table gh_ost_test (
3+
id int unsigned auto_increment,
4+
i int not null,
5+
dt datetime,
6+
primary key(id)
7+
) auto_increment=1;
8+
9+
drop event if exists gh_ost_test;
10+
delimiter ;;
11+
create event gh_ost_test
12+
on schedule every 1 second
13+
starts current_timestamp
14+
ends current_timestamp + interval 60 second
15+
on completion not preserve
16+
enable
17+
do
18+
begin
19+
insert into gh_ost_test values (null, 7, '2010-10-20 10:20:30');
20+
end ;;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Invalid default value for 'dt'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--alter="change column dt dt datetime not null default '1970-00-00 00:00:00'"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(5.5|5.6)

0 commit comments

Comments
 (0)