Repository: ariga/atlas
Pull request #2403 (2023-12-25 16:15:27)

sql: add triggers to schema inspect/exclude


Patch:
diff --git a/sql/internal/sqlx/dev.go b/sql/internal/sqlx/dev.go
index 675f84ce..694edac2 100644
--- a/sql/internal/sqlx/dev.go
+++ b/sql/internal/sqlx/dev.go
@@ -59,12 +59,16 @@ func (d *DevDriver) NormalizeRealm(ctx context.Context, r *schema.Realm) (nr *sc
 			},
 		})
 		for _, t := range s.Tables {
-			changes = append(changes, &schema.AddTable{
-				T: t,
-			})
+			changes = append(changes, &schema.AddTable{T: t})
+			for _, r := range t.Triggers {
+				changes = append(changes, &schema.AddTrigger{T: r})
+			}
 		}
 		for _, v := range s.Views {
 			changes = append(changes, &schema.AddView{V: v})
+			for _, r := range v.Triggers {
+				changes = append(changes, &schema.AddTrigger{T: r})
+			}
 		}
 		for _, o := range s.Objects {
 			changes = append(changes, &schema.AddObject{O: o})
@@ -123,14 +127,14 @@ func (d *DevDriver) NormalizeSchema(ctx context.Context, s *schema.Schema) (*sch
 				e.Schema = s
 			}
 		}
