Repository: ariga/atlas
Pull request #3103 (2024-09-06 11:36:09)

sql/schema: detach excluded objects from their references


Patch:
diff --git a/sql/schema/dsl.go b/sql/schema/dsl.go
index 28a98a66..d18ec35c 100644
--- a/sql/schema/dsl.go
+++ b/sql/schema/dsl.go
@@ -274,6 +274,21 @@ func addRefs(dependent Object, refs []Object) {
 	}
 }
 
+// DepRemover wraps the RemoveDep method. Objects that implemented this method
+// allow dependent objects to remove themselves from their references.
+type DepRemover interface {
+	RemoveDep(Object)
+}
+
+// removeObj removes the given object from the list of objects.
+func removeObj(objs []Object, o Object) []Object {
+	i := slices.Index(objs, o)
+	if i == -1 {
+		return objs
+	}
+	return append(objs[:i:i], objs[i+1:]...)
+}
+
 // sortRefs maintains consistent dependents list.
 func sortRefs(refs []Object) {
 	slices.SortFunc(refs, func(a, b Object) int {
@@ -304,6 +319,11 @@ func (t *Table) AddRefs(refs ...Object) {
 	sortRefs(t.Refs)
 }
 
+// RemoveDep removes the given object from the table dependencies.
+func (t *Table) RemoveDep(o Object) {
+	t.Deps = removeObj(t.Deps, o)
+}
+
 // NewView creates a new View.
 func NewView(name, def string) *View {
 	return &View{Name: name, Def: def}
@@ -347,6 +367,11 @@ func (v *View) AddDeps(objs ...Object) *View {
 	return v
 }
 
+// RemoveDep removes the given object from the view dependencies.
+func (v *View) RemoveDep(o Object) {
+	v.Deps = removeObj(v.Deps, o)
+}
+
 // AddRefs adds references to the view.
 func (v *View) AddRefs(refs ...Object) {
 	v.Refs = append(v.Refs, refs...)
@@ -908,6 +933,11 @@ func (f *Func) AddDeps(objs ...Object) *Func {
 	return f
 }
 
+// RemoveDep removes the given object from the function dependencies.
+func (f *Func) RemoveDep(o Object) {
+	f.Deps = removeObj(f.Deps, o)
+}
+
 // AddRefs adds references to the function.
 func (f *Func) AddRefs(refs ...Object) {
 	f.Refs = append(f.Refs, refs...)
@@ -921,6 +951,11 @@ func (p *Proc) AddDeps(objs ...Object) *Proc {
 	return p
 }
 
+// RemoveDep removes the given object from the procedure dependencies.
+func (p *Proc) RemoveDep(o Object) {
+	p.Deps = removeObj(p.Deps, o)
+}
+
 // AddRefs adds references to the procedure.
 func (p *Proc) AddRefs(refs ...Object) {
 	p.Refs = append(p.Refs, refs...)
diff --git a/sql/schema/exclude.go b/sql/schema/exclude.go
index 6cd951c5..b7caf608 100644
--- a/sql/schema/exclude.go
+++ b/sql/schema/exclude.go
@@ -135,6 +135,7 @@ func excludeS(s *Schema, glob []string) error {
 				// In case there is a match, and it is
 				// a single glob we exclude this table.
 				if len(glob) == 1 {
+					detachObject(t, t.Refs)
 					continue
 				}
 				if err := excludeT(t, glob[1]); err != nil {
@@ -155,6 +156,7 @@ func excludeS(s *Schema, glob []string) error {
 			}
 			if match {
 				if len(glob) == 1 {
+					detachObject(v, v.Refs)
 					continue
 				}
 				if err := excludeV(v, glob[1]); err != nil {
@@ -168,7 +170,11 @@ func excludeS(s *Schema, glob []string) error {
 	if globF, exclude := excludeType(typeFn, glob[0]); exclude {
 		var err error
 		s.Funcs, err = filter(s.Funcs, func(f *Func) (bool, error) {
-			return filepath.Match(globF, f.Name)
+			if match, err := filepath.Match(globF, f.Name); !match || err != nil {
+				return false, err
+			}
+			detachObject(f, f.Refs)
+			return true, nil
 		})
 		if err != nil {
 			return err
@@ -177,7 +183,11 @@ func excludeS(s *Schema, glob []string) error {
 	if globP, exclude := excludeType(typePr, glob[0]); exclude {
 		var err error
 		s.Procs, err = filter(s.Procs, func(p *Proc) (bool, error) {
-			return filepath.Match(globP, p.Name)
+			if match, err := filepath.Match(globP, p.Name); !match || err != nil {
+				return false, err
+			}
+			detachObject(p, p.Refs)
+			return true, nil
 		})
 		if err != nil {
 			return err
@@ -260,7 +270,6 @@ func excludeV(v *View, pattern string) (err error) {
 }
 
 const (
-	typeE  = "enum"
 	typeV  = "view"
 	typeT  = "table"
 	typeC  = "column"
@@ -303,3 +312,12 @@ func filter[T any](s []T, f func(T) (bool, error)) ([]T, error) {
 	}
 	return r, nil
 }
+
+// detach the given object from all its references.
+func detachObject(o Object, refs []Object) {
+	for _, r := range refs {
+		if d, ok := r.(DepRemover); ok {
+			d.RemoveDep(o)
+		}
+	}
+}


Test patch:
diff --git a/sql/schema/exclude_test.go b/sql/schema/exclude_test.go
index 7f516310..7992b62e 100644
--- a/sql/schema/exclude_test.go
+++ b/sql/schema/exclude_test.go
@@ -130,6 +130,30 @@ func TestExcludeRealm_Enums(t *testing.T) {
 	require.Empty(t, r.Schemas[2].Objects)
 }
 
+func TestExcludeRealm_DependsOn(t *testing.T) {
+	s1, s2 := New("s1"), New("s2")
+	s1.AddTables(NewTable("t1"), NewTable("t2"))
+	s2.AddTables(NewTable("t1"), NewTable("t2"))
+	s1.AddViews(NewView("v1", "SELECT * FROM t1").AddDeps(s1.Tables[0], s2.Tables[1]))
+	r, err := ExcludeRealm(NewRealm(s1, s2), []string{"s1.t1"})
+	require.NoError(t, err)
+	require.Len(t, r.Schemas[0].Views[0].Deps, 1)
+	require.Equal(t, s2.Tables[1], r.Schemas[0].Views[0].Deps[0])
+
+	f1 := &Func{Name: "f1"}
+	s1.AddFuncs(f1)
+	s1.Views[0].AddDeps(f1)
+	r, err = ExcludeRealm(NewRealm(s1, s2), []string{"s1.f1", "s1.t2"})
+	require.NoError(t, err)
+	require.Empty(t, r.Schemas[0].Funcs)
+	require.Empty(t, r.Schemas[0].Tables)
+	require.Len(t, r.Schemas[0].Views[0].Deps, 1)
+
+	r, err = ExcludeRealm(NewRealm(s1, s2), []string{"s2.t2"})
+	require.NoError(t, err)
+	require.Empty(t, r.Schemas[0].Views[0].Deps)
+}
+
 type (
 	domain struct {
 		Object