Repository: ariga/atlas
Pull request #739 (2022-04-27 08:14:02)

sql: adding eval method to drivers


Patch:
diff --git a/schema/schemaspec/schemahcl/hcl.go b/schema/schemaspec/schemahcl/hcl.go
index d1652575..93c50649 100644
--- a/schema/schemaspec/schemahcl/hcl.go
+++ b/schema/schemaspec/schemahcl/hcl.go
@@ -38,8 +38,20 @@ type (
 	State struct {
 		config *Config
 	}
+	// Evaluator evaluates the Atlas DDL document with the given input and stores it in
+	// the provided object.
+	Evaluator interface {
+		Eval([]byte, interface{}, map[string]string) error
+	}
+	// EvalFunc is an adapter that allows the use of an ordinary function as an Evaluator.
+	EvalFunc func([]byte, interface{}, map[string]string) error
 )
 
+// Eval implements the Evaluator interface.
+func (f EvalFunc) Eval(data []byte, v interface{}, input map[string]string) error {
+	return f(data, v, input)
+}
+
 // MarshalSpec implements schemaspec.Marshaler for Atlas HCL documents.
 func (s *State) MarshalSpec(v interface{}) ([]byte, error) {
 	r := &schemaspec.Resource{}
@@ -54,8 +66,7 @@ func (s *State) UnmarshalSpec(data []byte, v interface{}) error {
 	return s.Eval(data, v, nil)
 }
 
-// Eval evaluates the Atlas DDL document with the given input and stores it in
-// the provided object.
+// Eval implements the Evaluator interface.
 func (s *State) Eval(data []byte, v interface{}, input map[string]string) error {
 	spec, err := s.eval(data, input)
 	if err != nil {
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 9c763ba8..292ecbd5 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -5,6 +5,7 @@ import (
 	"strconv"
 
 	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schema/schemaspec/schemahcl"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
 )
@@ -133,10 +134,10 @@ func QualifyDuplicates(tableSpecs []*sqlspec.Table) error {
 
 // Unmarshal unmarshals an Atlas DDL document using an unmarshaler into v. Unmarshal uses the
 // given convertTable function to convert a *sqlspec.Table into a *schema.Table.
-func Unmarshal(data []byte, unmarshaler schemaspec.Unmarshaler, v interface{},
+func Unmarshal(data []byte, evaluator schemahcl.Evaluator, v interface{}, input map[string]string,
 	convertTable ConvertTableFunc) error {
 	var d doc
-	if err := unmarshaler.UnmarshalSpec(data, &d); err != nil {
+	if err := evaluator.Eval(data, &d, input); err != nil {
 		return err
 	}
 	switch v := v.(type) {
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 5471bb3d..9ea71d4b 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -19,10 +19,10 @@ type doc struct {
 	Schemas []*sqlspec.Schema `spec:"schema"`
 }
 
-// UnmarshalSpec unmarshals an Atlas DDL document using an unmarshaler into v.
-func UnmarshalSpec(data []byte, unmarshaler schemaspec.Unmarshaler, v interface{}) error {
+// evalSpec evaluates an Atlas DDL document into v using the input.
+func evalSpec(data []byte, v interface{}, input map[string]string) error {
 	var d doc
-	if err := unmarshaler.UnmarshalSpec(data, &d); err != nil {
+	if err := hclState.Eval(data, &d, input); err != nil {
 		return err
 	}
 	switch v := v.(type) {
@@ -73,13 +73,15 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(bytes []byte, i interface{}) error {
-		return UnmarshalSpec(bytes, hclState, i)
+	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(data []byte, v interface{}) error {
+		return evalSpec(data, v, nil)
 	})
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
 	MarshalHCL = schemaspec.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
+	// EvalHCL implements the schemahcl.Evaluator interface.
+	EvalHCL = schemahcl.EvalFunc(evalSpec)
 )
 
 // convertTable converts a sqlspec.Table to a schema.Table. Table conversion is done without converting
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 8d5fb52b..38f9beb0 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -33,10 +33,10 @@ func init() {
 	schemaspec.Register("enum", &Enum{})
 }
 
-// UnmarshalSpec unmarshals an Atlas DDL document using an unmarshaler into v.
-func UnmarshalSpec(data []byte, unmarshaler schemaspec.Unmarshaler, v interface{}) error {
+// evalSpec evaluates an Atlas DDL document into v using the input.
+func evalSpec(data []byte, v interface{}, input map[string]string) error {
 	var d doc
-	if err := unmarshaler.UnmarshalSpec(data, &d); err != nil {
+	if err := hclState.Eval(data, &d, input); err != nil {
 		return err
 	}
 	switch v := v.(type) {
@@ -112,13 +112,15 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(bytes []byte, i interface{}) error {
-		return UnmarshalSpec(bytes, hclState, i)
+	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(data []byte, v interface{}) error {
+		return evalSpec(data, v, nil)
 	})
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
 	MarshalHCL = schemaspec.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
+	// EvalHCL implements the schemahcl.Evaluator interface.
+	EvalHCL = schemahcl.EvalFunc(evalSpec)
 )
 
 // convertTable converts a sqlspec.Table to a schema.Table. Table conversion is done without converting
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index 469b5ebb..16eb6c0f 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -13,9 +13,9 @@ import (
 	"ariga.io/atlas/sql/sqlspec"
 )
 
-// UnmarshalSpec unmarshals an Atlas DDL document using an unmarshaler into v.
-func UnmarshalSpec(data []byte, unmarshaler schemaspec.Unmarshaler, v interface{}) error {
-	return specutil.Unmarshal(data, unmarshaler, v, convertTable)
+// evalSpec evaluates an Atlas DDL document using an unmarshaler into v by using the input.
+func evalSpec(data []byte, v interface{}, input map[string]string) error {
+	return specutil.Unmarshal(data, hclState, v, input, convertTable)
 }
 
 // MarshalSpec marshals v into an Atlas DDL document using a schemaspec.Marshaler.
@@ -169,13 +169,15 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(bytes []byte, i interface{}) error {
-		return UnmarshalSpec(bytes, hclState, i)
+	UnmarshalHCL = schemaspec.UnmarshalerFunc(func(data []byte, v interface{}) error {
+		return evalSpec(data, v, nil)
 	})
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
 	MarshalHCL = schemaspec.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
+	// EvalHCL implements the schemahcl.Evaluator interface.
+	EvalHCL = schemahcl.EvalFunc(evalSpec)
 )
 
 // storedOrVirtual returns a STORED or VIRTUAL
diff --git a/sql/sqlspec/sqlspec.go b/sql/sqlspec/sqlspec.go
index 8741a5e1..faeba647 100644
--- a/sql/sqlspec/sqlspec.go
+++ b/sql/sqlspec/sqlspec.go
@@ -7,7 +7,7 @@ import (
 type (
 	// Schema holds a specification for a Schema.
 	Schema struct {
-		Name string `spec:",name"`
+		Name string `spec:"name,name"`
 		schemaspec.DefaultExtension
 	}
 


Test patch:
diff --git a/sql/internal/spectest/spectest.go b/sql/internal/spectest/spectest.go
index 22ff1a8c..41bc2fe0 100644
--- a/sql/internal/spectest/spectest.go
+++ b/sql/internal/spectest/spectest.go
@@ -11,6 +11,7 @@ import (
 	"ariga.io/atlas/schema/schemaspec"
 	"ariga.io/atlas/schema/schemaspec/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
+	"ariga.io/atlas/sql/schema"
 
 	"github.com/stretchr/testify/require"
 )
@@ -36,6 +37,30 @@ func RegistrySanityTest(t *testing.T, registry *schemahcl.TypeRegistry, skip []s
 	}
 }
 
+// TestInputVars runs a test verifying that the driver's exposed Eval function uses
+// input variables properly.
+func TestInputVars(t *testing.T, evaluator schemahcl.Evaluator) {
+	h := `
+variable "tenant" {
+	type = string
+	default = "test"
+}
+schema "tenant" {
+	name = var.tenant
+}
+table "users" {
+	schema = schema.tenant
+	column "id" {
+		type = int
+	}
+}
+`
+	var test schema.Realm
+	err := evaluator.Eval([]byte(h), &test, map[string]string{"tenant": "rotemtam"})
+	require.NoError(t, err)
+	require.EqualValues(t, "rotemtam", test.Schemas[0].Name)
+}
+
 func contains(s string, l []string) bool {
 	for i := range l {
 		if s == l[i] {
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index 4886b208..e8f7dafe 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"testing"
 
+	"ariga.io/atlas/sql/internal/spectest"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/stretchr/testify/require"
@@ -1152,6 +1153,10 @@ schema "test" {
 	}
 }
 
+func TestInputVars(t *testing.T) {
+	spectest.TestInputVars(t, EvalHCL)
+}
+
 func typeTime(t string, p int) schema.Type {
 	return &schema.TimeType{T: t, Precision: &p}
 }
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index a85686ce..cf3674f4 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -888,3 +888,7 @@ func TestParseType_Time(t *testing.T) {
 func TestRegistrySanity(t *testing.T) {
 	spectest.RegistrySanityTest(t, TypeRegistry, []string{"enum"})
 }
+
+func TestInputVars(t *testing.T) {
+	spectest.TestInputVars(t, EvalHCL)
+}
diff --git a/sql/sqlite/sqlspec_test.go b/sql/sqlite/sqlspec_test.go
index 8cdcf2a5..8fc97d43 100644
--- a/sql/sqlite/sqlspec_test.go
+++ b/sql/sqlite/sqlspec_test.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"testing"
 
+	"ariga.io/atlas/sql/internal/spectest"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/stretchr/testify/require"
@@ -174,7 +175,7 @@ table "accounts" {
 	}
 
 	var s schema.Schema
-	err := UnmarshalSpec([]byte(f), hclState, &s)
+	err := UnmarshalHCL([]byte(f), &s)
 	require.NoError(t, err)
 	require.EqualValues(t, exp, &s)
 }
@@ -406,16 +407,20 @@ func TestTypes(t *testing.T) {
 schema "test" {
 }
 `, tt.typeExpr)
-			err := UnmarshalSpec([]byte(doc), hclState, &test)
+			err := UnmarshalHCL([]byte(doc), &test)
 			require.NoError(t, err)
 			colspec := test.Tables[0].Columns[0]
 			require.EqualValues(t, tt.expected, colspec.Type.Type)
 			spec, err := MarshalSpec(&test, hclState)
 			require.NoError(t, err)
 			var after schema.Schema
-			err = UnmarshalSpec(spec, hclState, &after)
+			err = UnmarshalHCL(spec, &after)
 			require.NoError(t, err)
 			require.EqualValues(t, tt.expected, after.Tables[0].Columns[0].Type.Type)
 		})
 	}
 }
+
+func TestInputVars(t *testing.T) {
+	spectest.TestInputVars(t, EvalHCL)
+}