-		changes = append(changes, &schema.AddTable{T: t})
+		changes = append(changes, addTableChange(t)...)
 	}
 	for _, v := range s.Views {
 		// If objects are not strongly connected.
 		if v.Schema != s {
 			v.Schema = s
 		}
-		changes = append(changes, &schema.AddView{V: v})
+		changes = append(changes, addViewChange(v)...)
 	}
 	for _, o := range s.Objects {
 		if d.PatchObject != nil {
diff --git a/sql/internal/sqlx/diff.go b/sql/internal/sqlx/diff.go
index 7f7b5770..663bac81 100644
--- a/sql/internal/sqlx/diff.go
+++ b/sql/internal/sqlx/diff.go
@@ -150,16 +150,10 @@ func (d *Diff) RealmDiff(from, to *schema.Realm, options ...schema.DiffOption) (
 			changes = opts.AddOrSkip(changes, &schema.AddProc{P: p})
 		}
 		for _, t := range s1.Tables {
-			changes = opts.AddOrSkip(changes, &schema.AddTable{T: t})
-			for _, r := range t.Triggers {
-				changes = opts.AddOrSkip(changes, &schema.AddTrigger{T: r})
-			}
+			changes = opts.AddOrSkip(changes, addTableChange(t)...)
 		}
 		for _, v := range s1.Views {
-			changes = opts.AddOrSkip(changes, &schema.AddView{V: v})
-			for _, r := range v.Triggers {
-				changes = opts.AddOrSkip(changes, &schema.AddTrigger{T: r})
-			}
+			changes = opts.AddOrSkip(changes, addViewChange(v)...)
 		}
 	}
 	return d.mayAnnotate(changes, opts)
@@ -223,7 +217,7 @@ func (d *Diff) schemaDiff(from, to *schema.Schema, opts *schema.DiffOptions) ([]
 	for _, t1 := range to.Tables {
 		switch _, err := d.findTable(from, t1.Name); {
 		case schema.IsNotExistError(err):
-			changes = opts.AddOrSkip(changes, &schema.AddTable{T: t1})
+			changes = opts.AddOrSkip(changes, addTableChange(t1)...)
 		case err != nil:
 			return nil, err
 		}
@@ -249,7 +243,7 @@ func (d *Diff) schemaDiff(from, to *schema.Schema, opts *schema.DiffOptions) ([]
 	// Add views.
 	for _, v1 := range to.Views {
 		if _, ok := findView(from, v1); !ok {
-			changes = opts.AddOrSkip(changes, &schema.AddView{V: v1})
+			changes = opts.AddOrSkip(changes, addViewChange(v1)...)
 		}
 	}
 	// Add, drop and modify functions and procedures.
@@ -370,6 +364,26 @@ func (d *Diff) mayAnnotate(changes []schema.Change, opts *schema.DiffOptions) ([
 	return changes, nil
 }
 
+// addTableChange returns the changeset for creating the table.
+func addTableChange(t *schema.Table) []schema.Change {
+	changes := make([]schema.Change, 0, 1+len(t.Triggers))
+	changes = append(changes, &schema.AddTable{T: t})
+	for _, r := range t.Triggers {
+		changes = append(changes, &schema.AddTrigger{T: r})
+	}
+	return changes
+}
+
+// addViewChange returns the changeset for creating the view.
+func addViewChange(v *schema.View) []schema.Change {
+	changes := make([]schema.Change, 0, 1+len(v.Triggers))
+	changes = append(changes, &schema.AddView{V: v})
+	for _, r := range v.Triggers {
+		changes = append(changes, &schema.AddTrigger{T: r})
+	}
+	return changes
+}
+
 // pkDiff returns the schema changes (if any) for migrating table
 // primary-key from current state to the desired state.
 func (d *Diff) pkDiff(from, to *schema.Table, opts *schema.DiffOptions) (changes []schema.Change) {
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 6a974f6d..5e312890 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -224,7 +224,8 @@ func ValuesEqual(v1, v2 []string) bool {
 // ModeInspectSchema returns the InspectMode or its default.
 func ModeInspectSchema(o *schema.InspectOptions) schema.InspectMode {
 	if o == nil || o.Mode == 0 {
-		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs | schema.InspectTypes | schema.InspectObjects
+		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs |
+			schema.InspectTypes | schema.InspectObjects | schema.InspectTriggers
 	}
 	return o.Mode
 }
@@ -232,7 +233,8 @@ func ModeInspectSchema(o *schema.InspectOptions) schema.InspectMode {
 // ModeInspectRealm returns the InspectMode or its default.
 func ModeInspectRealm(o *schema.InspectRealmOption) schema.InspectMode {
 	if o == nil || o.Mode == 0 {
-		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs | schema.InspectTypes | schema.InspectObjects
+		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs |
+			schema.InspectTypes | schema.InspectObjects | schema.InspectTriggers
 	}
 	return o.Mode
 }
@@ -309,7 +311,7 @@ func (b *Builder) Func(f *schema.Func) *Builder {
 func (b *Builder) FuncCall(f *schema.Func, args ...string) *Builder {
 	b.Func(f).rewriteLastByte('(')
 	b.MapComma(args, func(i int, b *Builder) {
-		b.P(args[i])
+		b.WriteString(args[i])
 	})
 	b.WriteByte(')')
 	return b
@@ -325,7 +327,7 @@ func (b *Builder) Proc(p *schema.Proc) *Builder {
 func (b *Builder) ProcCall(p *schema.Proc, args ...string) *Builder {
 	b.Proc(p).rewriteLastByte('(')
 	b.MapComma(args, func(i int, b *Builder) {
-		b.P(args[i])
+		b.WriteString(args[i])
 	})
 	b.WriteByte(')')
 	return b
diff --git a/sql/postgres/driver_oss.go b/sql/postgres/driver_oss.go
index 9332240d..a8c712ab 100644
--- a/sql/postgres/driver_oss.go
+++ b/sql/postgres/driver_oss.go
@@ -50,6 +50,10 @@ func (*inspect) inspectSequences(context.Context, *schema.Realm, *schema.Inspect
 	return nil // unimplemented.
 }
 
+func (*inspect) inspectTriggers(context.Context, *schema.Realm, *schema.InspectOptions) error {
+	return nil // unimplemented.
+}
+
 func (*inspect) inspectDeps(context.Context, *schema.Realm, *schema.InspectOptions) error {
 	return nil // unimplemented.
 }
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index 94bf5dff..972eff26 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -68,6 +68,11 @@ func (i *inspect) InspectRealm(ctx context.Context, opts *schema.InspectRealmOpt
 				return nil, err
 			}
 		}
+		if mode.Is(schema.InspectTriggers) {
+			if err := i.inspectTriggers(ctx, r, nil); err != nil {
+				return nil, err
+			}
+		}
 		if err := i.inspectDeps(ctx, r, nil); err != nil {
 			return nil, err
 		}
@@ -126,6 +131,11 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 			return nil, err
 		}
 	}
+	if mode.Is(schema.InspectTriggers) {
+		if err := i.inspectTriggers(ctx, r, nil); err != nil {
+			return nil, err
+		}
+	}
 	if err := i.inspectDeps(ctx, r, opts); err != nil {
 		return nil, err
 	}
diff --git a/sql/schema/exclude.go b/sql/schema/exclude.go
index e9b8844e..983de581 100644
--- a/sql/schema/exclude.go
+++ b/sql/schema/exclude.go
@@ -203,6 +203,11 @@ func excludeT(t *Table, pattern string) (err error) {
 			return filepath.Match(p, fk.Symbol)
 		})
 	}
+	if p, exclude := excludeType(typeG, pattern); exclude {
+		t.Triggers, err = filter(t.Triggers, func(t *Trigger) (bool, error) {
+			return filepath.Match(p, t.Name)
+		})
+	}
 	if p, exclude := excludeType(typeK, pattern); exclude {
 		t.Attrs, err = filter(t.Attrs, func(a Attr) (bool, error) {
 			c, ok := a.(*Check)
@@ -219,9 +224,9 @@ func excludeT(t *Table, pattern string) (err error) {
 	return
 }
 
-func excludeV(t *View, pattern string) (err error) {
+func excludeV(v *View, pattern string) (err error) {
 	if p, exclude := excludeType(typeC, pattern); exclude {
-		t.Columns, err = filter(t.Columns, func(c *Column) (bool, error) {
+		v.Columns, err = filter(v.Columns, func(c *Column) (bool, error) {
 			match, err := filepath.Match(p, c.Name)
 			if !match || err != nil {
 				return false, err
@@ -229,6 +234,11 @@ func excludeV(t *View, pattern string) (err error) {
 			return true, nil
 		})
 	}
+	if p, exclude := excludeType(typeG, pattern); exclude {
+		v.Triggers, err = filter(v.Triggers, func(t *Trigger) (bool, error) {
+			return filepath.Match(p, t.Name)
+		})
+	}
 	return
 }
 
@@ -239,6 +249,7 @@ const (
 	typeC  = "column"
 	typeI  = "index"
 	typeF  = "fk"
+	typeG  = "trigger"
 	typeK  = "check"
 	typeFn = "function"
 	typePr = "procedure"
diff --git a/sql/schema/inspect.go b/sql/schema/inspect.go
index b920dbe6..84b736d8 100644
--- a/sql/schema/inspect.go
+++ b/sql/schema/inspect.go
@@ -56,6 +56,9 @@ const (
 	// InspectObjects enables inspection of database specific
 	// objects like sequences and extensions.
 	InspectObjects
+
+	// InspectTriggers enables schema triggers inspection.
+	InspectTriggers
 )
 
 // Is reports whether the given mode is enabled.


Test patch:
diff --git a/sql/schema/exclude_test.go b/sql/schema/exclude_test.go
index cae7a2df..cb5006dc 100644
--- a/sql/schema/exclude_test.go
+++ b/sql/schema/exclude_test.go
@@ -224,17 +224,23 @@ func TestExcludeRealm_Selector(t *testing.T) {
 		AddViews(
 			NewView("v1", "SELECT * FROM t1").AddColumns(NewColumn("c1")),
 		)
+	r.Schemas[0].Views[0].Triggers = []*Trigger{{Name: "g1"}}
+	r.Schemas[0].Tables[0].Triggers = []*Trigger{{Name: "g1"}}
 	r, err = ExcludeRealm(r, []string{"*.*[type=table].*1[type=column|check]"})
 	require.NoError(t, err)
 	require.Empty(t, r.Schemas[0].Tables[0].Attrs)
 	require.Empty(t, r.Schemas[0].Tables[0].Columns)
 	require.Len(t, r.Schemas[0].Tables[0].Indexes, 1)
+	require.Len(t, r.Schemas[0].Tables[0].Triggers, 1)
 	require.Len(t, r.Schemas[0].Views[0].Columns, 1)
+	require.Len(t, r.Schemas[0].Views[0].Triggers, 1)
 
-	r, err = ExcludeRealm(r, []string{"*.*[type=table|view].*1[type=column|check|index]"})
+	r, err = ExcludeRealm(r, []string{"*.*[type=table|view].*1[type=column|check|index|trigger]"})
 	require.NoError(t, err)
 	require.Empty(t, r.Schemas[0].Tables[0].Indexes)
 	require.Empty(t, r.Schemas[0].Views[0].Columns)
+	require.Empty(t, r.Schemas[0].Tables[0].Triggers)
+	require.Empty(t, r.Schemas[0].Views[0].Triggers)
 }
 
 func TestExcludeRealm_Checks(t *testing.T) {