Skip to content

Commit 76b20d5

Browse files
authored
Merge pull request #3109 from dolthub/elianddb/9544-fix-time-fk-constraints
dolthub/dolt#9544 - Fix time type foreign key constraints
2 parents 7bc2294 + 22c3902 commit 76b20d5

File tree

3 files changed

+40
-46
lines changed

3 files changed

+40
-46
lines changed

enginetest/queries/script_queries.go

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11088,81 +11088,68 @@ where
1108811088
},
1108911089
Assertions: []ScriptTestAssertion{
1109011090
{
11091-
Skip: true,
1109211091
Query: "create table child_datetime0 (dt datetime, foreign key (dt) references parent_datetime6(dt));",
1109311092
Expected: []sql.Row{
1109411093
{types.NewOkResult(0)},
1109511094
},
1109611095
},
1109711096
{
11098-
Skip: true,
1109911097
Query: "insert into child_datetime0 values ('2001-02-03 12:34:56');",
1110011098
ExpectedErr: sql.ErrForeignKeyChildViolation,
1110111099
},
1110211100
{
11103-
Skip: true,
1110411101
Query: "create table child_datetime6 (dt datetime(6), foreign key (dt) references parent_datetime0(dt));",
1110511102
Expected: []sql.Row{
1110611103
{types.NewOkResult(0)},
1110711104
},
1110811105
},
1110911106
{
11110-
Skip: true,
1111111107
Query: "insert into child_datetime6 values ('2001-02-03 12:34:56');",
1111211108
ExpectedErr: sql.ErrForeignKeyChildViolation,
1111311109
},
1111411110

1111511111
{
11116-
Skip: true,
1111711112
Query: "create table child1_timestamp0 (ts timestamp, foreign key (ts) references parent_datetime0(dt));",
1111811113
Expected: []sql.Row{
1111911114
{types.NewOkResult(0)},
1112011115
},
1112111116
},
1112211117
{
11123-
Skip: true,
1112411118
Query: "insert into child1_timestamp0 values ('2001-02-03 12:34:56');",
1112511119
ExpectedErr: sql.ErrForeignKeyChildViolation,
1112611120
},
1112711121
{
11128-
Skip: true,
1112911122
Query: "create table child2_timestamp0 (ts timestamp, foreign key (ts) references parent_datetime6(dt));",
1113011123
Expected: []sql.Row{
1113111124
{types.NewOkResult(0)},
1113211125
},
1113311126
},
1113411127
{
11135-
Skip: true,
1113611128
Query: "insert into child2_timestamp0 values ('2001-02-03 12:34:56');",
1113711129
ExpectedErr: sql.ErrForeignKeyChildViolation,
1113811130
},
1113911131

1114011132
{
11141-
Skip: true,
1114211133
Query: "create table child1_timestamp6 (ts timestamp(6), foreign key (ts) references parent_datetime0(dt));",
1114311134
Expected: []sql.Row{
1114411135
{types.NewOkResult(0)},
1114511136
},
1114611137
},
1114711138
{
11148-
Skip: true,
1114911139
Query: "insert into child1_timestamp6 values ('2001-02-03 12:34:56');",
1115011140
ExpectedErr: sql.ErrForeignKeyChildViolation,
1115111141
},
1115211142
{
11153-
Skip: true,
1115411143
Query: "create table child2_timestamp6 (ts timestamp(6), foreign key (ts) references parent_datetime6(dt));",
1115511144
Expected: []sql.Row{
1115611145
{types.NewOkResult(0)},
1115711146
},
1115811147
},
1115911148
{
11160-
Skip: true,
1116111149
Query: "insert into child2_timestamp6 values ('2001-02-03 12:34:56');",
1116211150
ExpectedErr: sql.ErrForeignKeyChildViolation,
1116311151
},
1116411152
{
11165-
Skip: true,
1116611153
Query: "insert into child2_timestamp6 values ('2001-02-03 12:34:56.123456');",
1116711154
ExpectedErr: sql.ErrForeignKeyChildViolation,
1116811155
},
@@ -11198,81 +11185,68 @@ where
1119811185
},
1119911186
Assertions: []ScriptTestAssertion{
1120011187
{
11201-
Skip: true,
1120211188
Query: "create table child_timestamp0 (ts timestamp, foreign key (ts) references parent_timestamp6(ts));",
1120311189
Expected: []sql.Row{
1120411190
{types.NewOkResult(0)},
1120511191
},
1120611192
},
1120711193
{
11208-
Skip: true,
1120911194
Query: "insert into child_timestamp0 values ('2001-02-03 12:34:56');",
1121011195
ExpectedErr: sql.ErrForeignKeyChildViolation,
1121111196
},
1121211197
{
11213-
Skip: true,
1121411198
Query: "create table child_timestamp6 (ts timestamp(6), foreign key (ts) references parent_timestamp0(ts));",
1121511199
Expected: []sql.Row{
1121611200
{types.NewOkResult(0)},
1121711201
},
1121811202
},
1121911203
{
11220-
Skip: true,
1122111204
Query: "insert into child_timestamp6 values ('2001-02-03 12:34:56');",
1122211205
ExpectedErr: sql.ErrForeignKeyChildViolation,
1122311206
},
1122411207

1122511208
{
11226-
Skip: true,
1122711209
Query: "create table child1_datetime0 (dt datetime, foreign key (dt) references parent_timestamp0(ts));",
1122811210
Expected: []sql.Row{
1122911211
{types.NewOkResult(0)},
1123011212
},
1123111213
},
1123211214
{
11233-
Skip: true,
1123411215
Query: "insert into child1_datetime0 values ('2001-02-03 12:34:56');",
1123511216
ExpectedErr: sql.ErrForeignKeyChildViolation,
1123611217
},
1123711218
{
11238-
Skip: true,
1123911219
Query: "create table child2_datetime0 (dt datetime, foreign key (dt) references parent_timestamp6(ts));",
1124011220
Expected: []sql.Row{
1124111221
{types.NewOkResult(0)},
1124211222
},
1124311223
},
1124411224
{
11245-
Skip: true,
1124611225
Query: "insert into child2_datetime0 values ('2001-02-03 12:34:56');",
1124711226
ExpectedErr: sql.ErrForeignKeyChildViolation,
1124811227
},
1124911228

1125011229
{
11251-
Skip: true,
1125211230
Query: "create table child1_datetime6 (dt datetime(6), foreign key (dt) references parent_timestamp0(ts));",
1125311231
Expected: []sql.Row{
1125411232
{types.NewOkResult(0)},
1125511233
},
1125611234
},
1125711235
{
11258-
Skip: true,
1125911236
Query: "insert into child1_datetime6 values ('2001-02-03 12:34:56');",
1126011237
ExpectedErr: sql.ErrForeignKeyChildViolation,
1126111238
},
1126211239
{
11263-
Skip: true,
1126411240
Query: "create table child2_datetime6 (dt datetime(6), foreign key (dt) references parent_timestamp6(ts));",
1126511241
Expected: []sql.Row{
1126611242
{types.NewOkResult(0)},
1126711243
},
1126811244
},
1126911245
{
11270-
Skip: true,
1127111246
Query: "insert into child2_datetime6 values ('2001-02-03 12:34:56');",
1127211247
ExpectedErr: sql.ErrForeignKeyChildViolation,
1127311248
},
1127411249
{
11275-
Skip: true,
1127611250
Query: "insert into child2_datetime6 values ('2001-02-03 12:34:56.123456');",
1127711251
ExpectedErr: sql.ErrForeignKeyChildViolation,
1127811252
},
@@ -11314,9 +11288,9 @@ where
1131411288
},
1131511289
},
1131611290
{
11317-
Skip: true,
1131811291
Query: "insert into child_time0 values ('12:34:56');",
1131911292
ExpectedErr: sql.ErrForeignKeyChildViolation,
11293+
Skip: true, // TODO: Fix TIME precision handling in foreign key constraints (https://github.com/dolthub/dolt/issues/9544)
1132011294
},
1132111295
{
1132211296
Query: "create table child_time6 (t time(6), foreign key (t) references parent_time0(t));",
@@ -11325,9 +11299,9 @@ where
1132511299
},
1132611300
},
1132711301
{
11328-
Skip: true,
1132911302
Query: "insert into child_time6 values ('12:34:56');",
1133011303
ExpectedErr: sql.ErrForeignKeyChildViolation,
11304+
Skip: true, // TODO: Fix TIME precision handling in foreign key constraints (https://github.com/dolthub/dolt/issues/9544)
1133111305
},
1133211306
},
1133311307
},

