Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Cascade Planning leading to Foreign key constraint verification #13902

Merged
merged 15 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions go/test/endtoend/vtgate/foreignkey/fk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ func TestUpdateWithFK(t *testing.T) {
utils.Exec(t, conn, `insert into u_t2(id, col2) values (342, 123), (19, 1234)`)
utils.Exec(t, conn, `insert into u_t3(id, col3) values (32, 123), (1, 12)`)

t.Run("Cascade update with a new value", func(t *testing.T) {
t.Skip("This doesn't work right now. We are able to only cascade updates for which the data already exists in the parent table")
_ = utils.Exec(t, conn, `update u_t1 set col1 = 2 where id = 100`)
})
// Cascade update with a new value
_ = utils.Exec(t, conn, `update u_t1 set col1 = 2 where id = 100`)
// Verify the result in u_t2 and u_t3 as well.
utils.AssertMatches(t, conn, `select * from u_t2 order by id`, `[[INT64(19) INT64(1234)] [INT64(342) NULL]]`)
utils.AssertMatches(t, conn, `select * from u_t3 order by id`, `[[INT64(1) INT64(12)] [INT64(32) INT64(2)]]`)

// Update u_t1 which has a foreign key constraint to u_t2 with SET NULL type, and to u_t3 with CASCADE type.
qr = utils.Exec(t, conn, `update u_t1 set col1 = 13 where id = 100`)
Expand Down
11 changes: 11 additions & 0 deletions go/vt/sqlparser/ast_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2532,3 +2532,14 @@ func (v *visitor) visitAllSelects(in SelectStatement, f func(p *Select, idx int)
}
panic("switch should be exhaustive")
}

