Skip to content

Commit 7b9f1fc

Browse files
committed
fix fk time issues
1 parent e4d9d0a commit 7b9f1fc

File tree

3 files changed

+50
-13
lines changed

3 files changed

+50
-13
lines changed

enginetest/queries/script_queries.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10856,81 +10856,68 @@ where
1085610856
},
1085710857
Assertions: []ScriptTestAssertion{
1085810858
{
10859-
Skip: true,
1086010859
Query: "create table child_datetime0 (dt datetime, foreign key (dt) references parent_datetime6(dt));",
1086110860
Expected: []sql.Row{
1086210861
{types.NewOkResult(0)},
1086310862
},
1086410863
},
1086510864
{
10866-
Skip: true,
1086710865
Query: "insert into child_datetime0 values ('2001-02-03 12:34:56');",
1086810866
ExpectedErr: sql.ErrForeignKeyChildViolation,
1086910867
},
1087010868
{
10871-
Skip: true,
1087210869
Query: "create table child_datetime6 (dt datetime(6), foreign key (dt) references parent_datetime0(dt));",
1087310870
Expected: []sql.Row{
1087410871
{types.NewOkResult(0)},
1087510872
},
1087610873
},
1087710874
{
10878-
Skip: true,
1087910875
Query: "insert into child_datetime6 values ('2001-02-03 12:34:56');",
1088010876
ExpectedErr: sql.ErrForeignKeyChildViolation,
1088110877
},
1088210878

1088310879
{
10884-
Skip: true,
1088510880
Query: "create table child1_timestamp0 (ts timestamp, foreign key (ts) references parent_datetime0(dt));",
1088610881
Expected: []sql.Row{
1088710882
{types.NewOkResult(0)},
1088810883
},
1088910884
},
1089010885
{
10891-
Skip: true,
1089210886
Query: "insert into child1_timestamp0 values ('2001-02-03 12:34:56');",
1089310887
ExpectedErr: sql.ErrForeignKeyChildViolation,
1089410888
},
1089510889
{
10896-
Skip: true,
1089710890
Query: "create table child2_timestamp0 (ts timestamp, foreign key (ts) references parent_datetime6(dt));",
1089810891
Expected: []sql.Row{
1089910892
{types.NewOkResult(0)},
1090010893
},
1090110894
},
1090210895
{
10903-
Skip: true,
1090410896
Query: "insert into child2_timestamp0 values ('2001-02-03 12:34:56');",
1090510897
ExpectedErr: sql.ErrForeignKeyChildViolation,
1090610898
},
1090710899

1090810900
{
10909-
Skip: true,
1091010901
Query: "create table child1_timestamp6 (ts timestamp(6), foreign key (ts) references parent_datetime0(dt));",
1091110902
Expected: []sql.Row{
1091210903
{types.NewOkResult(0)},
1091310904
},
1091410905
},
1091510906
{
10916-
Skip: true,
1091710907
Query: "insert into child1_timestamp6 values ('2001-02-03 12:34:56');",
1091810908
ExpectedErr: sql.ErrForeignKeyChildViolation,
1091910909
},
1092010910
{
10921-
Skip: true,
1092210911
Query: "create table child2_timestamp6 (ts timestamp(6), foreign key (ts) references parent_datetime6(dt));",
1092310912
Expected: []sql.Row{
1092410913
{types.NewOkResult(0)},
1092510914
},
1092610915
},
1092710916
{
10928-
Skip: true,
1092910917
Query: "insert into child2_timestamp6 values ('2001-02-03 12:34:56');",
1093010918
ExpectedErr: sql.ErrForeignKeyChildViolation,
1093110919
},
1093210920
{
10933-
Skip: true,
1093410921
Query: "insert into child2_timestamp6 values ('2001-02-03 12:34:56.123456');",
1093510922
ExpectedErr: sql.ErrForeignKeyChildViolation,
1093610923
},

sql/plan/alter_foreign_key.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,16 @@ func foreignKeyComparableTypes(ctx *sql.Context, type1 sql.Type, type2 sql.Type)
651651
t1 := type1.Type()
652652
t2 := type2.Type()
653653

654+
// Handle time-related types with different precisions or cross-type references
655+
if (types.IsTime(type1) || types.IsTimespan(type1)) && (types.IsTime(type2) || types.IsTimespan(type2)) {
656+
// MySQL allows time-related types to reference each other in foreign keys:
657+
// - DATETIME can reference DATETIME with different precision
658+
// - TIMESTAMP can reference TIMESTAMP with different precision
659+
// - DATETIME can reference TIMESTAMP and vice versa
660+
// - TIME can reference TIME with different precision
661+
return true
662+
}
663+
654664
// Handle same-type cases for special types
655665
if t1 == t2 {
656666
switch t1 {

sql/plan/foreign_key_editor.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,9 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
521521
if validationErr := reference.validateDecimalConstraints(row); validationErr != nil {
522522
return validationErr
523523
}
524+
if validationErr := reference.validateTimeConstraints(row); validationErr != nil {
525+
return validationErr
526+
}
524527
// We have a parent row so throw no error
525528
return nil
526529
}
@@ -581,6 +584,43 @@ func (reference *ForeignKeyReferenceHandler) validateDecimalConstraints(row sql.
581584
return nil
582585
}
583586

587+
// validateTimeConstraints checks that time-related foreign key columns have exact type and precision matches.
588+
// MySQL requires strict matching for time types in foreign keys - even logically equivalent values
589+
// like '2001-02-03 12:34:56' vs '2001-02-03 12:34:56.000000' are rejected if precision differs.
590+
func (reference *ForeignKeyReferenceHandler) validateTimeConstraints(row sql.Row) error {
591+
if reference.RowMapper.Index == nil {
592+
return nil
593+
}
594+
indexColumnTypes := reference.RowMapper.Index.ColumnExpressionTypes()
595+
for parentIdx, parentCol := range indexColumnTypes {
596+
if parentIdx >= len(reference.RowMapper.IndexPositions) {
597+
break
598+
}
599+
parentType := parentCol.Type
600+
childColIdx := reference.RowMapper.IndexPositions[parentIdx]
601+
childType := reference.RowMapper.SourceSch[childColIdx].Type
602+
603+
// Check if both types are time-related
604+
isChildTime := types.IsTime(childType) || types.IsTimespan(childType)
605+
isParentTime := types.IsTime(parentType) || types.IsTimespan(parentType)
606+
607+
if !isChildTime || !isParentTime {
608+
continue
609+
}
610+
611+
// MySQL requires exact type matching for time types in foreign key validation
612+
if !childType.Equals(parentType) {
613+
return sql.ErrForeignKeyChildViolation.New(
614+
reference.ForeignKey.Name,
615+
reference.ForeignKey.Table,
616+
reference.ForeignKey.ParentTable,
617+
reference.RowMapper.GetKeyString(row),
618+
)
619+
}
620+
}
621+
return nil
622+
}
623+
584624
// CheckTable checks that every row in the table has an index entry in the referenced table.
585625
func (reference *ForeignKeyReferenceHandler) CheckTable(ctx *sql.Context, tbl sql.ForeignKeyTable) error {
586626
partIter, err := tbl.Partitions(ctx)

0 commit comments

Comments
 (0)