sql/plan/alter_foreign_key.go

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

654+
// MySQL allows time-related types to reference each other in foreign keys
655+
if (types.IsTime(type1) || types.IsTimespan(type1)) && (types.IsTime(type2) || types.IsTimespan(type2)) {
656+
return true
657+
}
658+
654659
// Handle same-type cases for special types
655660
if t1 == t2 {
656661
switch t1 {

sql/plan/foreign_key_editor.go

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
507507
}
508508
defer rowIter.Close(ctx)
509509

510-
_, err = rowIter.Next(ctx)
510+
parentRow, err := rowIter.Next(ctx)
511511
if err != nil && err != io.EOF {
512512
// For SET types, conversion failures during foreign key validation should be treated as foreign key violations
513513
if sql.ErrConvertingToSet.Is(err) || sql.ErrInvalidSetValue.Is(err) {
@@ -518,9 +518,10 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
518518
}
519519
if err == nil {
520520
// We have a parent row, but check for type-specific validation
521-
if validationErr := reference.validateDecimalConstraints(row); validationErr != nil {
521+
if validationErr := reference.validateColumnTypeConstraints(ctx, row, parentRow); validationErr != nil {
522522
return validationErr
523523
}
524+
524525
// We have a parent row so throw no error
525526
return nil
526527
}
@@ -548,33 +549,46 @@ func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, ro
548549
reference.ForeignKey.ParentTable, reference.RowMapper.GetKeyString(row))
549550
}
550551

551-
// validateDecimalConstraints checks that decimal foreign key columns have compatible scales.
552-
func (reference *ForeignKeyReferenceHandler) validateDecimalConstraints(row sql.Row) error {
553-
if reference.RowMapper.Index == nil {
552+
// validateColumnTypeConstraints enforces foreign key type validation between child and parent columns in a foreign key relationship.
553+
func (reference *ForeignKeyReferenceHandler) validateColumnTypeConstraints(ctx *sql.Context, childRow sql.Row, parentRow sql.Row) error {
554+
mapper := reference.RowMapper
555+
if mapper.Index == nil {
554556
return nil
555557
}
556-
indexColumnTypes := reference.RowMapper.Index.ColumnExpressionTypes()
557-
for parentIdx, parentCol := range indexColumnTypes {
558-
if parentIdx >= len(reference.RowMapper.IndexPositions) {
558+
559+
for parentIdx, parentCol := range mapper.Index.ColumnExpressionTypes() {
560+
if parentIdx >= len(mapper.IndexPositions) {
559561
break
560562
}
563+
561564
parentType := parentCol.Type
562-
childColIdx := reference.RowMapper.IndexPositions[parentIdx]
563-
childType := reference.RowMapper.SourceSch[childColIdx].Type
564-
childDecimal, ok := childType.(sql.DecimalType)
565-
if !ok {
566-
continue
565+
childType := mapper.SourceSch[mapper.IndexPositions[parentIdx]].Type
566+
hasViolation := false
567+
568+
// For decimal types, scales must match exactly
569+
childDecimal, childOk := childType.(sql.DecimalType)
570+
parentDecimal, parentOk := parentType.(sql.DecimalType)
571+
if childOk && parentOk {
572+
hasViolation = childDecimal.Scale() != parentDecimal.Scale()
567573
}
568-
parentDecimal, ok := parentType.(sql.DecimalType)
569-
if !ok {
570-
continue
574+
575+
// For time types, require exact type matching (including precision)
576+
// TODO: The TIME type currently normalizes all precisions to TIME(6) internally,
577+
// which means TIME and TIME(n) are all treated as TIME(6). This prevents proper
578+
// precision validation between different TIME types in foreign keys.
579+
// See time.go:"TIME is implemented as TIME(6)."
580+
isChildTime := types.IsTime(childType) || types.IsTimespan(childType)
581+
isParentTime := types.IsTime(parentType) || types.IsTimespan(parentType)
582+
if isChildTime && isParentTime {
583+
hasViolation = hasViolation || !childType.Equals(parentType)
571584
}
572-
if childDecimal.Scale() != parentDecimal.Scale() {
585+
586+
if hasViolation {
573587
return sql.ErrForeignKeyChildViolation.New(
574588
reference.ForeignKey.Name,
575589
reference.ForeignKey.Table,
576590
reference.ForeignKey.ParentTable,
577-
reference.RowMapper.GetKeyString(row),
591+
mapper.GetKeyString(childRow),
578592
)
579593
}
580594
}
@@ -638,6 +652,7 @@ func (mapper *ForeignKeyRowMapper) GetIter(ctx *sql.Context, row sql.Row, refChe
638652
}
639653

640654
targetType := mapper.SourceSch[rowPos].Type
655+
641656
// Transform the type of the value in this row to the one in the other table for the index lookup, if necessary
642657
if mapper.TargetTypeConversions != nil && mapper.TargetTypeConversions[rowPos] != nil {
643658
var err error

0 commit comments

Comments
 (0)