Skip to content

Commit

Permalink
Merge branch 'planetscale-fk-verify-update-planning'
Browse files Browse the repository at this point in the history
Signed-off-by: Harshit Gangal <harshit@planetscale.com>
  • Loading branch information
harshit-gangal committed Sep 8, 2023
2 parents 868a1d6 + c301cdc commit ee77f54
Show file tree
Hide file tree
Showing 24 changed files with 1,150 additions and 386 deletions.
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

0 comments on commit ee77f54

Please sign in to comment.