Skip to content

Commit 4d903d0

Browse files
author
Shlomi Noach
authored
Merge pull request #264 from github/discard-foreign-keys
Discard foreign keys
2 parents 167cda3 + 4d4af07 commit 4d903d0

File tree

13 files changed

+151
-13
lines changed

13 files changed

+151
-13
lines changed

build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
#
44

5-
RELEASE_VERSION="1.0.20"
5+
RELEASE_VERSION="1.0.21"
66

77
function build {
88
osname=$1

doc/command-line-flags.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ This is somewhat similar to a Nagios `n`-times test, where `n` in our case is al
5757

5858
Optional. Default is `safe`. See more discussion in [cut-over](cut-over.md)
5959

60+
### discard-foreign-keys
61+
62+
**Danger**: this flag will _silently_ discard any foreign keys existing on your table.
63+
64+
At this time (10-2016) `gh-ost` does not support foreign keys on migrated tables (it bails out when it notices a FK on the migrated table). However, it is able to support _dropping_ of foreign keys via this flag. If you're trying to get rid of foreign keys in your environment, this is a useful flag.
65+
6066
### exact-rowcount
6167

6268
A `gh-ost` execution need to copy whatever rows you have in your existing table onto the ghost table. This can, and often be, a large number. Exactly what that number is?

go/base/context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ type MigrationContext struct {
7070
ApproveRenamedColumns bool
7171
SkipRenamedColumns bool
7272
IsTungsten bool
73+
DiscardForeignKeys bool
7374

7475
config ContextConfig
7576
configMutex *sync.Mutex

go/cmd/gh-ost/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func main() {
6161
flag.BoolVar(&migrationContext.ApproveRenamedColumns, "approve-renamed-columns", false, "in case your `ALTER` statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag approves that gh-ost's interpretation si correct")
6262
flag.BoolVar(&migrationContext.SkipRenamedColumns, "skip-renamed-columns", false, "in case your `ALTER` statement renames columns, gh-ost will note that and offer its interpretation of the rename. By default gh-ost does not proceed to execute. This flag tells gh-ost to skip the renamed columns, i.e. to treat what gh-ost thinks are renamed columns as unrelated columns. NOTE: you may lose column data")
6363
flag.BoolVar(&migrationContext.IsTungsten, "tungsten", false, "explicitly let gh-ost know that you are running on a tungsten-replication based topology (you are likely to also provide --assume-master-host)")
64+
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")
6465

6566
executeFlag := flag.Bool("execute", false, "actually execute the alter & migrate the table. Default is noop: do some tests and exit")
6667
flag.BoolVar(&migrationContext.TestOnReplica, "test-on-replica", false, "Have the migration run on a replica, not on the master. At the end of migration replication is stopped, and tables are swapped and immediately swap-revert. Replication remains stopped and you can compare the two tables for building trust")

go/logic/inspect.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (this *Inspector) ValidateOriginalTable() (err error) {
6363
if err := this.validateTable(); err != nil {
6464
return err
6565
}
66-
if err := this.validateTableForeignKeys(); err != nil {
66+
if err := this.validateTableForeignKeys(this.migrationContext.DiscardForeignKeys); err != nil {
6767
return err
6868
}
6969
if err := this.validateTableTriggers(); err != nil {
@@ -349,34 +349,46 @@ func (this *Inspector) validateTable() error {
349349
}
350350

351351
// validateTableForeignKeys makes sure no foreign keys exist on the migrated table
352-
func (this *Inspector) validateTableForeignKeys() error {
352+
func (this *Inspector) validateTableForeignKeys(allowChildForeignKeys bool) error {
353353
query := `
354-
SELECT TABLE_SCHEMA, TABLE_NAME
354+
SELECT
355+
SUM(REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_SCHEMA=? AND TABLE_NAME=?) as num_child_side_fk,
356+
SUM(REFERENCED_TABLE_NAME IS NOT NULL AND REFERENCED_TABLE_SCHEMA=? AND REFERENCED_TABLE_NAME=?) as num_parent_side_fk
355357
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
356358
WHERE
357359
REFERENCED_TABLE_NAME IS NOT NULL
358360
AND ((TABLE_SCHEMA=? AND TABLE_NAME=?)
359361
OR (REFERENCED_TABLE_SCHEMA=? AND REFERENCED_TABLE_NAME=?)
360362
)
361363
`
362-
numForeignKeys := 0
363-
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
364-
fkSchema := rowMap.GetString("TABLE_SCHEMA")
365-
fkTable := rowMap.GetString("TABLE_NAME")
366-
log.Infof("Found foreign key on %s.%s related to %s.%s", sql.EscapeName(fkSchema), sql.EscapeName(fkTable), sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
367-
numForeignKeys++
364+
numParentForeignKeys := 0
365+
numChildForeignKeys := 0
366+
err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error {
367+
numChildForeignKeys = m.GetInt("num_child_side_fk")
368+
numParentForeignKeys = m.GetInt("num_parent_side_fk")
368369
return nil
369370
},
370371
this.migrationContext.DatabaseName,
371372
this.migrationContext.OriginalTableName,
372373
this.migrationContext.DatabaseName,
373374
this.migrationContext.OriginalTableName,
375+
this.migrationContext.DatabaseName,
376+
this.migrationContext.OriginalTableName,
377+
this.migrationContext.DatabaseName,
378+
this.migrationContext.OriginalTableName,
374379
)
375380
if err != nil {
376381
return err
377382
}
378-
if numForeignKeys > 0 {
379-
return log.Errorf("Found %d foreign keys related to %s.%s. Foreign keys are not supported. Bailing out", numForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
383+
if numParentForeignKeys > 0 {
384+
return log.Errorf("Found %d parent-side foreign keys on %s.%s. Parent-side foreign keys are not supported. Bailing out", numParentForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
385+
}
386+
if numChildForeignKeys > 0 {
387+
if allowChildForeignKeys {
388+
log.Debugf("Foreign keys found and will be dropped, as per given --discard-foreign-keys flag")
389+
return nil
390+
}
391+
return log.Errorf("Found %d child-side foreign keys on %s.%s. Child-side foreign keys are not supported. Bailing out", numChildForeignKeys, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
380392
}
381393
log.Debugf("Validated no foreign keys exist on table")
382394
return nil

localtests/discard-fk/create.sql

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
drop table if exists gh_ost_test_child;
2+
drop table if exists gh_ost_test;
3+
drop table if exists gh_ost_test_fk_parent;
4+
create table gh_ost_test_fk_parent (
5+
id int auto_increment,
6+
ts timestamp,
7+
primary key(id)
8+
);
9+
create table gh_ost_test (
10+
id int auto_increment,
11+
i int not null,
12+
parent_id int not null,
13+
primary key(id),
14+
constraint test_fk foreign key (parent_id) references gh_ost_test_fk_parent (id) on delete no action
15+
) auto_increment=1;
16+
17+
insert into gh_ost_test_fk_parent (id) values (1),(2),(3);
18+
19+
drop event if exists gh_ost_test;
20+
delimiter ;;
21+
create event gh_ost_test
22+
on schedule every 1 second
23+
starts current_timestamp
24+
ends current_timestamp + interval 60 second
25+
on completion not preserve
26+
enable
27+
do
28+
begin
29+
insert into gh_ost_test values (null, 11, 1);
30+
insert into gh_ost_test values (null, 13, 2);
31+
insert into gh_ost_test values (null, 17, 3);
32+
end ;;

localtests/discard-fk/extra_args

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--discard-foreign-keys

localtests/fail-fk-parent/create.sql

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
drop table if exists gh_ost_test_child;
2+
drop table if exists gh_ost_test;
3+
create table gh_ost_test (
4+
id int auto_increment,
5+
primary key(id)
6+
) engine=innodb auto_increment=1;
7+
8+
create table gh_ost_test_child (
9+
id int auto_increment,
10+
i int not null,
11+
parent_id int not null,
12+
constraint test_fk foreign key (parent_id) references gh_ost_test (id) on delete no action,
13+
primary key(id)
14+
) engine=innodb;
15+
insert into gh_ost_test (id) values (1),(2),(3);
16+
17+
drop event if exists gh_ost_test;
18+
drop event if exists gh_ost_test_cleanup;
19+
20+
delimiter ;;
21+
create event gh_ost_test
22+
on schedule every 1 second
23+
starts current_timestamp
24+
ends current_timestamp + interval 60 second
25+
on completion not preserve
26+
enable
27+
do
28+
begin
29+
insert into gh_ost_test_child values (null, 11, 1);
30+
insert into gh_ost_test_child values (null, 13, 2);
31+
insert into gh_ost_test_child values (null, 17, 3);
32+
end ;;
33+
34+
create event gh_ost_test_cleanup
35+
on schedule at current_timestamp + interval 60 second
36+
on completion not preserve
37+
enable
38+
do
39+
begin
40+
drop table if exists gh_ost_test_child;
41+
end ;;

localtests/fail-fk-parent/expect_failure

Whitespace-only changes.

localtests/fail-fk-parent/extra_args

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--discard-foreign-keys

0 commit comments

Comments
 (0)