Skip to content

Commit 76decf9

Browse files
committed
embed time type check in column check
1 parent 7b9f1fc commit 76decf9

File tree

2 files changed

+34
-70
lines changed

2 files changed

+34
-70
lines changed

enginetest/queries/script_queries.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10953,81 +10953,68 @@ where
1095310953
},
1095410954
Assertions: []ScriptTestAssertion{
1095510955
{
10956-
Skip: true,
1095710956
Query: "create table child_timestamp0 (ts timestamp, foreign key (ts) references parent_timestamp6(ts));",
1095810957
Expected: []sql.Row{
1095910958
{types.NewOkResult(0)},
1096010959
},
1096110960
},
1096210961
{
10963-
Skip: true,
1096410962
Query: "insert into child_timestamp0 values ('2001-02-03 12:34:56');",
1096510963
ExpectedErr: sql.ErrForeignKeyChildViolation,
1096610964
},
1096710965
{
10968-
Skip: true,
1096910966
Query: "create table child_timestamp6 (ts timestamp(6), foreign key (ts) references parent_timestamp0(ts));",
1097010967
Expected: []sql.Row{
1097110968
{types.NewOkResult(0)},
1097210969
},
1097310970
},
1097410971
{
10975-
Skip: true,
1097610972
Query: "insert into child_timestamp6 values ('2001-02-03 12:34:56');",
1097710973
ExpectedErr: sql.ErrForeignKeyChildViolation,
1097810974
},
1097910975

1098010976
{
10981-
Skip: true,
1098210977
Query: "create table child1_datetime0 (dt datetime, foreign key (dt) references parent_timestamp0(ts));",
1098310978
Expected: []sql.Row{
1098410979
{types.NewOkResult(0)},
1098510980
},
1098610981
},
1098710982
{
10988-
Skip: true,
1098910983
Query: "insert into child1_datetime0 values ('2001-02-03 12:34:56');",
1099010984
ExpectedErr: sql.ErrForeignKeyChildViolation,
1099110985
},
1099210986
{
10993-
Skip: true,
1099410987
Query: "create table child2_datetime0 (dt datetime, foreign key (dt) references parent_timestamp6(ts));",
1099510988
Expected: []sql.Row{
1099610989
{types.NewOkResult(0)},
1099710990
},
1099810991
},
1099910992
{
11000-
Skip: true,
1100110993
Query: "insert into child2_datetime0 values ('2001-02-03 12:34:56');",
1100210994
ExpectedErr: sql.ErrForeignKeyChildViolation,
1100310995
},
1100410996

1100510997
{
11006-
Skip: true,
1100710998
Query: "create table child1_datetime6 (dt datetime(6), foreign key (dt) references parent_timestamp0(ts));",
1100810999
Expected: []sql.Row{
1100911000
{types.NewOkResult(0)},
1101011001
},
1101111002
},
1101211003
{
11013-
Skip: true,
1101411004
Query: "insert into child1_datetime6 values ('2001-02-03 12:34:56');",
1101511005
ExpectedErr: sql.ErrForeignKeyChildViolation,
1101611006
},
1101711007
{
11018-
Skip: true,
1101911008
Query: "create table child2_datetime6 (dt datetime(6), foreign key (dt) references parent_timestamp6(ts));",
1102011009
Expected: []sql.Row{
1102111010
{types.NewOkResult(0)},
1102211011
},
1102311012
},
1102411013
{
11025-
Skip: true,
1102611014
Query: "insert into child2_datetime6 values ('2001-02-03 12:34:56');",
1102711015
ExpectedErr: sql.ErrForeignKeyChildViolation,
1102811016
},
1102911017
{
11030-
Skip: true,
1103111018
Query: "insert into child2_datetime6 values ('2001-02-03 12:34:56.123456');",
1103211019
ExpectedErr: sql.ErrForeignKeyChildViolation,
1103311020
},
@@ -11069,7 +11056,6 @@ where
1106911056
},
1107011057
},
1107111058
{
11072-
Skip: true,
1107311059
Query: "insert into child_time0 values ('12:34:56');",
1107411060
ExpectedErr: sql.ErrForeignKeyChildViolation,
1107511061
},
@@ -11080,7 +11066,6 @@ where
1108011066
},
1108111067
},
1108211068
{
11083-
Skip: true,
1108411069
Query: "insert into child_time6 values ('12:34:56');",
1108511070
ExpectedErr: sql.ErrForeignKeyChildViolation,
1108611071
},

sql/plan/foreign_key_editor.go

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ func (reference *ForeignKeyReferenceHandler) IsInitialized() bool {
493493
}
494494

