Skip to content

Commit d291bf6

Browse files
committed
Trigger support -Working - No rollback
1 parent 5fe0cb3 commit d291bf6

File tree

6 files changed

+185
-117
lines changed

6 files changed

+185
-117
lines changed

go/base/context.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414
"sync"
1515
"sync/atomic"
1616
"time"
17+
"unicode/utf8"
1718

18-
"github.com/satori/go.uuid"
19+
uuid "github.com/satori/go.uuid"
1920

2021
"github.com/github/gh-ost/go/mysql"
2122
"github.com/github/gh-ost/go/sql"
@@ -154,10 +155,9 @@ type MigrationContext struct {
154155
InitiallyDropOldTable bool
155156
InitiallyDropGhostTable bool
156157
TimestampOldTable bool // Should old table name include a timestamp
157-
IncludeTriggers bool
158158
CutOverType CutOver
159159
ReplicaServerId uint
160-
160+
161161
Hostname string
162162
AssumeMasterHostname string
163163
ApplierTimeZone string
@@ -221,12 +221,16 @@ type MigrationContext struct {
221221
MappedSharedColumns *sql.ColumnList
222222
MigrationRangeMinValues *sql.ColumnValues
223223
MigrationRangeMaxValues *sql.ColumnValues
224-
Triggers []*sqlutils.RowMap
225224
Iteration int64
226225
MigrationIterationRangeMinValues *sql.ColumnValues
227226
MigrationIterationRangeMaxValues *sql.ColumnValues
228227
ForceTmpTableName string
229228

229+
IncludeTriggers bool
230+
RemoveTriggerSuffix bool
231+
TriggerSuffix string
232+
Triggers []*sqlutils.RowMap
233+
230234
recentBinlogCoordinates mysql.BinlogCoordinates
231235

232236
Log Logger
@@ -856,3 +860,26 @@ func (this *MigrationContext) ReadConfigFile() error {
856860

857861
return nil
858862
}
863+
864+
// getGhostTriggerName generates the name of a ghost trigger, based on original trigger name
865+
// or a given trigger name
866+
func (this *MigrationContext) GetGhostTriggerName(triggerName string) string {
867+
// var triggerName string
868+
if this.RemoveTriggerSuffix && strings.HasSuffix(triggerName, this.TriggerSuffix) {
869+
return strings.TrimSuffix(triggerName, this.TriggerSuffix)
870+
}
871+
872+
return triggerName + this.TriggerSuffix
873+
}
874+
875+
// validateGhostTriggerLength check if the ghost trigger name length is not more than 64 characters
876+
func (this *MigrationContext) ValidateGhostTriggerLengthBelow65chars(triggerName string) bool {
877+
var ghostTriggerName string
878+
if this.RemoveTriggerSuffix && strings.HasSuffix(triggerName, this.TriggerSuffix) {
879+
ghostTriggerName = strings.TrimSuffix(triggerName, this.TriggerSuffix)
880+
} else {
881+
ghostTriggerName = triggerName + this.TriggerSuffix
882+
}
883+
884+
return utf8.RuneCountInString(ghostTriggerName) < 65
885+
}

go/base/context_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io/ioutil"
1111
"os"
12+
"strings"
1213
"testing"
1314
"time"
1415

@@ -60,6 +61,68 @@ func TestGetTableNames(t *testing.T) {
6061
}
6162
}
6263

64+
func TestGetTriggerNames(t *testing.T) {
65+
{
66+
context := NewMigrationContext()
67+
context.TriggerSuffix = "_gho"
68+
test.S(t).ExpectEquals(context.GetGhostTriggerName("my_trigger"), "my_trigger"+context.TriggerSuffix)
69+
}
70+
{
71+
context := NewMigrationContext()
72+
context.TriggerSuffix = "_gho"
73+
context.RemoveTriggerSuffix = true
74+
test.S(t).ExpectEquals(context.GetGhostTriggerName("my_trigger"), "my_trigger"+context.TriggerSuffix)
75+
}
76+
{
77+
context := NewMigrationContext()
78+
context.TriggerSuffix = "_gho"
79+
context.RemoveTriggerSuffix = true
80+
test.S(t).ExpectEquals(context.GetGhostTriggerName("my_trigger_gho"), "my_trigger")
81+
}
82+
{
83+
context := NewMigrationContext()
84+
context.TriggerSuffix = "_gho"
85+
context.RemoveTriggerSuffix = false
86+
test.S(t).ExpectEquals(context.GetGhostTriggerName("my_trigger_gho"), "my_trigger_gho_gho")
87+
}
88+
89+
}
90+
func TestValidateGhostTriggerLengthBelow65chars(t *testing.T) {
91+
{
92+
context := NewMigrationContext()
93+
context.TriggerSuffix = "_gho"
94+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars("my_trigger"), true)
95+
}
96+
{
97+
context := NewMigrationContext()
98+
context.TriggerSuffix = "_ghost"
99+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars(strings.Repeat("my_trigger_ghost", 4)), false) // 64 characters + "_ghost"
100+
}
101+
{
102+
context := NewMigrationContext()
103+
context.TriggerSuffix = "_ghost"
104+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars(strings.Repeat("my_trigger_ghost", 3)), true) // 48 characters + "_ghost"
105+
}
106+
{
107+
context := NewMigrationContext()
108+
context.TriggerSuffix = "_ghost"
109+
context.RemoveTriggerSuffix = true
110+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars(strings.Repeat("my_trigger_ghost", 4)), true) // 64 characters + "_ghost" removed
111+
}
112+
{
113+
context := NewMigrationContext()
114+
context.TriggerSuffix = "_ghost"
115+
context.RemoveTriggerSuffix = true
116+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars(strings.Repeat("my_trigger_ghost", 4)+"X"), false) // 65 characters + "_ghost" not removed
117+
}
118+
{
119+
context := NewMigrationContext()
120+
context.TriggerSuffix = "_ghost"
121+
context.RemoveTriggerSuffix = true
122+
test.S(t).ExpectEquals(context.ValidateGhostTriggerLengthBelow65chars(strings.Repeat("my_trigger_ghost", 4)+"_ghost"), true) // 70 characters + last "_ghost" removed
123+
}
124+
}
125+
63126
func TestReadConfigFile(t *testing.T) {
64127
{
65128
context := NewMigrationContext()

go/cmd/gh-ost/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"os"
1212
"os/signal"
13+
"regexp"
1314
"syscall"
1415

1516
"github.com/github/gh-ost/go/base"
@@ -128,6 +129,8 @@ func main() {
128129
flag.UintVar(&migrationContext.ReplicaServerId, "replica-server-id", 99999, "server id used by gh-ost process. Default: 99999")
129130

130131
flag.BoolVar(&migrationContext.IncludeTriggers, "include-triggers", false, "When true, the triggers (if exist) will be created on the new table")
132+
flag.StringVar(&migrationContext.TriggerSuffix, "trigger-suffix", "", "have to be used with '--include-triggers'")
133+
flag.BoolVar(&migrationContext.RemoveTriggerSuffix, "remove-trigger-suffix-if-exists", false, "Removal (instead of addition) of trigger suffix if it exists at the end of the original triger name. Have to be used with '--include-triggers' and '--TriggerSuffix'")
131134

132135
maxLoad := flag.String("max-load", "", "Comma delimited status-name=threshold. e.g: 'Threads_running=100,Threads_connected=500'. When status exceeds threshold, app throttles writes")
133136
criticalLoad := flag.String("critical-load", "", "Comma delimited status-name=threshold, same format as --max-load. When status exceeds threshold, app panics and quits")
@@ -237,6 +240,19 @@ func main() {
237240
if *replicationLagQuery != "" {
238241
migrationContext.Log.Warningf("--replication-lag-query is deprecated")
239242
}
243+
if migrationContext.IncludeTriggers && migrationContext.TriggerSuffix == "" {
244+
migrationContext.Log.Fatalf("--trigger-suffix must be used with --include-triggers")
245+
}
246+
if !migrationContext.IncludeTriggers && migrationContext.TriggerSuffix != "" {
247+
migrationContext.Log.Fatalf("--trigger-suffix cannot be be used without --include-triggers")
248+
}
249+
if migrationContext.TriggerSuffix != "" {
250+
regex, err := regexp.Compile(`^[\da-zA-Z_]+$`)
251+
252+
if err != nil || !regex.Match([]byte(migrationContext.TriggerSuffix)) {
253+
migrationContext.Log.Fatalf("--trigger-suffix must contain only alpha numeric characters and underscore (0-9,a-z,A-Z,_)")
254+
}
255+
}
240256

241257
switch *cutOver {
242258
case "atomic", "default", "":

go/logic/applier.go

Lines changed: 14 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -273,121 +273,41 @@ func (this *Applier) dropTable(tableName string) error {
273273
}
274274

275275
// dropTriggers drop the triggers on the applied host
276-
func (this *Applier) dropTriggers() error {
276+
func (this *Applier) DropTriggersFromGhost() error {
277277
if len(this.migrationContext.Triggers) > 0 {
278278
for _, trigger := range this.migrationContext.Triggers {
279-
name := (*trigger).GetString("name")
280-
query := fmt.Sprintf("drop trigger if exists %s", sql.EscapeName(name))
281-
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
279+
triggerName := this.migrationContext.GetGhostTriggerName((*trigger).GetString("name"))
280+
query := fmt.Sprintf("drop trigger if exists %s", sql.EscapeName(triggerName))
281+
_, err := sqlutils.ExecNoPrepare(this.db, query)
282+
if err != nil {
282283
return err
283284
}
284-
this.migrationContext.Log.Infof("Trigger '%s' dropped", name)
285+
this.migrationContext.Log.Infof("Trigger '%s' dropped", triggerName)
285286
}
286287
}
287288
return nil
288289
}
289290

290291
// createTriggers creates the triggers on the applied host
291-
func (this *Applier) createTriggers(tableName, suffix string) error {
292+
func (this *Applier) createTriggers(tableName string) error {
292293
if len(this.migrationContext.Triggers) > 0 {
293294
for _, trigger := range this.migrationContext.Triggers {
295+
triggerName := this.migrationContext.GetGhostTriggerName((*trigger).GetString("name"))
294296
query := fmt.Sprintf(`create /* gh-ost */ trigger %s %s %s on %s.%s for each row
295297
%s`,
296-
sql.EscapeName((*trigger).GetString("name")+suffix),
298+
sql.EscapeName(triggerName),
297299
(*trigger).GetString("timing"),
298300
(*trigger).GetString("event"),
299301
sql.EscapeName(this.migrationContext.DatabaseName),
300302
sql.EscapeName(tableName),
301303
(*trigger).GetString("statement"),
302304
)
303-
this.migrationContext.Log.Infof("Createing trigger %s on %s.%s",
304-
sql.EscapeName((*trigger).GetString("name")+suffix),
305-
sql.EscapeName(this.migrationContext.DatabaseName),
306-
sql.EscapeName(tableName),
307-
)
308-
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
309-
// tx.Rollback()
310-
return err
311-
}
312-
}
313-
this.migrationContext.Log.Infof("Triggers created on %s", tableName)
314-
}
315-
return nil
316-
}
317-
318-
// createTriggersWithLock creates the triggers on the applied host while locking the table
319-
func (this *Applier) createTriggersWithLock(tableName string) error {
320-
if len(this.migrationContext.Triggers) > 0 {
321-
for _, trigger := range this.migrationContext.Triggers {
322-
triggerName := (*trigger).GetString("name")
323305
this.migrationContext.Log.Infof("Createing trigger %s on %s.%s",
324306
sql.EscapeName(triggerName),
325307
sql.EscapeName(this.migrationContext.DatabaseName),
326308
sql.EscapeName(tableName),
327309
)
328-
329-
// if err := this.LockOriginalTable(); err != nil {
330-
// return err
331-
// }
332-
333-
// tx, err := this.singletonDB.Begin()
334-
tx, err := this.db.Begin()
335-
if err != nil {
336-
return err
337-
}
338-
339-
// if _, err := tx.Exec("DELIMITER ;;"); err != nil {
340-
// tx.Rollback()
341-
// return err
342-
// }
343-
lockQuery := fmt.Sprintf(`lock /* gh-ost */ tables %s.%s write`,
344-
sql.EscapeName(this.migrationContext.DatabaseName),
345-
sql.EscapeName(this.migrationContext.OriginalTableName),
346-
)
347-
this.migrationContext.Log.Infof("Locking %s.%s",
348-
sql.EscapeName(this.migrationContext.DatabaseName),
349-
sql.EscapeName(this.migrationContext.OriginalTableName),
350-
)
351-
if _, err := tx.Exec(lockQuery); err != nil {
352-
return err
353-
}
354-
355-
if _, err := tx.Exec(fmt.Sprintf("drop trigger if exists %s", sql.EscapeName(triggerName+"_gho"))); err != nil {
356-
return err
357-
}
358-
359-
// if _, err := sqlutils.ExecNoPrepare(this.singletonDB, "DELIMITER ;;"); err != nil {
360-
// return err
361-
// }
362-
// if _, err := sqlutils.ExecNoPrepare(this.singletonDB, fmt.Sprintf("drop trigger if exists %s", sql.EscapeName(triggerName+"_gho"))); err != nil {
363-
// return err
364-
// }
365-
366-
query := fmt.Sprintf(`create /* gh-ost */ trigger %s %s %s on %s for each row
367-
%s`,
368-
sql.EscapeName(triggerName),
369-
(*trigger).GetString("timing"),
370-
(*trigger).GetString("event"),
371-
sql.EscapeName(tableName),
372-
(*trigger).GetString("statement"),
373-
)
374-
// spew.Dump(query)
375-
// if _, err := sqlutils.ExecNoPrepare(this.singletonDB, query); err != nil {
376-
// return err
377-
// }
378-
379-
if _, err := tx.Exec(query); err != nil {
380-
tx.Rollback()
381-
return err
382-
}
383-
384-
// if _, err := tx.Exec("delimiter ;"); err != nil {
385-
// tx.Rollback()
386-
// return err
387-
// }
388-
389-
if err := this.UnlockTables(); err != nil {
390-
// tx.Rollback()
310+
if _, err := sqlutils.ExecNoPrepare(this.db, query); err != nil {
391311
return err
392312
}
393313
}
@@ -397,20 +317,15 @@ func (this *Applier) createTriggersWithLock(tableName string) error {
397317
}
398318

399319
// ReCreateTriggers creates the original triggers on applier host
400-
func (this *Applier) CreateTriggersOnGhost(tableLocked chan<- error) error {
401-
err := this.createTriggers(this.migrationContext.GetGhostTableName(), "_gho")
320+
func (this *Applier) CreateTriggersOnGhostAtomic(tableLocked chan<- error) error {
321+
err := this.createTriggers(this.migrationContext.GetGhostTableName())
402322
tableLocked <- err
403323
return err
404324
}
405325

406326
// ReCreateTriggers creates the original triggers on applier host
407-
func (this *Applier) CreateTriggersOnSource(triggersMigrated chan<- error) error {
408-
if err := this.dropTriggers(); err != nil { // first lets drop original triggers
409-
triggersMigrated <- err
410-
return err
411-
}
412-
err := this.createTriggersWithLock(this.migrationContext.DatabaseName + "." + this.migrationContext.OriginalTableName)
413-
triggersMigrated <- err
327+
func (this *Applier) CreateTriggersOnGhost() error {
328+
err := this.createTriggers(this.migrationContext.GetGhostTableName())
414329
return err
415330
}
416331

0 commit comments

Comments
 (0)