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

schemadiff: rich error for unmet view dependencies #10940

Merged
merged 3 commits into from
Aug 15, 2022
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
2 changes: 1 addition & 1 deletion go/vt/schemadiff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ func TestDiffSchemas(t *testing.T) {
name: "create view: unresolved dependencies",
from: "create table t(id int)",
to: "create table t(id int); create view v1 as select id from t2",
expectError: ErrViewDependencyUnresolved.Error(),
expectError: (&ApplyViewNotFoundError{View: "v1"}).Error(),
},
{
name: "convert table to view",
Expand Down
9 changes: 8 additions & 1 deletion go/vt/schemadiff/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ var (
ErrUnexpectedTableSpec = errors.New("unexpected table spec")
ErrExpectedCreateTable = errors.New("expected a CREATE TABLE statement")
ErrExpectedCreateView = errors.New("expected a CREATE VIEW statement")
ErrViewDependencyUnresolved = errors.New("views have unresolved/loop dependencies")
)

type UnsupportedEntityError struct {
Expand Down Expand Up @@ -239,3 +238,11 @@ func (e *InvalidColumnInForeignKeyConstraintError) Error() string {
return fmt.Sprintf("invalid column %s referenced by foreign key constraint %s in table %s",
sqlescape.EscapeID(e.Column), sqlescape.EscapeID(e.Constraint), sqlescape.EscapeID(e.Table))
}

type ViewDependencyUnresolvedError struct {
View string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also here have a reference to the entity it tries to refer to? Like if a another table / view is missing, we'd also know the name here as well?

We could then further improve the error to "view %s references unknown entity %s" or something along those lines?

And maybe different from having a loop and have a separate error type for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the way we detect these dependencies is by creating a connected graph, and then looking at "what's outside the graph". We don't explicitly then check which tables/views are referenced by the left-out views. We could definitely do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbussink let's iterate on that in a followup PR though, if that's OK

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can improve on a further iteration.

}

func (e *ViewDependencyUnresolvedError) Error() string {
return fmt.Sprintf("view %s has unresolved/loop dependencies", sqlescape.EscapeID(e.View))
}
8 changes: 7 additions & 1 deletion go/vt/schemadiff/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,13 @@ func (s *Schema) normalize() error {
// We have leftover views. This can happen if the schema definition is invalid:
// - a view depends on a nonexistent table
// - two views have a circular dependency
return ErrViewDependencyUnresolved
for _, v := range s.views {
if _, ok := dependencyLevels[v.Name()]; !ok {
// We _know_ that in this iteration, at least one view is found unassigned a dependency level.
// We return the first one.
return &ApplyViewNotFoundError{View: v.ViewName.Name.String()}
}
}
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions go/vt/schemadiff/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestNewSchemaFromQueriesUnresolved(t *testing.T) {
)
_, err := NewSchemaFromQueries(queries)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrViewDependencyUnresolved)
assert.EqualError(t, err, (&ApplyViewNotFoundError{View: "v7"}).Error())
}

func TestNewSchemaFromQueriesUnresolvedAlias(t *testing.T) {
Expand All @@ -120,7 +120,7 @@ func TestNewSchemaFromQueriesUnresolvedAlias(t *testing.T) {
)
_, err := NewSchemaFromQueries(queries)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrViewDependencyUnresolved)
assert.EqualError(t, err, (&ApplyViewNotFoundError{View: "v7"}).Error())
}

func TestNewSchemaFromQueriesLoop(t *testing.T) {
Expand All @@ -131,7 +131,7 @@ func TestNewSchemaFromQueriesLoop(t *testing.T) {
)
_, err := NewSchemaFromQueries(queries)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrViewDependencyUnresolved)
assert.EqualError(t, err, (&ApplyViewNotFoundError{View: "v7"}).Error())
}

func TestToSQL(t *testing.T) {
Expand Down