func IsNonLiteral(updExprs UpdateExprs) bool {
for _, updateExpr := range updExprs {
switch updateExpr.Expr.(type) {
case *Argument, *NullVal, BoolVal, *Literal:
default:
return true
}
}
return false
}
2 changes: 0 additions & 2 deletions go/vt/vterrors/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ var (

VT12001 = errorWithoutState("VT12001", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: %s", "This statement is unsupported by Vitess. Please rewrite your query to use supported syntax.")
VT12002 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: cross-shard foreign keys", "Vitess does not support cross shard foreign keys.")
VT12003 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: foreign keys management at vitess", "Vitess does not support managing foreign keys tables.")

// VT13001 General Error
VT13001 = errorWithoutState("VT13001", vtrpcpb.Code_INTERNAL, "[BUG] %s", "This error should not happen and is a bug. Please file an issue on GitHub: https://github.com/vitessio/vitess/issues/new/choose.")
Expand Down Expand Up @@ -148,7 +147,6 @@ var (
VT10001,
VT12001,
VT12002,
VT12003,
VT13001,
VT13002,
VT14001,
Expand Down
55 changes: 17 additions & 38 deletions go/vt/vtgate/engine/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 26 additions & 89 deletions go/vt/vtgate/engine/fk_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,30 @@ import (
"vitess.io/vitess/go/sqltypes"
querypb "vitess.io/vitess/go/vt/proto/query"
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
"vitess.io/vitess/go/vt/sqlparser"
"vitess.io/vitess/go/vt/vterrors"
)

// FkParent is a primitive that represents a parent table foreign key constraint to verify against.
type FkParent struct {
Values []sqlparser.Exprs
Cols []CheckCol
BvName string

// Verify contains the verification primitve and its type i.e. parent or child
type Verify struct {
Exec Primitive
Typ string
}

// FkVerify is a primitive that verifies that the foreign key constraints in parent tables are satisfied.
// It does this by executing a select distinct query on the parent table with the values that are being inserted/updated.
type FkVerify struct {
Verify []*FkParent
Verify []*Verify
Exec Primitive

txNeeded
}

// constants for verification type.
const (
ParentVerify = "VerifyParent"
ChildVerify = "VerifyChild"
)

// RouteType implements the Primitive interface
func (f *FkVerify) RouteType() string {
return "FKVerify"
Expand All @@ -67,78 +69,30 @@ func (f *FkVerify) GetFields(ctx context.Context, vcursor VCursor, bindVars map[

// TryExecute implements the Primitive interface
func (f *FkVerify) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
for _, fk := range f.Verify {
pt := newProbeTable(fk.Cols)
newBv := &querypb.BindVariable{
Type: querypb.Type_TUPLE,
}
for _, exprs := range fk.Values {
var row sqltypes.Row
var values []*querypb.Value
for _, expr := range exprs {
val, err := getValue(expr, bindVars)
if err != nil {
return nil, vterrors.Wrapf(err, "unable to get value for the expression %v", expr)
}
row = append(row, val)
values = append(values, sqltypes.ValueToProto(val))
}
if exists, err := pt.exists(row); err != nil {
return nil, err
} else if !exists {
newBv.Values = append(newBv.Values, &querypb.Value{Type: querypb.Type_TUPLE, Values: values})
}
}
distinctValues := len(newBv.Values)
qr, err := vcursor.ExecutePrimitive(ctx, fk.Exec, map[string]*querypb.BindVariable{fk.BvName: newBv}, wantfields)
for _, v := range f.Verify {
qr, err := vcursor.ExecutePrimitive(ctx, v.Exec, bindVars, wantfields)
if err != nil {
return nil, err
}
if distinctValues != len(qr.Rows) {
return nil, vterrors.NewErrorf(vtrpcpb.Code_FAILED_PRECONDITION, vterrors.NoReferencedRow2, "Cannot add or update a child row: a foreign key constraint fails")
if len(qr.Rows) > 0 {
return nil, getError(v.Typ)
}
}
return vcursor.ExecutePrimitive(ctx, f.Exec, bindVars, wantfields)
}

// TryStreamExecute implements the Primitive interface
func (f *FkVerify) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
for _, fk := range f.Verify {
pt := newProbeTable(fk.Cols)
newBv := &querypb.BindVariable{
Type: querypb.Type_TUPLE,
}
for _, exprs := range fk.Values {
var row sqltypes.Row
var values []*querypb.Value
for _, expr := range exprs {
val, err := getValue(expr, bindVars)
if err != nil {
return vterrors.Wrapf(err, "unable to get value for the expression %v", expr)
}
row = append(row, val)
values = append(values, sqltypes.ValueToProto(val))
for _, v := range f.Verify {
err := vcursor.StreamExecutePrimitive(ctx, v.Exec, bindVars, wantfields, func(qr *sqltypes.Result) error {
if len(qr.Rows) > 0 {
return getError(v.Typ)
}
if exists, err := pt.exists(row); err != nil {
return err
} else if !exists {
newBv.Values = append(newBv.Values, &querypb.Value{Type: querypb.Type_TUPLE, Values: values})
}
}
distinctValues := len(newBv.Values)

seenRows := 0
err := vcursor.StreamExecutePrimitive(ctx, fk.Exec, map[string]*querypb.BindVariable{fk.BvName: newBv}, wantfields, func(qr *sqltypes.Result) error {
seenRows += len(qr.Rows)
return nil
})
if err != nil {
return err
}

if distinctValues != seenRows {
return vterrors.NewErrorf(vtrpcpb.Code_FAILED_PRECONDITION, vterrors.NoReferencedRow2, "Cannot add or update a child row: a foreign key constraint fails")
}
}
return vcursor.StreamExecutePrimitive(ctx, f.Exec, bindVars, wantfields, callback)
}
Expand All @@ -147,17 +101,15 @@ func (f *FkVerify) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVa
func (f *FkVerify) Inputs() ([]Primitive, []map[string]any) {
var inputs []Primitive
var inputsMap []map[string]any
for idx, parent := range f.Verify {
for idx, v := range f.Verify {
inputsMap = append(inputsMap, map[string]any{
inputName: fmt.Sprintf("VerifyParent-%d", idx+1),
"BvName": parent.BvName,
"Cols": parent.Cols,
inputName: fmt.Sprintf("%s-%d", v.Typ, idx+1),
})
inputs = append(inputs, parent.Exec)
inputs = append(inputs, v.Exec)
}
inputs = append(inputs, f.Exec)
inputsMap = append(inputsMap, map[string]any{
inputName: "Child",
inputName: "PostVerify",
})
return inputs, inputsMap

Expand All @@ -169,24 +121,9 @@ func (f *FkVerify) description() PrimitiveDescription {

var _ Primitive = (*FkVerify)(nil)

func getValue(expr sqlparser.Expr, bindVars map[string]*querypb.BindVariable) (sqltypes.Value, error) {
switch e := expr.(type) {
case *sqlparser.Literal:
return sqlparser.LiteralToValue(e)
case sqlparser.BoolVal:
b := int32(0)
if e {
b = 1
}
return sqltypes.NewInt32(b), nil
case *sqlparser.NullVal:
return sqltypes.NULL, nil
case *sqlparser.Argument:
bv, exists := bindVars[e.Name]
if !exists {
return sqltypes.Value{}, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] bind variable %s missing", e.Name)
}
return sqltypes.BindVariableToValue(bv)
func getError(typ string) error {
if typ == ParentVerify {
return vterrors.NewErrorf(vtrpcpb.Code_FAILED_PRECONDITION, vterrors.NoReferencedRow2, "Cannot add or update a child row: a foreign key constraint fails")
}
return sqltypes.Value{}, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] unexpected expression type: %T", expr)
return vterrors.NewErrorf(vtrpcpb.Code_FAILED_PRECONDITION, vterrors.RowIsReferenced2, "Cannot delete or update a parent row: a foreign key constraint fails")
}
Loading