Skip to content

Commit

Permalink
feat: fix code to return proper error instead of panic
Browse files Browse the repository at this point in the history
Signed-off-by: Manan Gupta <[email protected]>
  • Loading branch information
GuptaManan100 committed Jan 24, 2024
1 parent c4bd538 commit 66bc8ea
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 10 deletions.
5 changes: 5 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -3043,5 +3043,10 @@
"unsharded_fk_allow.u_tbl3"
]
}
},
{
"comment": "Unknown update column in foreign keys",
"query": "update tbl_auth set unknown_col = 'verified' where id = 1",
"plan": "column 'unknown_col' not found in table 'tbl_auth'"
}
]
2 changes: 1 addition & 1 deletion go/vt/vtgate/planbuilder/testdata/onecase.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"comment": "Add your test case here for debugging and run go test -run=One.",
"query": "update tbl_auth set unknown_col = 'verified' where id = 1",
"query": "",
"plan": {

}
Expand Down
29 changes: 21 additions & 8 deletions go/vt/vtgate/semantics/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,25 +350,24 @@ func (a *analyzer) getInvolvedForeignKeys(statement sqlparser.Statement, fkCheck
}
// If only a certain set of columns are being updated, then there might be some child foreign keys that don't need any consideration since their columns aren't being updated.
// So, we filter these child foreign keys out. We can't filter any parent foreign keys because the statement will INSERT a row too, which requires validating all the parent foreign keys.
updatedChildFks, _, childFkToUpdExprs := a.filterForeignKeysUsingUpdateExpressions(allChildFks, nil, sqlparser.UpdateExprs(stmt.OnDup))
return updatedChildFks, allParentFKs, childFkToUpdExprs, nil
updatedChildFks, _, childFkToUpdExprs, err := a.filterForeignKeysUsingUpdateExpressions(allChildFks, nil, sqlparser.UpdateExprs(stmt.OnDup))
return updatedChildFks, allParentFKs, childFkToUpdExprs, err
case *sqlparser.Update:
// For UPDATE queries we get all the parent and child foreign keys, but we can filter some of them out if the columns that they consist off aren't being updated or are set to NULLs.
allChildFks, allParentFks, err := a.getAllManagedForeignKeys()
if err != nil {
return nil, nil, nil, err
}
childFks, parentFks, childFkToUpdExprs := a.filterForeignKeysUsingUpdateExpressions(allChildFks, allParentFks, stmt.Exprs)
return childFks, parentFks, childFkToUpdExprs, nil
return a.filterForeignKeysUsingUpdateExpressions(allChildFks, allParentFks, stmt.Exprs)
default:
return nil, nil, nil, nil
}
}