495495
// CheckReference checks that the given row has an index entry in the referenced table.
496+
// Performs MySQL-compatible foreign key constraint validation with type-specific checks.
496497
func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, row sql.Row) error {
497498
// If even one of the values are NULL then we don't check the parent
498499
for _, pos := range reference.RowMapper.IndexPositions {
@@ -507,7 +508,7 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
507508
}
508509
defer rowIter.Close(ctx)
509510

510-
_, err = rowIter.Next(ctx)
511+
parentRow, err := rowIter.Next(ctx)
511512
if err != nil && err != io.EOF {
512513
// For SET types, conversion failures during foreign key validation should be treated as foreign key violations
513514
if sql.ErrConvertingToSet.Is(err) || sql.ErrInvalidSetValue.Is(err) {
@@ -518,12 +519,10 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
518519
}
519520
if err == nil {
520521
// We have a parent row, but check for type-specific validation
521-
if validationErr := reference.validateDecimalConstraints(row); validationErr != nil {
522-
return validationErr
523-
}
524-
if validationErr := reference.validateTimeConstraints(row); validationErr != nil {
522+
if validationErr := reference.validateColumnTypeConstraints(ctx, row, parentRow); validationErr != nil {
525523
return validationErr
526524
}
525+
527526
// We have a parent row so throw no error
528527
return nil
529528
}
@@ -551,76 +550,55 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
551550
reference.ForeignKey.ParentTable, reference.RowMapper.GetKeyString(row))
552551
}
553552

554-
// validateDecimalConstraints checks that decimal foreign key columns have compatible scales.
555-
func (reference *ForeignKeyReferenceHandler) validateDecimalConstraints(row sql.Row) error {
556-
if reference.RowMapper.Index == nil {
557-
return nil
558-
}
559-
indexColumnTypes := reference.RowMapper.Index.ColumnExpressionTypes()
560-
for parentIdx, parentCol := range indexColumnTypes {
561-
if parentIdx >= len(reference.RowMapper.IndexPositions) {
562-
break
563-
}
564-
parentType := parentCol.Type
565-
childColIdx := reference.RowMapper.IndexPositions[parentIdx]
566-
childType := reference.RowMapper.SourceSch[childColIdx].Type
567-
childDecimal, ok := childType.(sql.DecimalType)
568-
if !ok {
569-
continue
570-
}
571-
parentDecimal, ok := parentType.(sql.DecimalType)
572-
if !ok {
573-
continue
574-
}
575-
if childDecimal.Scale() != parentDecimal.Scale() {
576-
return sql.ErrForeignKeyChildViolation.New(
577-
reference.ForeignKey.Name,
578-
reference.ForeignKey.Table,
579-
reference.ForeignKey.ParentTable,
580-
reference.RowMapper.GetKeyString(row),
581-
)
582-
}
583-
}
584-
return nil
585-
}
586553

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 {
554+
// validateColumnTypeConstraints validates that column types meet MySQL foreign key requirements.
555+
// Centralizes type validation for decimal scale matching and exact time type precision matching.
556+
func (reference *ForeignKeyReferenceHandler) validateColumnTypeConstraints(ctx *sql.Context, childRow sql.Row, parentRow sql.Row) error {
557+
mapper := reference.RowMapper
558+
if mapper.Index == nil {
592559
return nil
593560
}
594-
indexColumnTypes := reference.RowMapper.Index.ColumnExpressionTypes()
595-
for parentIdx, parentCol := range indexColumnTypes {
596-
if parentIdx >= len(reference.RowMapper.IndexPositions) {
561+
562+
for parentIdx, parentCol := range mapper.Index.ColumnExpressionTypes() {
563+
if parentIdx >= len(mapper.IndexPositions) {
597564
break
598565
}
566+
599567
parentType := parentCol.Type
600-
childColIdx := reference.RowMapper.IndexPositions[parentIdx]
601-
childType := reference.RowMapper.SourceSch[childColIdx].Type
568+
childType := mapper.SourceSch[mapper.IndexPositions[parentIdx]].Type
602569

603-
// Check if both types are time-related
604-
isChildTime := types.IsTime(childType) || types.IsTimespan(childType)
605-
isParentTime := types.IsTime(parentType) || types.IsTimespan(parentType)
570+
// Check for constraint violations
571+
hasViolation := false
606572

607-
if !isChildTime || !isParentTime {
608-
continue
573+
// Decimal scale must match
574+
if childDecimal, ok := childType.(sql.DecimalType); ok {
575+
if parentDecimal, ok := parentType.(sql.DecimalType); ok {
576+
hasViolation = childDecimal.Scale() != parentDecimal.Scale()
577+
}
609578
}
610579

611-
// MySQL requires exact type matching for time types in foreign key validation
612-
if !childType.Equals(parentType) {
580+
// Time types must match exactly (including precision)
581+
if !hasViolation {
582+
isChildTime := types.IsTime(childType) || types.IsTimespan(childType)
583+
isParentTime := types.IsTime(parentType) || types.IsTimespan(parentType)
584+
if isChildTime && isParentTime {
585+
hasViolation = !childType.Equals(parentType)
586+
}
587+
}
588+
589+
if hasViolation {
613590
return sql.ErrForeignKeyChildViolation.New(
614591
reference.ForeignKey.Name,
615592
reference.ForeignKey.Table,
616593
reference.ForeignKey.ParentTable,
617-
reference.RowMapper.GetKeyString(row),
594+
mapper.GetKeyString(childRow),
618595
)
619596
}
620597
}
621598
return nil
622599
}
623600

601+
624602
// CheckTable checks that every row in the table has an index entry in the referenced table.
625603
func (reference *ForeignKeyReferenceHandler) CheckTable(ctx *sql.Context, tbl sql.ForeignKeyTable) error {
626604
partIter, err := tbl.Partitions(ctx)
@@ -678,6 +656,7 @@ func (mapper *ForeignKeyRowMapper) GetIter(ctx *sql.Context, row sql.Row, refChe
678656
}
679657

680658
targetType := mapper.SourceSch[rowPos].Type
659+
681660
// Transform the type of the value in this row to the one in the other table for the index lookup, if necessary
682661
if mapper.TargetTypeConversions != nil && mapper.TargetTypeConversions[rowPos] != nil {
683662
var err error

0 commit comments

Comments
 (0)