From e22aa12956bf2f7b4163e04d09f7e8b9df62f94a Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Thu, 6 Mar 2025 12:18:07 -0800 Subject: [PATCH] Expose a hook for foreign key validation, which happens at runtime rather than analysis --- sql/plan/alter_foreign_key.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sql/plan/alter_foreign_key.go b/sql/plan/alter_foreign_key.go index 4f80b00caf..f17a9b2b27 100644 --- a/sql/plan/alter_foreign_key.go +++ b/sql/plan/alter_foreign_key.go @@ -105,6 +105,9 @@ func (p *CreateForeignKey) String() string { return pr.String() } +// ValidateForeignKeyDefinition checks that the foreign key definition is valid for creation +var ValidateForeignKeyDefinition = validateForeignKeyDefinition + // ResolveForeignKey verifies the foreign key definition and resolves the foreign key, creating indexes and validating // data as necessary. // fkChecks - whether to check the foreign key against the data in the table @@ -171,16 +174,9 @@ func ResolveForeignKey(ctx *sql.Context, tbl sql.ForeignKeyTable, refTbl sql.For } // Check that the types align and are valid - for i := range fkDef.Columns { - col := cols[strings.ToLower(fkDef.Columns[i])] - parentCol := parentCols[strings.ToLower(fkDef.ParentColumns[i])] - if !foreignKeyComparableTypes(ctx, col.Type, parentCol.Type) { - return sql.ErrForeignKeyColumnTypeMismatch.New(fkDef.Columns[i], fkDef.ParentColumns[i]) - } - sqlParserType := col.Type.Type() - if sqlParserType == sqltypes.Text || sqlParserType == sqltypes.Blob { - return sql.ErrForeignKeyTextBlob.New() - } + err := ValidateForeignKeyDefinition(ctx, fkDef, cols, parentCols) + if err != nil { + return err } // Ensure that a suitable index exists on the referenced table, and check the declaring table for a suitable index. @@ -324,6 +320,22 @@ func ResolveForeignKey(ctx *sql.Context, tbl sql.ForeignKeyTable, refTbl sql.For } } +// validateForeignKeyDefinition checks that the foreign key definition is valid for creation +func validateForeignKeyDefinition(ctx *sql.Context, fkDef sql.ForeignKeyConstraint, cols map[string]*sql.Column, parentCols map[string]*sql.Column) error { + for i := range fkDef.Columns { + col := cols[strings.ToLower(fkDef.Columns[i])] + parentCol := parentCols[strings.ToLower(fkDef.ParentColumns[i])] + if !foreignKeyComparableTypes(ctx, col.Type, parentCol.Type) { + return sql.ErrForeignKeyColumnTypeMismatch.New(fkDef.Columns[i], fkDef.ParentColumns[i]) + } + sqlParserType := col.Type.Type() + if sqlParserType == sqltypes.Text || sqlParserType == sqltypes.Blob { + return sql.ErrForeignKeyTextBlob.New() + } + } + return nil +} + type DropForeignKey struct { // In the cases where we have multiple ALTER statements, we need to resolve the table at execution time rather than // during analysis. Otherwise, you could add a foreign key in the preceding alter and we may have analyzed to a