Repository: ariga/atlas
Pull request #1871 (2023-07-17 18:49:22)

sql/internal/specutil: support marshaling qualified views


Patch:
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index fa095d3d..37c68b9d 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -481,16 +481,38 @@ func FromView(v *schema.View, colFn ViewColumnSpecFunc) (*sqlspec.View, error) {
 			schemahcl.StringAttr("as", as),
 		},
 	}
-	deps := make([]*schemahcl.Ref, 0, len(v.Deps))
+	var (
+		deps         = make([]*schemahcl.Ref, 0, len(v.Deps))
+		nameT, nameV = make(map[string]int), make(map[string]int)
+	)
+	// Qualify table/view names if there are
+	// multiple tables/views with the same name.
+	if v.Schema.Realm != nil {
+		for _, s := range v.Schema.Realm.Schemas {
+			for _, t := range s.Tables {
+				nameT[t.Name]++
+			}
+			for _, v := range s.Views {
+				nameV[v.Name]++
+			}
+		}
+	}
 	for _, d := range v.Deps {
+		path := make([]string, 0, 2)
 		switch d := d.(type) {
 		case *schema.Table:
+			if nameT[d.Name] > 1 {
+				path = append(path, d.Schema.Name)
+			}
 			deps = append(deps, schemahcl.BuildRef([]schemahcl.PathIndex{
-				{T: "table", V: []string{d.Name}},
+				{T: "table", V: append(path, d.Name)},
 			}))
 		case *schema.View:
+			if nameV[d.Name] > 1 {
+				path = append(path, d.Schema.Name)
+			}
 			deps = append(deps, schemahcl.BuildRef([]schemahcl.PathIndex{
-				{T: "view", V: []string{d.Name}},
+				{T: "view", V: append(path, d.Name)},
 			}))
 		}
 	}
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 5248e5b1..203df7e3 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -58,7 +58,10 @@ func Marshal(v any, marshaler schemahcl.Marshaler, convertFunc func(*schema.Sche
 			d.Views = append(d.Views, spec.Views...)
 			d.Schemas = append(d.Schemas, spec.Schema)
 		}
-		if err := QualifyDuplicates(d.Tables); err != nil {
+		if err := QualifyTables(d.Tables); err != nil {
+			return nil, err
+		}
+		if err := QualifyViews(d.Views); err != nil {
 			return nil, err
 		}
 		if err := QualifyReferences(d.Tables, s); err != nil {
@@ -70,24 +73,46 @@ func Marshal(v any, marshaler schemahcl.Marshaler, convertFunc func(*schema.Sche
 	return marshaler.MarshalSpec(d)
 }
 
-// QualifyDuplicates sets the Qualified field equal to the schema name in any tables
-// with duplicate names in the provided table specs.
-func QualifyDuplicates(tableSpecs []*sqlspec.Table) error {
-	seen := make(map[string]*sqlspec.Table, len(tableSpecs))
-	for _, tbl := range tableSpecs {
-		if s, ok := seen[tbl.Name]; ok {
+// QualifyTables sets the Qualifier field equal to the schema
+// name in any tables with duplicate names in the provided specs.
+func QualifyTables(specs []*sqlspec.Table) error {
+	seen := make(map[string]*sqlspec.Table, len(specs))
+	for _, t := range specs {
+		if s, ok := seen[t.Name]; ok {
+			schemaName, err := SchemaName(s.Schema)
+			if err != nil {
+				return err
+			}
+			s.Qualifier = schemaName
+			schemaName, err = SchemaName(t.Schema)
+			if err != nil {
+				return err
+			}
+			t.Qualifier = schemaName
+		}
+		seen[t.Name] = t
+	}
+	return nil
+}
+
+// QualifyViews sets the Qualifier field equal to the schema
+// name in any tables with duplicate names in the provided specs.
+func QualifyViews(specs []*sqlspec.View) error {
+	seen := make(map[string]*sqlspec.View, len(specs))
+	for _, v := range specs {
+		if s, ok := seen[v.Name]; ok {
 			schemaName, err := SchemaName(s.Schema)
 			if err != nil {
 				return err
 			}
 			s.Qualifier = schemaName
-			schemaName, err = SchemaName(tbl.Schema)
+			schemaName, err = SchemaName(v.Schema)
 			if err != nil {
 				return err
 			}
-			tbl.Qualifier = schemaName
+			v.Qualifier = schemaName
 		}
-		seen[tbl.Name] = tbl
+		seen[v.Name] = v
 	}
 	return nil
 }
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index c2d3af8c..551d52a6 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -112,7 +112,10 @@ func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 			d.Schemas = append(d.Schemas, doc.Schemas...)
 			d.Enums = append(d.Enums, doc.Enums...)
 		}
-		if err := specutil.QualifyDuplicates(d.Tables); err != nil {
+		if err := specutil.QualifyTables(d.Tables); err != nil {
+			return nil, err
+		}
+		if err := specutil.QualifyViews(d.Views); err != nil {
 			return nil, err
 		}
 		if err := specutil.QualifyReferences(d.Tables, s); err != nil {


Test patch:
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index 9e018df2..0d3f153b 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -1925,9 +1925,17 @@ func TestMarshalRealm(t *testing.T) {
 	// Reference is qualified with s1.
 	t5.AddForeignKeys(schema.NewForeignKey("oid2id2").AddColumns(t5.Columns[0]).SetRefTable(t2).AddRefColumns(t2.Columns[0]))
 
+	// Two views with the same name resided in different schemas.
+	v2 := schema.NewView("v2", "SELECT oid FROM s1.t2").
+		AddColumns(schema.NewIntColumn("oid", "int")).
+		AddDeps(t2)
+	v4 := schema.NewView("v2", "SELECT oid FROM s2.t2").
+		AddColumns(schema.NewIntColumn("oid", "int")).
+		AddDeps(t4)
+
 	r := schema.NewRealm(
-		schema.New("s1").AddTables(t1, t2),
-		schema.New("s2").AddTables(t3, t4, t5),
+		schema.New("s1").AddTables(t1, t2).AddViews(v2),
+		schema.New("s2").AddTables(t3, t4, t5).AddViews(v4),
 	)
 	got, err := MarshalHCL.MarshalSpec(r)
 	require.NoError(t, err)
@@ -1986,6 +1994,24 @@ table "t5" {
     ref_columns = [table.s1.t2.column.oid]
   }
 }
+view "s1" "v2" {
+  schema = schema.s1
+  column "oid" {
+    null = false
+    type = int
+  }
+  as         = "SELECT oid FROM s1.t2"
+  depends_on = [table.s1.t2]
+}
+view "s2" "v2" {
+  schema = schema.s2
+  column "oid" {
+    null = false
+    type = int
+  }
+  as         = "SELECT oid FROM s2.t2"
+  depends_on = [table.s2.t2]
+}
 schema "s1" {
 }
 schema "s2" {