// filterForeignKeysUsingUpdateExpressions filters the child and parent foreign key constraints that don't require any validations/cascades given the updated expressions.
func (a *analyzer) filterForeignKeysUsingUpdateExpressions(allChildFks map[TableSet][]vindexes.ChildFKInfo, allParentFks map[TableSet][]vindexes.ParentFKInfo, updExprs sqlparser.UpdateExprs) (map[TableSet][]vindexes.ChildFKInfo, map[TableSet][]vindexes.ParentFKInfo, map[string]sqlparser.UpdateExprs) {
func (a *analyzer) filterForeignKeysUsingUpdateExpressions(allChildFks map[TableSet][]vindexes.ChildFKInfo, allParentFks map[TableSet][]vindexes.ParentFKInfo, updExprs sqlparser.UpdateExprs) (map[TableSet][]vindexes.ChildFKInfo, map[TableSet][]vindexes.ParentFKInfo, map[string]sqlparser.UpdateExprs, error) {
if len(allChildFks) == 0 && len(allParentFks) == 0 {
return nil, nil, nil
return nil, nil, nil, nil
}

pFksRequired := make(map[TableSet][]bool, len(allParentFks))
Expand All @@ -390,7 +389,10 @@ func (a *analyzer) filterForeignKeysUsingUpdateExpressions(allChildFks map[Table
for _, updateExpr := range updExprs {
deps := a.binder.direct.dependencies(updateExpr.Name)
if deps.NumberOfTables() != 1 {
panic("expected to have single table dependency")
// If we don't get exactly one table for the given update expression, we would have definitely run into an error
// during the binder phase that we would have stored. We should return that error, since we can't safely proceed with
// foreign key related changes without having all the information.
return nil, nil, nil, a.getError()
}
updExprToTableSet[updateExpr.Name] = deps
// Get all the child and parent foreign keys for the given table that the update expression belongs to.
Expand Down Expand Up @@ -457,7 +459,18 @@ func (a *analyzer) filterForeignKeysUsingUpdateExpressions(allChildFks map[Table
}
cFksNeedsHandling[ts] = cFKNeeded
}
return cFksNeedsHandling, pFksNeedsHandling, childFKToUpdExprs
return cFksNeedsHandling, pFksNeedsHandling, childFKToUpdExprs, nil
}

// getError gets the error stored in the analyzer during previous phases.
func (a *analyzer) getError() error {
if a.projErr != nil {
return a.projErr
}
if a.unshardedErr != nil {
return a.unshardedErr

Check warning on line 471 in go/vt/vtgate/semantics/analyzer.go

View check run for this annotation

Codecov / codecov/patch

go/vt/vtgate/semantics/analyzer.go#L470-L471

Added lines #L470 - L471 were not covered by tests
}
return a.err
}

// getAllManagedForeignKeys gets all the foreign keys for the query we are analyzing that Vitess is responsible for managing.
Expand Down
21 changes: 20 additions & 1 deletion go/vt/vtgate/semantics/analyzer_fk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ func TestFilterForeignKeysUsingUpdateExpressions(t *testing.T) {
cold: SingleTableSet(1),
},
},
unshardedErr: fmt.Errorf("ambiguous test error"),
tables: &tableCollector{
Tables: []TableInfo{
tbl["t4"],
Expand All @@ -261,6 +262,7 @@ func TestFilterForeignKeysUsingUpdateExpressions(t *testing.T) {
updExprs sqlparser.UpdateExprs
childFksWanted map[TableSet][]vindexes.ChildFKInfo
parentFksWanted map[TableSet][]vindexes.ParentFKInfo
errWanted string
}{
{
name: "Child Foreign Keys Filtering",
Expand Down Expand Up @@ -301,13 +303,30 @@ func TestFilterForeignKeysUsingUpdateExpressions(t *testing.T) {
pkInfo(parentTbl, []string{"pcolc", "pcolx"}, []string{"colc", "colx"}),
},
},
}, {
name: "Unknown column",
analyzer: a,
allParentFks: map[TableSet][]vindexes.ParentFKInfo{
SingleTableSet(0): tbl["t4"].(*RealTable).Table.ParentForeignKeys,
SingleTableSet(1): tbl["t5"].(*RealTable).Table.ParentForeignKeys,
},
allChildFks: nil,
updExprs: sqlparser.UpdateExprs{
&sqlparser.UpdateExpr{Name: sqlparser.NewColName("unknownCol"), Expr: sqlparser.NewIntLiteral("1")},
},
errWanted: "ambiguous test error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
childFks, parentFks, _ := tt.analyzer.filterForeignKeysUsingUpdateExpressions(tt.allChildFks, tt.allParentFks, tt.updExprs)
childFks, parentFks, _, err := tt.analyzer.filterForeignKeysUsingUpdateExpressions(tt.allChildFks, tt.allParentFks, tt.updExprs)
require.EqualValues(t, tt.childFksWanted, childFks)
require.EqualValues(t, tt.parentFksWanted, parentFks)
if tt.errWanted != "" {
require.EqualError(t, err, tt.errWanted)
} else {
require.NoError(t, err)
}
})
}
}
Expand Down

0 comments on commit 66bc8ea

Please sign in to comment.