Repository: ariga/atlas
Pull request #911 (2022-07-06 14:32:17)

all: remove the schemaspec package
Apologize for the huge PR. 

This PR removes the `schemaspec` package and leaves us with a simpler single-syntax implementation for the Atlas DDL based on HCL. 

Some highlights:
* Removing `Unmarshal` (interfaces, implementations, functional interfaces etc) and replacing it with `schemahcl.Eval`
* Docs updated
* Tests updated
* and more.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c4cafc6a..a8887172 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -66,9 +66,9 @@ jobs:
       - name: Run sql tests
         run: go test -race ./...
         working-directory: sql
-      - name: Run schema tests
+      - name: Run schemahcl tests
         run: go test -race ./...
-        working-directory: schema
+        working-directory: schemahcl
       - name: Run cli tests
         run: go test -race ./...
         working-directory: cmd/atlas
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index eb9bdbf7..a52181ee 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -26,6 +26,7 @@ import (
 	"ariga.io/atlas/sql/sqlclient"
 	"ariga.io/atlas/sql/sqltool"
 	"github.com/fatih/color"
+	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/spf13/pflag"
 
 	"github.com/spf13/cobra"
@@ -99,7 +100,7 @@ to re-hash the contents and resolve the error
 	}
 	// MigrateApplyCmd represents the 'atlas migrate apply' subcommand.
 	MigrateApplyCmd = &cobra.Command{
-		Use:   "apply",
+		Use:   "apply [flags] [count]",
 		Short: "Applies pending migration files on the connected database.",
 		Long: `'atlas migrate apply' reads the migration state of the connected database and computes what migrations are pending.
 It then attempts to apply the pending migration files in the correct order onto the database. 
@@ -108,19 +109,20 @@ As a safety measure 'atlas migrate apply' will abort with an error, if:
   - the migration directory is not in sync with the 'atlas.sum' file
   - the migration and database history do not match each other`,
 		Example: `  atlas migrate apply --to mysql://user:pass@localhost:3306/dbname
-  atlas migrate apply 1 --dir file:///path/to/migration/directory --to mysql://user:pass@localhost:3306/dbname`,
+  atlas migrate apply --dir file:///path/to/migration/directory --to mysql://user:pass@localhost:3306/dbname 1`,
 		Args: cobra.MaximumNArgs(1),
 		RunE: CmdMigrateApplyRun,
 	}
 	// MigrateDiffCmd represents the 'atlas migrate diff' subcommand.
 	MigrateDiffCmd = &cobra.Command{
-		Use:   "diff",
-		Short: "Compute the diff between the migration directory and a connected database and create a new migration file.",
-		Long: `'atlas migrate diff' uses the dev-database to re-run all migration files in the migration
-directory and compares it to a given desired state and create a new migration file containing SQL statements to migrate 
-the migration directory state to the desired schema. The desired state can be another connected database or an HCL file.`,
-		Example: `  atlas migrate diff --dev-url mysql://user:pass@localhost:3306/dev --to mysql://user:pass@localhost:3306/dbname
-  atlas migrate diff --dev-url mysql://user:pass@localhost:3306/dev --to file://atlas.hcl`,
+		Use:   "diff [flags] [name]",
+		Short: "Compute the diff between the migration directory and a desired state and create a new migration file.",
+		Long: `'atlas migrate diff' uses the dev-database to re-run all migration files in the migration directory, compares
+it to a given desired state and create a new migration file containing SQL statements to migrate the migration
+directory state to the desired schema. The desired state can be another connected database or an HCL file.`,
+		Example: `  atlas migrate diff --dev-url mysql://user:pass@localhost:3306/dev --to file://atlas.hcl
+  atlas migrate diff --dev-url mysql://user:pass@localhost:3306/dev --to file://atlas.hcl add_users_table
+  atlas migrate diff --dev-url mysql://user:pass@localhost:3306/dev --to mysql://user:pass@localhost:3306/dbname`,
 		Args:    cobra.MaximumNArgs(1),
 		PreRunE: migrateFlagsFromEnv,
 		RunE:    CmdMigrateDiffRun,
@@ -160,7 +162,8 @@ executed on the connected database in order to validate SQL semantics.`,
 	}
 	// MigrateLintCmd represents the 'atlas migrate Lint' command.
 	MigrateLintCmd = &cobra.Command{
-		Use: "lint",
+		Use:   "lint",
+		Short: "Run analysis on the migration directory",
 		Example: `  atlas migrate lint
   atlas migrate lint --dir file:///path/to/migration/directory --dev-url mysql://root:pass@localhost:3306 --latest 1
   atlas migrate lint --dir file:///path/to/migration/directory --dev-url mysql://root:pass@localhost:3306 --git-base master
@@ -198,6 +201,7 @@ func init() {
 	MigrateApplyCmd.Flags().StringVarP(&MigrateFlags.LogFormat, migrateFlagLog, "", logFormatTTY, "log format to use")
 	MigrateApplyCmd.Flags().StringVarP(&MigrateFlags.RevisionSchema, migrateFlagRevisionsSchema, "", "", "schema name where the revisions table is to be created")
 	toURL(MigrateApplyCmd.Flags())
+	cobra.CheckErr(MigrateApplyCmd.MarkFlagRequired(migrateFlagTo))
 	// Diff flags.
 	devURL(MigrateDiffCmd.Flags())
 	toURL(MigrateDiffCmd.Flags())
@@ -471,7 +475,11 @@ func to(ctx context.Context, client *sqlclient.Client) (migrate.StateReader, err
 			return nil, err
 		}
 		realm := &schema.Realm{}
-		if err := client.Eval(f, realm, nil); err != nil {
+		parsed, err := parseHCLBytes(f)
+		if err != nil {
+			return nil, err
+		}
+		if err := client.Eval(parsed, realm, nil); err != nil {
 			return nil, err
 		}
 		if len(schemas) > 0 {
@@ -511,6 +519,14 @@ func to(ctx context.Context, client *sqlclient.Client) (migrate.StateReader, err
 	}
 }
 
+func parseHCLBytes(b []byte) (*hclparse.Parser, error) {
+	p := hclparse.NewParser()
+	if _, diag := p.ParseHCL(b, ""); diag.HasErrors() {
+		return nil, diag
+	}
+	return p, nil
+}
+
 const (
 	formatAtlas         = "atlas"
 	formatGolangMigrate = "golang-migrate"
diff --git a/cmd/atlas/internal/cmdapi/project.go b/cmd/atlas/internal/cmdapi/project.go
index c8565757..d33624f9 100644
--- a/cmd/atlas/internal/cmdapi/project.go
+++ b/cmd/atlas/internal/cmdapi/project.go
@@ -8,8 +8,7 @@ import (
 	"fmt"
 	"os"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 )
 
 const projectFileName = "atlas.hcl"
@@ -47,7 +46,7 @@ func (e *Env) asMap() (map[string]string, error) {
 			m[attr.K] = v
 			continue
 		}
-		if lv, ok := attr.V.(*schemaspec.LiteralValue); ok {
+		if lv, ok := attr.V.(*schemahcl.LiteralValue); ok {
 			m[attr.K] = lv.V
 		}
 		return nil, fmt.Errorf("expecting attr %q to be a literal, got: %T", attr.K, attr.V)
@@ -76,7 +75,7 @@ type Env struct {
 	// Directory containing the migrations for the env.
 	MigrationDir *MigrationDir `spec:"migration_dir"`
 
-	schemaspec.DefaultExtension
+	schemahcl.DefaultExtension
 }
 
 var hclState = schemahcl.New(
@@ -95,7 +94,7 @@ func LoadEnv(path string, name string, opts ...LoadOption) (*Env, error) {
 		return nil, err
 	}
 	var project projectFile
-	if err := hclState.Eval(b, &project, cfg.inputVals); err != nil {
+	if err := hclState.EvalBytes(b, &project, cfg.inputVals); err != nil {
 		return nil, err
 	}
 	projEnvs := make(map[string]*Env)
@@ -125,5 +124,5 @@ func LoadEnv(path string, name string, opts ...LoadOption) (*Env, error) {
 }
 
 func init() {
-	schemaspec.Register("env", &Env{})
+	schemahcl.Register("env", &Env{})
 }
diff --git a/cmd/atlas/internal/cmdapi/schema.go b/cmd/atlas/internal/cmdapi/schema.go
index e6e8b14b..4b11d971 100644
--- a/cmd/atlas/internal/cmdapi/schema.go
+++ b/cmd/atlas/internal/cmdapi/schema.go
@@ -305,7 +305,11 @@ func applyRun(ctx context.Context, client *sqlclient.Client, devURL string, file
 		return err
 	}
 	desired := &schema.Realm{}
-	if err := client.Eval(f, desired, input); err != nil {
+	parsed, err := parseHCLBytes(f)
+	if err != nil {
+		return err
+	}
+	if err := client.Eval(parsed, desired, input); err != nil {
 		return err
 	}
 	if len(schemas) > 0 {
diff --git a/doc/md/ddl/intro.md b/doc/md/ddl/intro.md
index a743b0ae..abd24b3b 100644
--- a/doc/md/ddl/intro.md
+++ b/doc/md/ddl/intro.md
@@ -9,12 +9,9 @@ title: Data Definition Language
 In the core of the Atlas project resides the Atlas Data Definition Language (DDL). The DDL is designed to capture an
 organization's data topologies and other aspects of its data infrastructure.
 
-In the design of the DDL, we took two important considerations:
-
-* Extensible - As data topologies can contain a set of diverse data technologies, the language is designed to be modular
-  with different extensions extending the types of resources and relationships that can be described using it.
-* Decoupled from Syntax - The initial syntax for the language is based on HCL (v2). Additional 
-syntaxes are (such as YAML, TypeScript or Go) in the future.
+In the design of the DDL, we put an emphasis on extensibility: As data topologies can contain a set of diverse 
+data technologies, the language is designed to be modular with different extensions extending the types of 
+resources and relationships that can be described using it.
 
 ## HCL
 
@@ -28,7 +25,7 @@ and optionally a name. Consider this block:
 
 ```hcl
 user "rotemtam" {
-   ...
+   // ...
 }
 ```
 
@@ -132,7 +129,7 @@ playlist "comedy" {
 
 ### Reading with Go
 
-To read an Atlas HCL document with Go use the `Unmarshal` ([doc](https://pkg.go.dev/ariga.io/atlas/schema/schemaspec/schemahcl#Unmarshal)) function
+To read an Atlas HCL document with Go use the `EvalBytes` ([doc](https://pkg.go.dev/ariga.io/atlas/schemahcl#EvalBytes)) function
 from the `schemahcl` package:
 
 ```go
@@ -160,7 +157,7 @@ show "seinfeld" {
   var test struct {
     Shows []*Show `spec:"show"`
   }
-  err := Unmarshal([]byte(f), &test)
+  err := EvalBytes([]byte(f), &test, nil)
   if err != nil {
     panic(err)
   }
@@ -170,16 +167,16 @@ show "seinfeld" {
 }
 ```
 
-Observe that similar to the standard-library's `json.Unmarshal` function, this function
-takes a byte-slice and an empty interface as arguments. The empty interface should be a
-pointer to a struct into which the `Unmarshal` function will read the values. The struct
-fields must be annotated with `spec` tags that define the mapping from HCL to the Go type.
-This mapping is discussed in the section about [Extensions](#extensions).
+This function takes a byte-slice, an empty interface as arguments and a map of strings as input. 
+The empty interface should be a  pointer to a struct into which the `EvalBytes` function will 
+read the values. The struct fields must be annotated with `spec` tags that define the mapping from HCL to the Go type.
+This mapping is discussed in the section about [Extensions](#extensions). The final map argument may contain 
+[Input Values](input.md) to be passed as parameters of the evaluation.  
 
 ### Writing with Go
 
 To encode a Go struct back into HCL, use the `schemahcl.Marshal`
-([doc](https://pkg.go.dev/ariga.io/atlas/schema/schemaspec/schemahcl#Marshal)) function:
+([doc](https://pkg.go.dev/ariga.io/atlas/schemahcl#Marshal)) function:
 
 ```go
 func ExampleMarshal() {
@@ -240,16 +237,16 @@ point "origin" {
 }
 ```
 
-To operate correctly, struct extensions should be registered using the `schemaspec.Register`
+To operate correctly, struct extensions should be registered using the `schemahcl.Register`
 function:
 
 ```go
-schemaspec.Register("point", &Point{})
+schemahcl.Register("point", &Point{})
 ```
 
-Extension structs may implement the [Remainer](https://pkg.go.dev/ariga.io/atlas/schema/schemaspec#Remainer)
+Extension structs may implement the [Remainer](https://pkg.go.dev/ariga.io/atlas/schemahcl#Remainer)
 interface if they wish to store any attributes and children that are not matched by their
-tagged fields. As a convenience the `schemaspec` package exports a `DefaultExtension` type that
+tagged fields. As a convenience the `schemahcl` package exports a `DefaultExtension` type that
 can be embedded to support this behavior.
 
 ### Qualifiers
diff --git a/doc/md/ddl/sql.mdx b/doc/md/ddl/sql.mdx
index fbd9dcd7..afe0ab67 100644
--- a/doc/md/ddl/sql.mdx
+++ b/doc/md/ddl/sql.mdx
@@ -29,7 +29,6 @@ values={[
 In MySQL and MariaDB, the `schema` resource can contain the `charset` and `collate` attributes. Read more about them
 in [MySQL](https://dev.mysql.com/doc/refman/8.0/en/charset.html) or
 [MariaDB](https://mariadb.com/kb/en/setting-character-sets-and-collations/) websites.
-<br/>
 
 ```hcl
 # Schema with attributes.
@@ -59,7 +58,6 @@ schema "private" {}
 
 Atlas does not support [attached databases](https://www.sqlite.org/lang_attach.html), and support only the default
 database (i.e. `main`).
-<br/>
 
 ```hcl
 schema "main" {}
@@ -207,11 +205,11 @@ column "active" {
 
 #### Properties
 
-| Name    | Kind      | Type                     | Description                                                |
-|---------|-----------|--------------------------|------------------------------------------------------------|
-| null    | attribute | bool                     | Defines whether the column is nullable.                    |
-| type    | attribute | *schemaspec.Type         | Defines the type of data that can be stored in the column. |
-| default | attribute | *schemaspec.LiteralValue | Defines the default value of the column.                   |
+| Name    | Kind      | Type                    | Description                                                |
+|---------|-----------|-------------------------|------------------------------------------------------------|
+| null    | attribute | bool                    | Defines whether the column is nullable.                    |
+| type    | attribute | *schemahcl.Type         | Defines the type of data that can be stored in the column. |
+| default | attribute | *schemahcl.LiteralValue | Defines the default value of the column.                   |
 
 ### Generated Columns
 
@@ -577,7 +575,6 @@ values={[
 <TabItem value="mysql">
 
 In MySQL/MariaDB the `auto_increment` attribute can be set on columns and tables.
-<br/>
 
 ```hcl
 table "users" {
@@ -594,7 +591,6 @@ table "users" {
 ```
 
 The `auto_increment` column can be set on the table to configure a start value other than 1.
-<br/>
 
 ```hcl
 table "users" {
@@ -615,7 +611,6 @@ table "users" {
 <TabItem value="postgres">
 
 PostgreSQL supports `serial` columns and the `generated as identity` syntax for versions >= 10.
-<br/>
 
 ```hcl
 table "users" {
@@ -640,7 +635,6 @@ table "users" {
 
 SQLite allows configuring [`AUTOINCREMENT`](https://www.sqlite.org/autoinc.html) columns using the `auto_increment`
 attribute.
-<br/>
 
 ```hcl
 table "users" {
diff --git a/doc/md/getting-started/02-inspecting.mdx b/doc/md/getting-started/02-inspecting.mdx
index d5c2a47a..bf82ac70 100644
--- a/doc/md/getting-started/02-inspecting.mdx
+++ b/doc/md/getting-started/02-inspecting.mdx
@@ -4,6 +4,9 @@ title: Inspecting Schemas
 slug: /cli/getting-started/inspection
 ---
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
 Atlas features a Data Definition Language (DDL) that has an [HCL-syntax](https://github.com/hashicorp/hcl)
 for defining the desired state of database schemas. In this section we will learn how to use the Atlas
 CLI to inspect an existing database and write it's schema in HCL to a file. 
@@ -12,71 +15,91 @@ Inspection is done via the `atlas schema inspect` command. To learn about its pa
 ```shell
 atlas schema inspect --help
 ```
-View the output:
-```text
-`atlas schema inspect` connects to the given database and inspects its schema.
-It then prints to the screen the schema of that database in Atlas DDL syntax. This output can be
-saved to a file, commonly by redirecting the output to a file named with a ".hcl" suffix:
 
-  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname" > atlas.hcl
+### Inspecting our database
 
-This file can then be edited and used with the `atlas schema apply` command to plan
-and execute schema migrations against the given database. In cases where users wish to inspect
-all multiple schemas in a given database (for instance a MySQL server may contain multiple named
-databases), omit the relevant part from the url, e.g. "mysql://user:pass@localhost:3306/".
-To select specific schemas from the databases, users may use the "--schema" (or "-s" shorthand)
-flag.
+To inspect our locally-running MySQL instance from the [previous](01-introduction.mdx) section,
+use the `-u` flag and write output to a file named `schema.hcl`:
+
+<Tabs
+defaultValue="mysql"
+values={[
+{label: 'MySQL', value: 'mysql'},
+{label: 'MariaDB', value: 'maria'},
+{label: 'PostgreSQL', value: 'postgres'},
+{label: 'SQLite', value: 'sqlite'},
+]}>
+<TabItem value="mysql">
 
-Usage:
-  atlas schema inspect [flags]
+```shell
+atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > schema.hcl
+```
 
-Examples:
-  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname"
-  atlas schema inspect -u "mariadb://user:pass@localhost:3306/" --schema=schemaA,schemaB -s schemaC
-  atlas schema inspect --url "postgres://user:pass@host:port/dbname?sslmode=disable"
-  atlas schema inspect -u "sqlite://file:ex1.db?_fk=1"
+Then, view the contents of the file:
 
-Flags:
-      --addr string      Used with -w, local address to bind the server to (default ":5800")
-  -h, --help             help for inspect
-  -s, --schema strings   Set schema name
-  -u, --url string       [driver://username:password@protocol(address)/dbname?param=value] Select data source using the url format
-  -w, --web              Open in a local Atlas UI
+```hcl title="schema.hcl"
+schema "example" {
+  charset = "utf8mb4"
+  collate = "utf8mb4_0900_ai_ci"
+}
 ```
 
-:::info
+</TabItem>
+<TabItem value="maria">
 
-If you're using PostgreSQL, there is [additional documentation about building the connection string](../url#ssltls-mode)
+```shell
+atlas schema inspect -u "maria://root:pass@localhost:3306/example" > schema.hcl
+```
 
-:::
+Then, view the contents of the file:
 
-### Inspecting our database
+```hcl title="schema.hcl"
+schema "example" {
+  charset = "utf8mb4"
+  collate = "utf8mb4_0900_ai_ci"
+}
+```
+
+</TabItem>
+<TabItem value="postgres">
 
-To inspect our locally-running MySQL instance from the [previous](01-introduction.mdx) section,
-use the `-u` flag and write output to a file named `atlas.hcl`:
 ```shell
-atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > atlas.hcl
+atlas schema inspect -u "postgres://postgres:pass@localhost:5432/example?sslmode=disable" > schema.hcl
 ```
-To view the contents of the created file:
+
+Then, view the contents of the file:
+
+```hcl title="schema.hcl"
+schema "public" {
+}
+```
+
+</TabItem>
+<TabItem value="sqlite">
+
 ```shell
-cat atlas.hcl
+atlas schema inspect -u "sqlite://file.db" > schema.hcl
 ```
-View the output:
-```hcl
-schema "example" {
-  charset = "utf8mb4"
-  collate = "utf8mb4_0900_ai_ci"
+
+Then, view the contents of the file:
+
+```hcl title="schema.hcl"
+schema "main" {
 }
 ```
+
+</TabItem>
+</Tabs>
+
 As you can see, Atlas inspected our (empty) database and wrote an Atlas HCL document 
 containing only a [Schema](../ddl/sql.mdx#schema) resource.
 
 Next, let's create some tables in our SQL database and see how they are reflected
-in the inspected Atlas HCL document. 
+in the inspected Atlas HCL document.
 
 ### Modifying our database schema manually
 
-In our MySQL command-line prompt, let's create two tables:
+In your database command-line prompt, create the tables:
 ```sql
 CREATE table users (
     id int PRIMARY KEY,
@@ -94,17 +117,54 @@ Observe that the tables are created successfully:
 ```text
 Query OK, 0 rows affected (0.02 sec)
 ```
-Our schema represents a highly simplified blogging system with a users
-table for the authors and a blog_posts table for the contents:
+Our schema represents a highly simplified blogging system with a `users` table for the authors and a `blog_posts` table
+for the contents:
 
 ![Blog ERD](https://atlasgo.io/uploads/images/blog-erd.png)
 
 ### Inspecting table schemas with Atlas CLI
 
 Next, let's re-run our inspection command:
+
+
+<Tabs
+defaultValue="mysql"
+values={[
+{label: 'MySQL', value: 'mysql'},
+{label: 'MariaDB', value: 'maria'},
+{label: 'PostgreSQL', value: 'postgres'},
+{label: 'SQLite', value: 'sqlite'},
+]}>
+<TabItem value="mysql">
+
 ```shell
-atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > atlas.hcl
+atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > schema.hcl
 ```
+
+</TabItem>
+<TabItem value="maria">
+
+```shell
+atlas schema inspect -u "maria://root:pass@localhost:3306/example" > schema.hcl
+```
+
+</TabItem>
+<TabItem value="postgres">
+
+```shell
+atlas schema inspect -u "postgres://postgres:pass@localhost:5432/example?sslmode=disable" > schema.hcl
+```
+
+</TabItem>
+<TabItem value="sqlite">
+
+```shell
+atlas schema inspect -u "sqlite://file.db" > schema.hcl
+```
+
+</TabItem>
+</Tabs>
+
 Browse through the updated contents of the file, it contains 3 blocks representing
 our schema as before, and two new blocks representing the `users` and `blog_posts` tables.
 Consider the following block:
diff --git a/doc/md/getting-started/03-applying.md b/doc/md/getting-started/03-applying.mdx
similarity index 65%
rename from doc/md/getting-started/03-applying.md
rename to doc/md/getting-started/03-applying.mdx
index 607cba38..c367fb5a 100644
--- a/doc/md/getting-started/03-applying.md
+++ b/doc/md/getting-started/03-applying.mdx
@@ -4,6 +4,9 @@ title: Applying Schemas
 slug: /cli/getting-started/applying-schemas
 ---
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
 ### Declarative migrations
 
 In the previous section, we learned how to inspect an existing database and write
@@ -18,40 +21,8 @@ Let's start by viewing the help text for the `apply` command:
 ```shell
 atlas schema apply --help
 ```
-The documentation is printed out:
-```text
-'atlas schema apply' plans and executes a database migration to bring a given
-database to the state described in the Atlas schema file. Before running the
-migration, Atlas will print the migration plan and prompt the user for approval.
-
-If run with the "--dry-run" flag, atlas will exit after printing out the planned
-migration.
-
-Usage:
-  atlas schema apply [flags]
-
-Examples:
-  atlas schema apply -u "mysql://user:pass@localhost/dbname" -f atlas.hcl
-  atlas schema apply -u "mysql://localhost" -f atlas.hcl --schema prod --schema staging
-  atlas schema apply -u "mysql://user:pass@localhost:3306/dbname" -f atlas.hcl --dry-run
-  atlas schema apply -u "mariadb://user:pass@localhost:3306/dbname" -f atlas.hcl
-  atlas schema apply --url "postgres://user:pass@host:port/dbname?sslmode=disable" -f atlas.hcl
-  atlas schema apply -u "sqlite://file:ex1.db?_fk=1" -f atlas.hcl
-
-Flags:
-  -f, --file string      [/path/to/file] file containing the HCL schema.
-  -u, --url string       URL to the database using the format:
-                         [driver://username:password@address/dbname?param=value]
-  -s, --schema strings   Set schema names.
-      --dev-url string   URL for the dev database. Used to validate schemas and calculate diffs
-                         before running migration.
-      --dry-run          Dry-run. Print SQL plan without prompting for execution.
-      --auto-approve     Auto approve. Apply the schema changes without prompting for approval.
-  -w, --web              Open in a local Atlas UI.
-      --addr string      used with -w, local address to bind the server to. (default ":5800")
-  -h, --help             help for apply
-```
-As you can see, similar to the `inspect` command, the `-u` flag is used to define the
+
+You can see that similar to the `inspect` command, the `-u` flag is used to define the
 URL to connect to the database, and an additional flag `-f` specifies the path to
 the file containing the desired schema. 
 
@@ -64,10 +35,46 @@ relationship between blog posts and categories:
 
 ![Blog ERD](https://atlasgo.io/uploads/images/blog-erd-2.png)
 
-First, let's store the existing schema in a file named `atlas.hcl`:
+First, let's store the existing schema in a file named `schema.hcl`:
+
+<Tabs
+defaultValue="mysql"
+values={[
+{label: 'MySQL', value: 'mysql'},
+{label: 'MariaDB', value: 'maria'},
+{label: 'PostgreSQL', value: 'postgres'},
+{label: 'SQLite', value: 'sqlite'},
+]}>
+<TabItem value="mysql">
+
+```shell
+atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > schema.hcl
+```
+
+</TabItem>
+<TabItem value="maria">
+
 ```shell
-atlas schema inspect -u "mysql://root:pass@localhost:3306/example" > atlas.hcl
+atlas schema inspect -u "maria://root:pass@localhost:3306/example" > schema.hcl
 ```
+
+</TabItem>
+<TabItem value="postgres">
+
+```shell
+atlas schema inspect -u "postgres://postgres:pass@localhost:5432/example?sslmode=disable" > schema.hcl
+```
+
+</TabItem>
+<TabItem value="sqlite">
+
+```shell
+atlas schema inspect -u "sqlite://file.db" > schema.hcl
+```
+
+</TabItem>
+</Tabs>
+
 Next, add the following table definition to the file:
 ```hcl
 table "categories" {
@@ -87,9 +94,46 @@ table "categories" {
 ```
 
 To add this table to our database, let's use the `atlas schema apply` command:
+
+
+<Tabs
+defaultValue="mysql"
+values={[
+{label: 'MySQL', value: 'mysql'},
+{label: 'MariaDB', value: 'maria'},
+{label: 'PostgreSQL', value: 'postgres'},
+{label: 'SQLite', value: 'sqlite'},
+]}>
+<TabItem value="mysql">
+
+```shell
+atlas schema apply -u "mysql://root:pass@localhost:3306/example" -f schema.hcl
+```
+
+</TabItem>
+<TabItem value="maria">
+
 ```shell
-atlas schema apply -u "mysql://root:pass@localhost:3306/example" -f atlas.hcl 
+atlas schema apply -u "maria://root:pass@localhost:3306/example" -f schema.hcl
 ```
+
+</TabItem>
+<TabItem value="postgres">
+
+```shell
+atlas schema apply -u "postgres://postgres:pass@localhost:5432/example?sslmode=disable" -f schema.hcl
+```
+
+</TabItem>
+<TabItem value="sqlite">
+
+```shell
+atlas schema apply -u "sqlite://file.db" -f schema.hcl
+```
+
+</TabItem>
+</Tabs>
+
 Atlas plans a migration (schema change) for us and prompts us to approve it:
 ```text
 -- Planned Changes:
@@ -105,8 +149,8 @@ To apply the migration, press `ENTER`, and voila!
 ✔ Apply
 ```
 
-To verify that our new table was created, in our open `mysql` command-line from the 
-previous step run:
+To verify that our new table was created, open the database command line tool from previous step and run:
+
 ```text
 mysql> show create table categories;
 +------------+------------------------------------------------------+
@@ -122,7 +166,7 @@ mysql> show create table categories;
 ```
 
 Amazing! Our new table was created. Next, let's define our association table,
-add the following block to our `atlas.hcl` file:
+add the following block to our `schema.hcl` file:
 ```hcl
 table "post_categories" {
     schema = schema.example
@@ -134,11 +178,11 @@ table "post_categories" {
     }
     foreign_key "post_category_post" {
         columns     = [column.post_id]
-        ref_columns = [table.blog_posts.column.id]
+        ref_columns = [column.id]
     }
     foreign_key "post_category_category" {
         columns     = [column.category_id]
-        ref_columns = [table.categories.column.id]
+        ref_columns = [column.id]
     }
 }
 ```
@@ -148,7 +192,7 @@ and `categories` tables.
 
 Let's try to apply the schema again, this time with the updated schema:
 ```text
-atlas schema apply -u "mysql://root:pass@localhost:3306/example" -f atlas.hcl
+atlas schema apply -u "mysql://root:pass@localhost:3306/example" -f schema.hcl
 -- Planned Changes:
 -- Create "post_categories" table
 CREATE TABLE `example`.`post_categories` (`post_id` int NOT NULL, `category_id` int NOT NULL, CONSTRAINT `post_category_post` FOREIGN KEY (`post_id`) REFERENCES `example`.`blog_posts` (`id`), CONSTRAINT `post_category_category` FOREIGN KEY (`category_id`) REFERENCES `example`.`categories` (`id`))
diff --git a/doc/website/sidebars.js b/doc/website/sidebars.js
index f038de93..8064822d 100644
--- a/doc/website/sidebars.js
+++ b/doc/website/sidebars.js
@@ -78,14 +78,4 @@ module.exports = {
             id: 'about',
         }
     ]
-    // But you can create a sidebar manually
-    /*
-    tutorialSidebar: [
-      {
-        type: 'category',
-        label: 'Tutorial',
-        items: ['hello'],
-      },
-    ],
-     */
 };
diff --git a/doc/website/src/css/custom.css b/doc/website/src/css/custom.css
index 8bf26e6e..117cf1de 100644
--- a/doc/website/src/css/custom.css
+++ b/doc/website/src/css/custom.css
@@ -161,4 +161,8 @@ div[class^='announcementBar_'] {
           var(--site-announcement-bar-stripe-color2) 40px
   );
   font-weight: bold;
+}
+
+.tabs-container  p {
+  margin-bottom: 10px;
 }
\ No newline at end of file
diff --git a/internal/ci/ci.tmpl b/internal/ci/ci.tmpl
index e4e4d414..d276be5f 100644
--- a/internal/ci/ci.tmpl
+++ b/internal/ci/ci.tmpl
@@ -66,9 +66,9 @@ jobs:
       - name: Run sql tests
         run: go test -race ./...
         working-directory: sql
-      - name: Run schema tests
+      - name: Run schemahcl tests
         run: go test -race ./...
-        working-directory: schema
+        working-directory: schemahcl
       - name: Run cli tests
         run: go test -race ./...
         working-directory: cmd/atlas
diff --git a/internal/typedoc/main.go b/internal/typedoc/main.go
index ca5120ea..af4eada6 100644
--- a/internal/typedoc/main.go
+++ b/internal/typedoc/main.go
@@ -14,8 +14,7 @@ import (
 	"strings"
 	"text/template"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/postgres"
 	"ariga.io/atlas/sql/sqlite"
@@ -31,7 +30,7 @@ type (
 		Types []Type
 	}
 	Type struct {
-		*schemaspec.TypeSpec
+		*schemahcl.TypeSpec
 		Info            string
 		MarshalOverride string
 	}
@@ -42,7 +41,7 @@ func main() {
 	drivers := []*Driver{
 		{Name: "MySQL/MariaDB", Types: wrap(mysql.TypeRegistry.Specs())},
 		{Name: "Postgres", Types: append(wrap(postgres.TypeRegistry.Specs()), Type{
-			TypeSpec: &schemaspec.TypeSpec{
+			TypeSpec: &schemahcl.TypeSpec{
 				Name: "enum",
 				T:    "my_enum",
 			},
@@ -102,10 +101,10 @@ func colHcl(ts *Type, d *Driver) []string {
 	return res
 }
 
-func dummyType(ts *schemaspec.TypeSpec) *schemaspec.Type {
-	spec := &schemaspec.Type{T: ts.T}
+func dummyType(ts *schemahcl.TypeSpec) *schemahcl.Type {
+	spec := &schemahcl.Type{T: ts.T}
 	for _, attr := range ts.Attributes {
-		var a *schemaspec.Attr
+		var a *schemahcl.Attr
 		switch attr.Kind {
 		case reflect.Int, reflect.Int64:
 			n := "255"
@@ -130,36 +129,36 @@ func dummyType(ts *schemaspec.TypeSpec) *schemaspec.Type {
 	return spec
 }
 
-// StrAttr is a helper method for constructing *schemaspec.Attr of type string.
-func StrAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// StrAttr is a helper method for constructing *schemahcl.Attr of type string.
+func StrAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: strconv.Quote(v)},
+		V: &schemahcl.LiteralValue{V: strconv.Quote(v)},
 	}
 }
 
-// LitAttr is a helper method for constructing *schemaspec.Attr instances that contain literal values.
-func LitAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// LitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values.
+func LitAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: v},
+		V: &schemahcl.LiteralValue{V: v},
 	}
 }
 
-// ListAttr is a helper method for constructing *schemaspec.Attr instances that contain list values.
-func ListAttr(k string, litValues ...string) *schemaspec.Attr {
-	lv := &schemaspec.ListValue{}
+// ListAttr is a helper method for constructing *schemahcl.Attr instances that contain list values.
+func ListAttr(k string, litValues ...string) *schemahcl.Attr {
+	lv := &schemahcl.ListValue{}
 	for _, v := range litValues {
-		lv.V = append(lv.V, &schemaspec.LiteralValue{V: v})
+		lv.V = append(lv.V, &schemahcl.LiteralValue{V: v})
 	}
-	return &schemaspec.Attr{
+	return &schemahcl.Attr{
 		K: k,
 		V: lv,
 	}
 }
 
-// wrap iterates over the given slice of schemaspec.TypeSpec and wraps them with Type.
-func wrap(tss []*schemaspec.TypeSpec) []Type {
+// wrap iterates over the given slice of schemahcl.TypeSpec and wraps them with Type.
+func wrap(tss []*schemahcl.TypeSpec) []Type {
 	res := make([]Type, len(tss))
 	for i, ts := range tss {
 		res[i] = Type{TypeSpec: ts}
@@ -168,8 +167,8 @@ func wrap(tss []*schemaspec.TypeSpec) []Type {
 }
 
 // unwrap undoes wrap.
-func unwrap(tss []Type) []*schemaspec.TypeSpec {
-	res := make([]*schemaspec.TypeSpec, len(tss))
+func unwrap(tss []Type) []*schemahcl.TypeSpec {
+	res := make([]*schemahcl.TypeSpec, len(tss))
 	for i, ts := range tss {
 		res[i] = ts.TypeSpec
 	}
diff --git a/schema/schemaspec/doc.go b/schema/schemaspec/doc.go
deleted file mode 100644
index e6ed6b8c..00000000
--- a/schema/schemaspec/doc.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-// Package schemaspec provides types designed to capture atlas schema elements, and create
-// type-safe extensions to the configuration language. This package is designed to provide
-// an intermediate representation between a serialized configuration language representation
-// (Atlas HCL, JSON, YAML, etc.) and concrete representations that are created and handled
-// by a specific driver.
-//
-// The basic type that is handled in this package is schemaspec.Resource which is a generic
-// container for resources described in Atlas configurations. A resource has a set of "Attr" instances
-// associated with it, as well a a list of children schemaspec.Resource instances that can be associated
-// with it.
-//
-// Users of Atlas applications are expected to interface with this package via a configuration
-// language package. For example, via the `schemahcl` package:
-//
-//   schemahcl.Unmarshal([]byte(`
-//		table "users" {
-//			column "id" {
-//				type = "int"
-//			}
-//		}
-//  `), &someStruct{})
-//
-// Applications working with the Atlas DDL  are expected to extend the Atlas language by
-// defining their own type structs  that objects can be handled in a type-safe way. Resource
-// objects provide the `As` method to read a resource into an extension struct, as well as a
-// `Scan` method to read an extension struct back into a Resource.
-//
-// The mapping between the extension struct fields and a Resource is done by placing tags on the
-// extension struct field using the `spec` key in the tag. To specify that a field should be mapped to
-// the corresponding Resource's `Name` specify ",name" to the tag value. For example,
-//
-//  type Point struct {
-//      ID string `spec:",name"`
-//      X  int    `spec:"x"
-//      Y  int    `spec:"y"
-//  }
-//
-// Would be able to capture a Resource defined in Atlas HCL as:
-//
-//	point "origin" {
-//		x = 100
-//		y = 200
-//	}
-//
-// Extension structs may implement the Remainer interface if they wish to store
-// any attributes and children that are not matched by their tagged fields. As a convenience
-// the package exports a DefaultExtension type that can be embedded to support this behavior.
-package schemaspec
diff --git a/schema/schemaspec/internal/schemautil/schemautil.go b/schema/schemaspec/internal/schemautil/schemautil.go
deleted file mode 100644
index 938b80fa..00000000
--- a/schema/schemaspec/internal/schemautil/schemautil.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package schemautil
-
-import (
-	"strconv"
-
-	"ariga.io/atlas/schema/schemaspec"
-)
-
-// LitAttr is a helper method for constructing *schemaspec.Attr instances that contain literal values.
-func LitAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
-		K: k,
-		V: &schemaspec.LiteralValue{V: v},
-	}
-}
-
-// StrLitAttr is a helper method for constructing *schemaspec.Attr instances that contain literal values
-// representing string literals.
-func StrLitAttr(k, v string) *schemaspec.Attr {
-	return LitAttr(k, strconv.Quote(v))
-}
-
-// ListAttr is a helper method for constructing *schemaspec.Attr instances that contain list values.
-func ListAttr(k string, litValues ...string) *schemaspec.Attr {
-	lv := &schemaspec.ListValue{}
-	for _, v := range litValues {
-		lv.V = append(lv.V, &schemaspec.LiteralValue{V: v})
-	}
-	return &schemaspec.Attr{
-		K: k,
-		V: lv,
-	}
-}
diff --git a/schema/schemaspec/schemahcl/context.go b/schemahcl/context.go
similarity index 92%
rename from schema/schemaspec/schemahcl/context.go
rename to schemahcl/context.go
index ec6ae7ea..ff9444fc 100644
--- a/schema/schemaspec/schemahcl/context.go
+++ b/schemahcl/context.go
@@ -9,7 +9,6 @@ import (
 	"reflect"
 	"strconv"
 
-	"ariga.io/atlas/schema/schemaspec"
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/hashicorp/hcl/v2/hclsyntax"
@@ -79,9 +78,9 @@ func mergeCtxVar(ctx *hcl.EvalContext, vals map[string]cty.Value) {
 // readVar reads the raw inputVal as a cty.Value using the type definition on v.
 func readVar(v *varDef, inputVal string) (cty.Value, error) {
 	et := v.Type.EncapsulatedValue()
-	typ, ok := et.(*schemaspec.Type)
+	typ, ok := et.(*Type)
 	if !ok {
-		return cty.NilVal, fmt.Errorf("expected schemaspec.Type got %T", et)
+		return cty.NilVal, fmt.Errorf("expected schemahcl.Type got %T", et)
 	}
 	switch typ.T {
 	case "string":
@@ -104,7 +103,7 @@ func readVar(v *varDef, inputVal string) (cty.Value, error) {
 }
 
 func capsuleTypeVal(t string) cty.Value {
-	return cty.CapsuleVal(ctyTypeSpec, &schemaspec.Type{T: t})
+	return cty.CapsuleVal(ctyTypeSpec, &Type{T: t})
 }
 
 func setBlockVars(ctx *hcl.EvalContext, b *hclsyntax.Body) (*hcl.EvalContext, error) {
@@ -222,9 +221,9 @@ func attrMap(attrs hclsyntax.Attributes) map[string]cty.Value {
 	return out
 }
 
-// ctySchemaLit is a cty.Capsule type the encapsulates a schemaspec.LiteralValue.
+// ctySchemaLit is a cty.Capsule type the encapsulates a schemahcl.LiteralValue.
 var (
-	ctySchemaLit = cty.CapsuleWithOps("lit", reflect.TypeOf(schemaspec.LiteralValue{}), &cty.CapsuleOps{
+	ctySchemaLit = cty.CapsuleWithOps("lit", reflect.TypeOf(LiteralValue{}), &cty.CapsuleOps{
 		// ConversionFrom facilitates reading the encapsulated type as a string, as is needed, for example,
 		// when interpolating it in a string expression.
 		ConversionFrom: func(src cty.Type) func(interface{}, cty.Path) (cty.Value, error) {
@@ -232,9 +231,9 @@ var (
 				return nil
 			}
 			return func(i interface{}, path cty.Path) (cty.Value, error) {
-				lit, ok := i.(*schemaspec.LiteralValue)
+				lit, ok := i.(*LiteralValue)
 				if !ok {
-					return cty.Value{}, fmt.Errorf("schemahcl: expected *schemaspec.LiteralValue got %T", i)
+					return cty.Value{}, fmt.Errorf("schemahcl: expected *schemahcl.LiteralValue got %T", i)
 				}
 				uq, err := strconv.Unquote(lit.V)
 				if err != nil {
@@ -244,8 +243,8 @@ var (
 			}
 		},
 	})
-	ctyTypeSpec = cty.Capsule("type", reflect.TypeOf(schemaspec.Type{}))
-	ctyRawExpr  = cty.Capsule("raw_expr", reflect.TypeOf(schemaspec.RawExpr{}))
+	ctyTypeSpec = cty.Capsule("type", reflect.TypeOf(Type{}))
+	ctyRawExpr  = cty.Capsule("raw_expr", reflect.TypeOf(RawExpr{}))
 )
 
 // varBlock is the block type for variable declarations.
diff --git a/schema/schemaspec/extension.go b/schemahcl/extension.go
similarity index 89%
rename from schema/schemaspec/extension.go
rename to schemahcl/extension.go
index 638cd7a4..28b40493 100644
--- a/schema/schemaspec/extension.go
+++ b/schemahcl/extension.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package schemaspec
+package schemahcl
 
 import (
 	"errors"
@@ -60,7 +60,7 @@ func (r registry) lookup(ext interface{}) (string, bool) {
 // implementers returns a slice of the names of the extensions that implement i.
 func (r registry) implementers(i reflect.Type) ([]string, error) {
 	if i.Kind() != reflect.Interface {
-		return nil, fmt.Errorf("schemaspec: expected interface got %s", i.Kind())
+		return nil, fmt.Errorf("schemahcl: expected interface got %s", i.Kind())
 	}
 	var names []string
 	for name, typ := range r {
@@ -78,10 +78,10 @@ func Register(name string, ext interface{}) {
 	extensionsMu.Lock()
 	defer extensionsMu.Unlock()
 	if ext == nil {
-		panic("schemaspec: Register extension is nil")
+		panic("schemahcl: Register extension is nil")
 	}
 	if _, dup := extensions[name]; dup {
-		panic("schemaspec: Register called twice for type " + name)
+		panic("schemahcl: Register called twice for type " + name)
 	}
 	extensions[name] = ext
 }
@@ -99,16 +99,16 @@ func (r *Resource) As(target interface{}) error {
 		switch {
 		case ft.isName() && !hasAttr(r, ft.tag):
 			if seenName {
-				return errors.New("schemaspec: extension must have only one isName field")
+				return errors.New("schemahcl: extension must have only one isName field")
 			}
 			seenName = true
 			if field.Kind() != reflect.String {
-				return errors.New("schemaspec: extension isName field must be of type string")
+				return errors.New("schemahcl: extension isName field must be of type string")
 			}
 			field.SetString(r.Name)
 		case ft.isQualifier():
 			if seenQualifier {
-				return errors.New("schemaspec: extension must have only one qualifier field")
+				return errors.New("schemahcl: extension must have only one qualifier field")
 			}
 			seenQualifier = true
 			field.SetString(r.Qualifier)
@@ -193,7 +193,7 @@ func (r *Resource) As(target interface{}) error {
 	for attrName := range existingAttrs {
 		attr, ok := r.Attr(attrName)
 		if !ok {
-			return fmt.Errorf("schemaspec: expected attr %q to exist", attrName)
+			return fmt.Errorf("schemahcl: expected attr %q to exist", attrName)
 		}
 		extras.SetAttr(attr)
 	}
@@ -231,10 +231,10 @@ func (r *Resource) FinalName() (string, error) {
 func validateStructPtr(target interface{}) error {
 	typeOf := reflect.TypeOf(target)
 	if typeOf.Kind() != reflect.Ptr {
-		return errors.New("schemaspec: expected target to be a pointer")
+		return errors.New("schemahcl: expected target to be a pointer")
 	}
 	if typeOf.Elem().Kind() != reflect.Struct {
-		return errors.New("schemaspec: expected target to be a pointer to a struct")
+		return errors.New("schemahcl: expected target to be a pointer to a struct")
 	}
 	return nil
 }
@@ -252,7 +252,7 @@ func existingElements(r *Resource) (attrs, children map[string]struct{}) {
 
 func setChildSlice(field reflect.Value, children []*Resource) error {
 	if field.Type().Kind() != reflect.Slice {
-		return fmt.Errorf("schemaspec: expected field to be of kind slice")
+		return fmt.Errorf("schemahcl: expected field to be of kind slice")
 	}
 	if len(children) == 0 {
 		return nil
@@ -278,29 +278,29 @@ func setField(field reflect.Value, attr *Attr) error {
 	case reflect.String:
 		s, err := attr.String()
 		if err != nil {
-			return fmt.Errorf("schemaspec: value of attr %q cannot be read as string: %w", attr.K, err)
+			return fmt.Errorf("schemahcl: value of attr %q cannot be read as string: %w", attr.K, err)
 		}
 		field.SetString(s)
 	case reflect.Int, reflect.Int64:
 		i, err := attr.Int()
 		if err != nil {
-			return fmt.Errorf("schemaspec: value of attr %q cannot be read as integer: %w", attr.K, err)
+			return fmt.Errorf("schemahcl: value of attr %q cannot be read as integer: %w", attr.K, err)
 		}
 		field.SetInt(int64(i))
 	case reflect.Bool:
 		b, err := attr.Bool()
 		if err != nil {
-			return fmt.Errorf("schemaspec: value of attr %q cannot be read as bool: %w", attr.K, err)
+			return fmt.Errorf("schemahcl: value of attr %q cannot be read as bool: %w", attr.K, err)
 		}
 		field.SetBool(b)
 	case reflect.Ptr:
 		if err := setPtr(field, attr.V); err != nil {
-			return fmt.Errorf("schemaspec: failed setting pointer field %q: %w", attr.K, err)
+			return fmt.Errorf("schemahcl: failed setting pointer field %q: %w", attr.K, err)
 		}
 	case reflect.Interface:
 		field.Set(reflect.ValueOf(attr.V))
 	default:
-		return fmt.Errorf("schemaspec: unsupported field kind %q", field.Kind())
+		return fmt.Errorf("schemahcl: unsupported field kind %q", field.Kind())
 	}
 	return nil
 }
@@ -364,7 +364,7 @@ func setPtr(field reflect.Value, val Value) error {
 func setSliceAttr(field reflect.Value, attr *Attr) error {
 	lst, ok := attr.V.(*ListValue)
 	if !ok {
-		return fmt.Errorf("schemaspec: field is of type slice but attr %q does not contain a ListValue", attr.K)
+		return fmt.Errorf("schemahcl: field is of type slice but attr %q does not contain a ListValue", attr.K)
 	}
 	typ := field.Type().Elem()
 
@@ -413,12 +413,12 @@ func (r *Resource) Scan(ext interface{}) error {
 		case ft.omitempty() && isEmpty(field):
 		case ft.isName():
 			if field.Kind() != reflect.String {
-				return errors.New("schemaspec: extension name field must be string")
+				return errors.New("schemahcl: extension name field must be string")
 			}
 			r.Name = field.String()
 		case ft.isQualifier():
 			if field.Kind() != reflect.String {
-				return errors.New("schemaspec: extension qualifer field must be string")
+				return errors.New("schemahcl: extension qualifer field must be string")
 			}
 			r.Qualifier = field.String()
 		case isResourceSlice(field.Type()):
@@ -481,7 +481,7 @@ func scanPtr(key string, r *Resource, field reflect.Value) error {
 	case *string:
 		attr.V = &LiteralValue{V: strconv.Quote(*e)}
 	default:
-		return fmt.Errorf("schemaspec: unsupported pointer to %s", e)
+		return fmt.Errorf("schemahcl: unsupported pointer to %s", e)
 	}
 	r.SetAttr(attr)
 	return nil
@@ -505,7 +505,7 @@ func scanAttr(key string, r *Resource, field reflect.Value) error {
 		i := field.Interface()
 		v, ok := i.(Value)
 		if !ok {
-			return fmt.Errorf("schemaspec: unsupported interface type %T for field %q", i, key)
+			return fmt.Errorf("schemahcl: unsupported interface type %T for field %q", i, key)
 		}
 		r.SetAttr(&Attr{
 			K: key,
@@ -513,7 +513,7 @@ func scanAttr(key string, r *Resource, field reflect.Value) error {
 		})
 		return nil
 	default:
-		return fmt.Errorf("schemaspec: unsupported field kind %q", field.Kind())
+		return fmt.Errorf("schemahcl: unsupported field kind %q", field.Kind())
 	}
 	r.SetAttr(&Attr{
 		K: key,
@@ -541,7 +541,7 @@ func scanSliceAttr(key string, r *Resource, field reflect.Value) error {
 		}
 	case reflect.Ptr:
 		if typ.Elem() != reflect.TypeOf(&Ref{}) {
-			return fmt.Errorf("schemaspec: currently on ref slice values supported, got %s", typ)
+			return fmt.Errorf("schemahcl: currently on ref slice values supported, got %s", typ)
 		}
 		for i := 0; i < field.Len(); i++ {
 			item := field.Index(i).Interface().(*Ref)
diff --git a/schema/schemaspec/schemahcl/hcl.go b/schemahcl/hcl.go
similarity index 74%
rename from schema/schemaspec/schemahcl/hcl.go
rename to schemahcl/hcl.go
index 4815bc21..6dfb58cc 100644
--- a/schema/schemaspec/schemahcl/hcl.go
+++ b/schemahcl/hcl.go
@@ -12,8 +12,6 @@ import (
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
-
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/hashicorp/hcl/v2/hclsyntax"
@@ -23,52 +21,40 @@ import (
 
 var (
 	// Marshal returns the Atlas HCL encoding of v.
-	Marshal = schemaspec.MarshalerFunc(New().MarshalSpec)
-)
-
-var (
-	// Unmarshal parses the Atlas HCL-encoded data and stores the result in the target.
-	Unmarshal = schemaspec.UnmarshalerFunc(New().UnmarshalSpec)
+	Marshal = MarshalerFunc(New().MarshalSpec)
 )
 
 type (
-	container struct {
-		Body hcl.Body `hcl:",remain"`
-	}
-
-	// State implements schemaspec.Unmarshaler and schemaspec.Marshaler for Atlas HCL syntax
-	// and stores a configuration for these operations.
+	// State is used to evaluate and marshal Atlas HCL documents and stores a configuration for these operations.
 	State struct {
 		config *Config
 	}
-	// Evaluator evaluates the Atlas DDL document with the given input and stores it in
-	// the provided object.
+	// Evaluator is the interface that wraps the Eval function.
 	Evaluator interface {
-		Eval([]byte, interface{}, map[string]string) error
+		// Eval evaluates parsed HCL files using input variables into a schema.Realm.
+		Eval(*hclparse.Parser, 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
+	EvalFunc func(*hclparse.Parser, interface{}, map[string]string) error
+	// Marshaler is the interface that wraps the MarshalSpec function.
+	Marshaler interface {
+		// MarshalSpec marshals the provided input into a valid Atlas HCL document.
+		MarshalSpec(interface{}) ([]byte, error)
+	}
+	// MarshalerFunc is the function type that is implemented by the MarshalSpec
+	// method of the Marshaler interface.
+	MarshalerFunc func(interface{}) ([]byte, 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.
+// MarshalSpec implements Marshaler for Atlas HCL documents.
 func (s *State) MarshalSpec(v interface{}) ([]byte, error) {
-	r := &schemaspec.Resource{}
+	r := &Resource{}
 	if err := r.Scan(v); err != nil {
 		return nil, fmt.Errorf("schemahcl: failed scanning %T to resource: %w", v, err)
 	}
 	return s.encode(r)
 }
 
-// UnmarshalSpec implements schemaspec.Unmarshaler.
-func (s *State) UnmarshalSpec(data []byte, v interface{}) error {
-	return s.Eval(data, v, nil)
-}
-
 // EvalFiles evaluates the files in the provided paths using the input variables and
 // populates v with the result.
 func (s *State) EvalFiles(paths []string, v interface{}, input map[string]string) error {
@@ -78,12 +64,12 @@ func (s *State) EvalFiles(paths []string, v interface{}, input map[string]string
 			return diag
 		}
 	}
-	return s.eval(parser, v, input)
+	return s.Eval(parser, v, input)
 }
 
-// eval evaluates the parsed HCL documents using the input variables and populates v
+// Eval evaluates the parsed HCL documents using the input variables and populates v
 // using the result.
-func (s *State) eval(parsed *hclparse.Parser, v interface{}, input map[string]string) error {
+func (s *State) Eval(parsed *hclparse.Parser, v interface{}, input map[string]string) error {
 	ctx := s.config.newCtx()
 	reg := &blockDef{
 		fields:   make(map[string]struct{}),
@@ -118,7 +104,7 @@ func (s *State) eval(parsed *hclparse.Parser, v interface{}, input map[string]st
 	for k, v := range vars {
 		ctx.Variables[k] = v
 	}
-	spec := &schemaspec.Resource{}
+	spec := &Resource{}
 	sort.Slice(fileNames, func(i, j int) bool {
 		return fileNames[i] < fileNames[j]
 	})
@@ -140,28 +126,29 @@ func (s *State) eval(parsed *hclparse.Parser, v interface{}, input map[string]st
 	return nil
 }
 
-// Eval implements the Evaluator interface.
-func (s *State) Eval(data []byte, v interface{}, input map[string]string) error {
+// EvalBytes evaluates the data byte-slice as an Atlas HCL document using the input variables
+// and stores the result in v.
+func (s *State) EvalBytes(data []byte, v interface{}, input map[string]string) error {
 	parser := hclparse.NewParser()
 	if _, diag := parser.ParseHCL(data, ""); diag.HasErrors() {
 		return diag
 	}
-	return s.eval(parser, v, input)
+	return s.Eval(parser, v, input)
 }
 
 // addrRef maps addresses to their referenced resource.
-type addrRef map[string]*schemaspec.Resource
+type addrRef map[string]*Resource
 
-// patchRefs recursively searches for schemaspec.Ref under the provided schemaspec.Resource
+// patchRefs recursively searches for schemahcl.Ref under the provided schemahcl.Resource
 // and patches any variables with their concrete names.
-func patchRefs(spec *schemaspec.Resource) error {
+func patchRefs(spec *Resource) error {
 	return make(addrRef).patch(spec)
 }
 
-func (r addrRef) patch(resource *schemaspec.Resource) error {
+func (r addrRef) patch(resource *Resource) error {
 	cp := r.copy().load(resource, "")
 	for _, attr := range resource.Attrs {
-		if ref, ok := attr.V.(*schemaspec.Ref); ok {
+		if ref, ok := attr.V.(*Ref); ok {
 			referenced, ok := cp[ref.V]
 			if !ok {
 				fmt.Println(cp)
@@ -189,7 +176,7 @@ func (r addrRef) copy() addrRef {
 }
 
 // load loads the references from the children of the resource.
-func (r addrRef) load(res *schemaspec.Resource, track string) addrRef {
+func (r addrRef) load(res *Resource, track string) addrRef {
 	unlabeled := 0
 	for _, ch := range res.Children {
 		current := rep(ch)
@@ -206,7 +193,7 @@ func (r addrRef) load(res *schemaspec.Resource, track string) addrRef {
 	return r
 }
 
-func rep(r *schemaspec.Resource) string {
+func rep(r *Resource) string {
 	n := r.Name
 	if r.Qualifier != "" {
 		n = r.Qualifier + "." + n
@@ -214,8 +201,8 @@ func rep(r *schemaspec.Resource) string {
 	return fmt.Sprintf("$%s.%s", r.Type, n)
 }
 
-// resource converts the hcl file to a schemaspec.Resource.
-func (s *State) resource(ctx *hcl.EvalContext, file *hcl.File) (*schemaspec.Resource, error) {
+// resource converts the hcl file to a schemahcl.Resource.
+func (s *State) resource(ctx *hcl.EvalContext, file *hcl.File) (*Resource, error) {
 	body, ok := file.Body.(*hclsyntax.Body)
 	if !ok {
 		return nil, fmt.Errorf("schemahcl: expected remainder to be of type *hclsyntax.Body")
@@ -224,7 +211,7 @@ func (s *State) resource(ctx *hcl.EvalContext, file *hcl.File) (*schemaspec.Reso
 	if err != nil {
 		return nil, err
 	}
-	res := &schemaspec.Resource{
+	res := &Resource{
 		Attrs: attrs,
 	}
 	for _, blk := range body.Blocks {
@@ -257,11 +244,11 @@ func (s *State) mayExtendVars(ctx *hcl.EvalContext, scope []string) *hcl.EvalCon
 	return ctx
 }
 
-func (s *State) toAttrs(ctx *hcl.EvalContext, hclAttrs hclsyntax.Attributes, scope []string) ([]*schemaspec.Attr, error) {
-	var attrs []*schemaspec.Attr
+func (s *State) toAttrs(ctx *hcl.EvalContext, hclAttrs hclsyntax.Attributes, scope []string) ([]*Attr, error) {
+	var attrs []*Attr
 	for _, hclAttr := range hclAttrs {
 		ctx := s.mayExtendVars(ctx, append(scope, hclAttr.Name))
-		at := &schemaspec.Attr{K: hclAttr.Name}
+		at := &Attr{K: hclAttr.Name}
 		value, diag := hclAttr.Expr.Value(ctx)
 		if diag.HasErrors() {
 			return nil, diag
@@ -269,11 +256,11 @@ func (s *State) toAttrs(ctx *hcl.EvalContext, hclAttrs hclsyntax.Attributes, sco
 		var err error
 		switch {
 		case isRef(value):
-			at.V = &schemaspec.Ref{V: value.GetAttr("__ref").AsString()}
+			at.V = &Ref{V: value.GetAttr("__ref").AsString()}
 		case value.Type() == ctyRawExpr:
-			at.V = value.EncapsulatedValue().(*schemaspec.RawExpr)
+			at.V = value.EncapsulatedValue().(*RawExpr)
 		case value.Type() == ctyTypeSpec:
-			at.V = value.EncapsulatedValue().(*schemaspec.Type)
+			at.V = value.EncapsulatedValue().(*Type)
 		case value.Type().IsTupleType():
 			at.V, err = extractListValue(value)
 		default:
@@ -295,13 +282,13 @@ func isRef(v cty.Value) bool {
 	return v.Type().IsObjectType() && v.Type().HasAttribute("__ref")
 }
 
-func extractListValue(value cty.Value) (*schemaspec.ListValue, error) {
-	lst := &schemaspec.ListValue{}
+func extractListValue(value cty.Value) (*ListValue, error) {
+	lst := &ListValue{}
 	it := value.ElementIterator()
 	for it.Next() {
 		_, v := it.Element()
 		if isRef(v) {
-			lst.V = append(lst.V, &schemaspec.Ref{V: v.GetAttr("__ref").AsString()})
+			lst.V = append(lst.V, &Ref{V: v.GetAttr("__ref").AsString()})
 			continue
 		}
 		litv, err := extractLiteralValue(v)
@@ -313,25 +300,25 @@ func extractListValue(value cty.Value) (*schemaspec.ListValue, error) {
 	return lst, nil
 }
 
-func extractLiteralValue(value cty.Value) (*schemaspec.LiteralValue, error) {
+func extractLiteralValue(value cty.Value) (*LiteralValue, error) {
 	switch value.Type() {
 	case ctySchemaLit:
-		return value.EncapsulatedValue().(*schemaspec.LiteralValue), nil
+		return value.EncapsulatedValue().(*LiteralValue), nil
 	case cty.String:
-		return &schemaspec.LiteralValue{V: strconv.Quote(value.AsString())}, nil
+		return &LiteralValue{V: strconv.Quote(value.AsString())}, nil
 	case cty.Number:
 		bf := value.AsBigFloat()
 		num, _ := bf.Float64()
-		return &schemaspec.LiteralValue{V: strconv.FormatFloat(num, 'f', -1, 64)}, nil
+		return &LiteralValue{V: strconv.FormatFloat(num, 'f', -1, 64)}, nil
 	case cty.Bool:
-		return &schemaspec.LiteralValue{V: strconv.FormatBool(value.True())}, nil
+		return &LiteralValue{V: strconv.FormatBool(value.True())}, nil
 	default:
 		return nil, fmt.Errorf("schemahcl: unsupported type %q", value.Type().GoString())
 	}
 }
 
-func (s *State) toResource(ctx *hcl.EvalContext, block *hclsyntax.Block, scope []string) (*schemaspec.Resource, error) {
-	spec := &schemaspec.Resource{
+func (s *State) toResource(ctx *hcl.EvalContext, block *hclsyntax.Block, scope []string) (*Resource, error) {
+	spec := &Resource{
 		Type: block.Type,
 	}
 	switch len(block.Labels) {
@@ -360,9 +347,9 @@ func (s *State) toResource(ctx *hcl.EvalContext, block *hclsyntax.Block, scope [
 	return spec, nil
 }
 
-// encode encodes the give *schemaspec.Resource into a byte slice containing an Atlas HCL
+// encode encodes the give *schemahcl.Resource into a byte slice containing an Atlas HCL
 // document representing it.
-func (s *State) encode(r *schemaspec.Resource) ([]byte, error) {
+func (s *State) encode(r *Resource) ([]byte, error) {
 	f := hclwrite.NewFile()
 	body := f.Body()
 	// If the resource has a Type then it is rendered as an HCL block.
@@ -385,7 +372,7 @@ func (s *State) encode(r *schemaspec.Resource) ([]byte, error) {
 	return buf.Bytes(), err
 }
 
-func (s *State) writeResource(b *schemaspec.Resource, body *hclwrite.Body) error {
+func (s *State) writeResource(b *Resource, body *hclwrite.Body) error {
 	blk := body.AppendNewBlock(b.Type, labels(b))
 	nb := blk.Body()
 	for _, attr := range b.Attrs {
@@ -401,7 +388,7 @@ func (s *State) writeResource(b *schemaspec.Resource, body *hclwrite.Body) error
 	return nil
 }
 
-func labels(r *schemaspec.Resource) []string {
+func labels(r *Resource) []string {
 	var l []string
 	if r.Qualifier != "" {
 		l = append(l, r.Qualifier)
@@ -412,13 +399,13 @@ func labels(r *schemaspec.Resource) []string {
 	return l
 }
 
-func (s *State) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
+func (s *State) writeAttr(attr *Attr, body *hclwrite.Body) error {
 	attr = normalizeLiterals(attr)
 	switch v := attr.V.(type) {
-	case *schemaspec.Ref:
+	case *Ref:
 		expr := strings.ReplaceAll(v.V, "$", "")
 		body.SetAttributeRaw(attr.K, hclRawTokens(expr))
-	case *schemaspec.Type:
+	case *Type:
 		if v.IsRef {
 			expr := strings.ReplaceAll(v.T, "$", "")
 			body.SetAttributeRaw(attr.K, hclRawTokens(expr))
@@ -435,13 +422,13 @@ func (s *State) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
 			return err
 		}
 		body.SetAttributeRaw(attr.K, hclRawTokens(st))
-	case *schemaspec.LiteralValue:
+	case *LiteralValue:
 		body.SetAttributeRaw(attr.K, hclRawTokens(v.V))
-	case *schemaspec.RawExpr:
+	case *RawExpr:
 		// TODO(rotemtam): the func name should be decided on contextual basis.
 		fnc := fmt.Sprintf("sql(%q)", v.X)
 		body.SetAttributeRaw(attr.K, hclRawTokens(fnc))
-	case *schemaspec.ListValue:
+	case *ListValue:
 		// Skip scanning nil slices ([]T(nil)) by default. Users that
 		// want to print empty lists, should use make([]T, 0) instead.
 		if v.V == nil {
@@ -450,10 +437,10 @@ func (s *State) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
 		lst := make([]string, 0, len(v.V))
 		for _, item := range v.V {
 			switch v := item.(type) {
-			case *schemaspec.Ref:
+			case *Ref:
 				expr := strings.ReplaceAll(v.V, "$", "")
 				lst = append(lst, expr)
-			case *schemaspec.LiteralValue:
+			case *LiteralValue:
 				lst = append(lst, v.V)
 			default:
 				return fmt.Errorf("cannot write elem type %T of attr %q to HCL list", v, attr)
@@ -468,20 +455,20 @@ func (s *State) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
 
 // normalizeLiterals transforms attributes with LiteralValue that cannot be
 // written as correct HCL into RawExpr.
-func normalizeLiterals(attr *schemaspec.Attr) *schemaspec.Attr {
-	lv, ok := attr.V.(*schemaspec.LiteralValue)
+func normalizeLiterals(attr *Attr) *Attr {
+	lv, ok := attr.V.(*LiteralValue)
 	if !ok {
 		return attr
 	}
 	exp := "x = " + lv.V
 	p := hclparse.NewParser()
 	if _, diag := p.ParseHCL([]byte(exp), ""); diag != nil {
-		return &schemaspec.Attr{K: attr.K, V: &schemaspec.RawExpr{X: lv.V}}
+		return &Attr{K: attr.K, V: &RawExpr{X: lv.V}}
 	}
 	return attr
 }
 
-func (s *State) findTypeSpec(t string) (*schemaspec.TypeSpec, bool) {
+func (s *State) findTypeSpec(t string) (*TypeSpec, bool) {
 	for _, v := range s.config.types {
 		if v.T == t {
 			return v, true
@@ -490,7 +477,7 @@ func (s *State) findTypeSpec(t string) (*schemaspec.TypeSpec, bool) {
 	return nil, false
 }
 
-func hclType(spec *schemaspec.TypeSpec, typ *schemaspec.Type) (string, error) {
+func hclType(spec *TypeSpec, typ *Type) (string, error) {
 	if spec.Format != nil {
 		return spec.Format(typ)
 	}
@@ -504,11 +491,11 @@ func hclType(spec *schemaspec.TypeSpec, typ *schemaspec.Type) (string, error) {
 			continue
 		}
 		switch val := arg.V.(type) {
-		case *schemaspec.LiteralValue:
+		case *LiteralValue:
 			args = append(args, val.V)
-		case *schemaspec.ListValue:
+		case *ListValue:
 			for _, li := range val.V {
-				lit, ok := li.(*schemaspec.LiteralValue)
+				lit, ok := li.(*LiteralValue)
 				if !ok {
 					return "", errors.New("expecting literal value")
 				}
@@ -523,7 +510,7 @@ func hclType(spec *schemaspec.TypeSpec, typ *schemaspec.Type) (string, error) {
 	return fmt.Sprintf("%s(%s)", spec.Name, strings.Join(args, ",")), nil
 }
 
-func findAttr(attrs []*schemaspec.Attr, k string) (*schemaspec.Attr, bool) {
+func findAttr(attrs []*Attr, k string) (*Attr, bool) {
 	for _, attr := range attrs {
 		if attr.K == k {
 			return attr, true
@@ -558,3 +545,8 @@ func hclRawList(items []string) hclwrite.Tokens {
 	})
 	return t
 }
+
+// Eval implements the Evaluator interface.
+func (f EvalFunc) Eval(p *hclparse.Parser, i interface{}, input map[string]string) error {
+	return f(p, i, input)
+}
diff --git a/schema/schemaspec/schemahcl/opts.go b/schemahcl/opts.go
similarity index 87%
rename from schema/schemaspec/schemahcl/opts.go
rename to schemahcl/opts.go
index 5548d44c..5cda1724 100644
--- a/schema/schemaspec/schemahcl/opts.go
+++ b/schemahcl/opts.go
@@ -8,8 +8,6 @@ import (
 	"fmt"
 	"reflect"
 
-	"ariga.io/atlas/schema/schemaspec"
-
 	"github.com/hashicorp/hcl/v2"
 	"github.com/zclconf/go-cty/cty"
 	"github.com/zclconf/go-cty/cty/function"
@@ -18,7 +16,7 @@ import (
 type (
 	// Config configures an unmarshaling.
 	Config struct {
-		types    []*schemaspec.TypeSpec
+		types    []*TypeSpec
 		newCtx   func() *hcl.EvalContext
 		pathVars map[string]map[string]cty.Value
 	}
@@ -71,7 +69,7 @@ func WithScopedEnums(path string, enums ...string) Option {
 }
 
 // WithTypes configures the list of given types as identifiers in the unmarshaling context.
-func WithTypes(typeSpecs []*schemaspec.TypeSpec) Option {
+func WithTypes(typeSpecs []*TypeSpec) Option {
 	newCtx := func() *hcl.EvalContext {
 		ctx := &hcl.EvalContext{
 			Variables: make(map[string]cty.Value),
@@ -81,7 +79,7 @@ func WithTypes(typeSpecs []*schemaspec.TypeSpec) Option {
 			typeSpec := ts
 			// If no required args exist, register the type as a variable in the HCL context.
 			if len(typeFuncReqArgs(typeSpec)) == 0 {
-				typ := &schemaspec.Type{T: typeSpec.T}
+				typ := &Type{T: typeSpec.T}
 				ctx.Variables[typeSpec.Name] = cty.CapsuleVal(ctyTypeSpec, typ)
 			}
 			// If func args exist, register the type as a function in HCL.
@@ -105,14 +103,14 @@ func rawExprImpl() function.Function {
 		},
 		Type: function.StaticReturnType(ctyRawExpr),
 		Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
-			t := &schemaspec.RawExpr{X: args[0].AsString()}
+			t := &RawExpr{X: args[0].AsString()}
 			return cty.CapsuleVal(ctyRawExpr, t), nil
 		},
 	})
 }
 
 // typeFuncSpec returns the HCL function for defining the type in the spec.
-func typeFuncSpec(typeSpec *schemaspec.TypeSpec) function.Function {
+func typeFuncSpec(typeSpec *TypeSpec) function.Function {
 	spec := &function.Spec{
 		Type: function.StaticReturnType(ctyTypeSpec),
 	}
@@ -143,9 +141,9 @@ func typeFuncSpec(typeSpec *schemaspec.TypeSpec) function.Function {
 }
 
 // typeFuncSpecImpl returns the function implementation for the HCL function spec.
-func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) function.ImplFunc {
+func typeFuncSpecImpl(spec *function.Spec, typeSpec *TypeSpec) function.ImplFunc {
 	return func(args []cty.Value, retType cty.Type) (cty.Value, error) {
-		t := &schemaspec.Type{
+		t := &Type{
 			T: typeSpec.T,
 		}
 		if len(args) > len(typeSpec.Attributes) && typeSpec.Attributes[len(typeSpec.Attributes)-1].Kind != reflect.Slice {
@@ -157,7 +155,7 @@ func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) functi
 		for _, attr := range typeFuncArgs(typeSpec) {
 			// If the attribute is a slice, read all remaining args into a list value.
 			if attr.Kind == reflect.Slice {
-				lst := &schemaspec.ListValue{}
+				lst := &ListValue{}
 				for _, arg := range args {
 					v, err := extractLiteralValue(arg)
 					if err != nil {
@@ -165,7 +163,7 @@ func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) functi
 					}
 					lst.V = append(lst.V, v)
 				}
-				t.Attrs = append(t.Attrs, &schemaspec.Attr{K: attr.Name, V: lst})
+				t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: lst})
 				break
 			}
 			if len(args) == 0 {
@@ -178,7 +176,7 @@ func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) functi
 			if err != nil {
 				return cty.NilVal, err
 			}
-			t.Attrs = append(t.Attrs, &schemaspec.Attr{K: attr.Name, V: v})
+			t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: v})
 		}
 		return cty.CapsuleVal(ctyTypeSpec, t), nil
 	}
@@ -186,8 +184,8 @@ func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) functi
 
 // typeFuncArgs returns the type attributes that are configured via arguments to the
 // type definition, for example precision and scale in a decimal definition, i.e `decimal(10,2)`.
-func typeFuncArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
-	var args []*schemaspec.TypeAttr
+func typeFuncArgs(spec *TypeSpec) []*TypeAttr {
+	var args []*TypeAttr
 	for _, attr := range spec.Attributes {
 		// TODO(rotemtam): this should be defined on the TypeSpec.
 		if attr.Name == "unsigned" {
@@ -201,8 +199,8 @@ func typeFuncArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
 // typeFuncReqArgs returns the required type attributes that are configured via arguments.
 // for instance, in MySQL a field may be defined as both `int` and `int(10)`, in this case
 // it is not a required parameter.
-func typeFuncReqArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
-	var args []*schemaspec.TypeAttr
+func typeFuncReqArgs(spec *TypeSpec) []*TypeAttr {
+	var args []*TypeAttr
 	for _, arg := range typeFuncArgs(spec) {
 		if arg.Required {
 			args = append(args, arg)
diff --git a/schema/schemaspec/spec.go b/schemahcl/spec.go
similarity index 85%
rename from schema/schemaspec/spec.go
rename to schemahcl/spec.go
index c3e491e3..86b42c95 100644
--- a/schema/schemaspec/spec.go
+++ b/schemahcl/spec.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package schemaspec
+package schemahcl
 
 import (
 	"fmt"
@@ -57,26 +57,6 @@ type (
 		V string
 	}
 
-	// Marshaler is the interface implemented by types that can marshal objects into a
-	// valid Atlas DDL representation.
-	Marshaler interface {
-		MarshalSpec(interface{}) ([]byte, error)
-	}
-
-	// Unmarshaler is the interface implemented by types that can unmarshal an Atlas DDL
-	// representation into an object.
-	Unmarshaler interface {
-		UnmarshalSpec([]byte, interface{}) error
-	}
-
-	// MarshalerFunc is the function type that is implemented by the MarshalSpec
-	// method of the Marshaler interface.
-	MarshalerFunc func(interface{}) ([]byte, error)
-
-	// UnmarshalerFunc is the function type that is implemented by the UnmarshalSpec
-	// method of the Unmarshaler interface.
-	UnmarshalerFunc func([]byte, interface{}) error
-
 	// TypeSpec represents a specification for defining a Type.
 	TypeSpec struct {
 		// Name is the identifier for the type in an Atlas DDL document.
@@ -189,7 +169,7 @@ func (a *Attr) Strings() ([]string, error) {
 	for _, item := range lst.V {
 		sv, err := StrVal(item)
 		if err != nil {
-			return nil, fmt.Errorf("schemaspec: failed parsing item %q to string: %w", item, err)
+			return nil, fmt.Errorf("schemahcl: failed parsing item %q to string: %w", item, err)
 		}
 		out = append(out, sv)
 	}
@@ -201,7 +181,7 @@ func (a *Attr) Strings() ([]string, error) {
 func (a *Attr) Bools() ([]bool, error) {
 	lst, ok := a.V.(*ListValue)
 	if !ok {
-		return nil, fmt.Errorf("schemaspec: attribute %q is not a list", a.K)
+		return nil, fmt.Errorf("schemahcl: attribute %q is not a list", a.K)
 	}
 	out := make([]bool, 0, len(lst.V))
 	for _, item := range lst.V {
@@ -243,11 +223,6 @@ func (f MarshalerFunc) MarshalSpec(v interface{}) ([]byte, error) {
 	return f(v)
 }
 
-// UnmarshalSpec implements Unmarshaler.
-func (f UnmarshalerFunc) UnmarshalSpec(data []byte, v interface{}) error {
-	return f(data, v)
-}
-
 func attrVal(attrs []*Attr, name string) (*Attr, bool) {
 	for _, attr := range attrs {
 		if attr.K == name {
@@ -273,7 +248,7 @@ func replaceOrAppendAttr(attrs []*Attr, attr *Attr) []*Attr {
 func StrVal(v Value) (string, error) {
 	lit, ok := v.(*LiteralValue)
 	if !ok {
-		return "", fmt.Errorf("schemaspec: expected %T to be LiteralValue", v)
+		return "", fmt.Errorf("schemahcl: expected %T to be LiteralValue", v)
 	}
 	return strconv.Unquote(lit.V)
 }
@@ -284,11 +259,11 @@ func StrVal(v Value) (string, error) {
 func BoolVal(v Value) (bool, error) {
 	lit, ok := v.(*LiteralValue)
 	if !ok {
-		return false, fmt.Errorf("schemaspec: expected %T to be LiteralValue", v)
+		return false, fmt.Errorf("schemahcl: expected %T to be LiteralValue", v)
 	}
 	b, err := strconv.ParseBool(lit.V)
 	if err != nil {
-		return false, fmt.Errorf("schemaspec: failed parsing %q as bool: %w", lit.V, err)
+		return false, fmt.Errorf("schemahcl: failed parsing %q as bool: %w", lit.V, err)
 	}
 	return b, nil
 }
@@ -310,6 +285,31 @@ func (*Ref) val()          {}
 func (*Type) val()         {}
 
 var (
-	_ Unmarshaler = UnmarshalerFunc(nil)
-	_ Marshaler   = MarshalerFunc(nil)
+	_ Marshaler = MarshalerFunc(nil)
 )
+
+// LitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values.
+func LitAttr(k, v string) *Attr {
+	return &Attr{
+		K: k,
+		V: &LiteralValue{V: v},
+	}
+}
+
+// StrLitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values
+// representing string literals.
+func StrLitAttr(k, v string) *Attr {
+	return LitAttr(k, strconv.Quote(v))
+}
+
+// ListAttr is a helper method for constructing *schemahcl.Attr instances that contain list values.
+func ListAttr(k string, litValues ...string) *Attr {
+	lv := &ListValue{}
+	for _, v := range litValues {
+		lv.V = append(lv.V, &LiteralValue{V: v})
+	}
+	return &Attr{
+		K: k,
+		V: lv,
+	}
+}
diff --git a/schema/schemaspec/schemahcl/testdata/a.hcl b/schemahcl/testdata/a.hcl
similarity index 100%
rename from schema/schemaspec/schemahcl/testdata/a.hcl
rename to schemahcl/testdata/a.hcl
diff --git a/schema/schemaspec/schemahcl/testdata/b.hcl b/schemahcl/testdata/b.hcl
similarity index 100%
rename from schema/schemaspec/schemahcl/testdata/b.hcl
rename to schemahcl/testdata/b.hcl
diff --git a/schema/schemaspec/schemahcl/testdata/variables.hcl b/schemahcl/testdata/variables.hcl
similarity index 100%
rename from schema/schemaspec/schemahcl/testdata/variables.hcl
rename to schemahcl/testdata/variables.hcl
diff --git a/schema/schemaspec/schemahcl/types.go b/schemahcl/types.go
similarity index 73%
rename from schema/schemaspec/schemahcl/types.go
rename to schemahcl/types.go
index 326ce69e..737fd4bb 100644
--- a/schema/schemaspec/schemahcl/types.go
+++ b/schemahcl/types.go
@@ -12,8 +12,6 @@ import (
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/internal/schemautil"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/go-openapi/inflect"
@@ -21,7 +19,7 @@ import (
 
 // PrintType returns the string representation of a column type which can be parsed
 // by the driver into a schema.Type.
-func (r *TypeRegistry) PrintType(typ *schemaspec.Type) (string, error) {
+func (r *TypeRegistry) PrintType(typ *Type) (string, error) {
 	spec, ok := r.findT(typ.T)
 	if !ok {
 		return "", fmt.Errorf("specutil: type %q not found in registry", typ.T)
@@ -46,11 +44,11 @@ func (r *TypeRegistry) PrintType(typ *schemaspec.Type) (string, error) {
 			continue
 		}
 		switch v := arg.V.(type) {
-		case *schemaspec.LiteralValue:
+		case *LiteralValue:
 			args = append(args, v.V)
-		case *schemaspec.ListValue:
+		case *ListValue:
 			for _, li := range v.V {
-				lit, ok := li.(*schemaspec.LiteralValue)
+				lit, ok := li.(*LiteralValue)
 				if !ok {
 					return "", fmt.Errorf("expecting literal value. got: %T", li)
 				}
@@ -70,10 +68,10 @@ func (r *TypeRegistry) PrintType(typ *schemaspec.Type) (string, error) {
 	return typ.T + mid + suffix, nil
 }
 
-// TypeRegistry is a collection of *schemaspec.TypeSpec.
+// TypeRegistry is a collection of *schemahcl.TypeSpec.
 type TypeRegistry struct {
-	r      []*schemaspec.TypeSpec
-	spec   func(schema.Type) (*schemaspec.Type, error)
+	r      []*TypeSpec
+	spec   func(schema.Type) (*Type, error)
 	parser func(string) (schema.Type, error)
 }
 
@@ -81,20 +79,20 @@ type TypeRegistry struct {
 // schema.Type as string.
 func WithFormatter(f func(schema.Type) (string, error)) TypeRegistryOption {
 	return func(registry *TypeRegistry) error {
-		registry.spec = func(t schema.Type) (*schemaspec.Type, error) {
+		registry.spec = func(t schema.Type) (*Type, error) {
 			s, err := f(t)
 			if err != nil {
 				return nil, fmt.Errorf("specutil: cannot format type %T: %w", t, err)
 			}
-			return &schemaspec.Type{T: s}, nil
+			return &Type{T: s}, nil
 		}
 		return nil
 	}
 }
 
 // WithSpecFunc configures the registry to use the given function for converting
-// a schema.Type to schemaspec.Type
-func WithSpecFunc(spec func(schema.Type) (*schemaspec.Type, error)) TypeRegistryOption {
+// a schema.Type to schemahcl.Type
+func WithSpecFunc(spec func(schema.Type) (*Type, error)) TypeRegistryOption {
 	return func(registry *TypeRegistry) error {
 		registry.spec = spec
 		return nil
@@ -111,7 +109,7 @@ func WithParser(parser func(string) (schema.Type, error)) TypeRegistryOption {
 }
 
 // Register adds one or more TypeSpec to the registry.
-func (r *TypeRegistry) Register(specs ...*schemaspec.TypeSpec) error {
+func (r *TypeRegistry) Register(specs ...*TypeSpec) error {
 	for _, s := range specs {
 		if err := validSpec(s); err != nil {
 			return fmt.Errorf("specutil: invalid typespec %q: %w", s.Name, err)
@@ -127,7 +125,7 @@ func (r *TypeRegistry) Register(specs ...*schemaspec.TypeSpec) error {
 	return nil
 }
 
-func validSpec(typeSpec *schemaspec.TypeSpec) error {
+func validSpec(typeSpec *TypeSpec) error {
 	var seenOptional bool
 	for i, attr := range typeSpec.Attributes {
 		if attr.Kind == reflect.Slice && i < len(typeSpec.Attributes)-1 {
@@ -145,7 +143,7 @@ func validSpec(typeSpec *schemaspec.TypeSpec) error {
 type TypeRegistryOption func(*TypeRegistry) error
 
 // WithSpecs configures the registry to register the given list of type specs.
-func WithSpecs(specs ...*schemaspec.TypeSpec) TypeRegistryOption {
+func WithSpecs(specs ...*TypeSpec) TypeRegistryOption {
 	return func(registry *TypeRegistry) error {
 		if err := registry.Register(specs...); err != nil {
 			return fmt.Errorf("failed registering types: %s", err)
@@ -167,7 +165,7 @@ func NewRegistry(opts ...TypeRegistryOption) *TypeRegistry {
 }
 
 // findName searches the registry for types that have the provided name.
-func (r *TypeRegistry) findName(name string) (*schemaspec.TypeSpec, bool) {
+func (r *TypeRegistry) findName(name string) (*TypeSpec, bool) {
 	for _, current := range r.r {
 		if current.Name == name {
 			return current, true
@@ -177,7 +175,7 @@ func (r *TypeRegistry) findName(name string) (*schemaspec.TypeSpec, bool) {
 }
 
 // findT searches the registry for types that have the provided T.
-func (r *TypeRegistry) findT(t string) (*schemaspec.TypeSpec, bool) {
+func (r *TypeRegistry) findT(t string) (*TypeSpec, bool) {
 	for _, current := range r.r {
 		if current.T == t {
 			return current, true
@@ -186,10 +184,10 @@ func (r *TypeRegistry) findT(t string) (*schemaspec.TypeSpec, bool) {
 	return nil, false
 }
 
-// Convert converts the schema.Type to a *schemaspec.Type.
-func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
+// Convert converts the schema.Type to a *schemahcl.Type.
+func (r *TypeRegistry) Convert(typ schema.Type) (*Type, error) {
 	if ut, ok := typ.(*schema.UnsupportedType); ok {
-		return &schemaspec.Type{
+		return &Type{
 			T: ut.T,
 		}, nil
 	}
@@ -207,7 +205,7 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
 	if typeSpec.ToSpec != nil {
 		return typeSpec.ToSpec(typ)
 	}
-	s := &schemaspec.Type{T: typeSpec.T}
+	s := &Type{T: typeSpec.T}
 	// Iterate the attributes in reverse order, so we can skip zero value and optional attrs.
 	for i := len(typeSpec.Attributes) - 1; i >= 0; i-- {
 		attr := typeSpec.Attributes[i]
@@ -227,14 +225,14 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
 				break
 			}
 			i := strconv.Itoa(v)
-			s.Attrs = append([]*schemaspec.Attr{schemautil.LitAttr(attr.Name, i)}, s.Attrs...)
+			s.Attrs = append([]*Attr{LitAttr(attr.Name, i)}, s.Attrs...)
 		case reflect.Bool:
 			v := field.Bool()
 			if !v && len(s.Attrs) == 0 {
 				break
 			}
 			b := strconv.FormatBool(v)
-			s.Attrs = append([]*schemaspec.Attr{schemautil.LitAttr(attr.Name, b)}, s.Attrs...)
+			s.Attrs = append([]*Attr{LitAttr(attr.Name, b)}, s.Attrs...)
 		case reflect.Slice:
 			lits := make([]string, 0, field.Len())
 			for i := 0; i < field.Len(); i++ {
@@ -244,7 +242,7 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
 				}
 				lits = append(lits, strconv.Quote(fi.String()))
 			}
-			s.Attrs = append([]*schemaspec.Attr{schemautil.ListAttr(attr.Name, lits...)}, s.Attrs...)
+			s.Attrs = append([]*Attr{ListAttr(attr.Name, lits...)}, s.Attrs...)
 		default:
 			return nil, fmt.Errorf("specutil: unsupported attr kind %s for attribute %q of %q", attr.Kind, attr.Name, typeSpec.Name)
 		}
@@ -252,7 +250,7 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
 	return s, nil
 }
 
-func (r *TypeRegistry) findType(rv reflect.Value) (*schemaspec.TypeSpec, bool) {
+func (r *TypeRegistry) findType(rv reflect.Value) (*TypeSpec, bool) {
 	tf := rv.FieldByName("T")
 	if tf.IsValid() && tf.Kind() == reflect.String {
 		name := tf.String()
@@ -266,7 +264,7 @@ func (r *TypeRegistry) findType(rv reflect.Value) (*schemaspec.TypeSpec, bool) {
 	return nil, false
 }
 
-func (r *TypeRegistry) findRType(rt reflect.Type) (*schemaspec.TypeSpec, bool) {
+func (r *TypeRegistry) findRType(rt reflect.Type) (*TypeSpec, bool) {
 	for _, ts := range r.Specs() {
 		if ts.RType != nil && ts.RType == rt {
 			return ts, true
@@ -276,19 +274,19 @@ func (r *TypeRegistry) findRType(rt reflect.Type) (*schemaspec.TypeSpec, bool) {
 }
 
 // Specs returns the TypeSpecs in the registry.
-func (r *TypeRegistry) Specs() []*schemaspec.TypeSpec {
+func (r *TypeRegistry) Specs() []*TypeSpec {
 	return r.r
 }
 
-// Type converts a *schemaspec.Type into a schema.Type.
-func (r *TypeRegistry) Type(typ *schemaspec.Type, extra []*schemaspec.Attr) (schema.Type, error) {
+// Type converts a *schemahcl.Type into a schema.Type.
+func (r *TypeRegistry) Type(typ *Type, extra []*Attr) (schema.Type, error) {
 	typeSpec, ok := r.findT(typ.T)
 	if !ok {
 		return r.parser(typ.T)
 	}
 	nfa := typeNonFuncArgs(typeSpec)
 	picked := pickTypeAttrs(extra, nfa)
-	cp := &schemaspec.Type{
+	cp := &Type{
 		T: typ.T,
 	}
 	cp.Attrs = appendIfNotExist(typ.Attrs, picked)
@@ -302,45 +300,45 @@ func (r *TypeRegistry) Type(typ *schemaspec.Type, extra []*schemaspec.Attr) (sch
 	return r.parser(printType)
 }
 
-// TypeSpecOption configures a schemaspec.TypeSpec.
-type TypeSpecOption func(*schemaspec.TypeSpec)
+// TypeSpecOption configures a schemahcl.TypeSpec.
+type TypeSpecOption func(*TypeSpec)
 
 // WithAttributes returns an attributes TypeSpecOption.
-func WithAttributes(attrs ...*schemaspec.TypeAttr) TypeSpecOption {
-	return func(spec *schemaspec.TypeSpec) {
+func WithAttributes(attrs ...*TypeAttr) TypeSpecOption {
+	return func(spec *TypeSpec) {
 		spec.Attributes = attrs
 	}
 }
 
 // WithTypeFormatter allows overriding the Format function for the Type.
-func WithTypeFormatter(f func(*schemaspec.Type) (string, error)) TypeSpecOption {
-	return func(spec *schemaspec.TypeSpec) {
+func WithTypeFormatter(f func(*Type) (string, error)) TypeSpecOption {
+	return func(spec *TypeSpec) {
 		spec.Format = f
 	}
 }
 
 // WithFromSpec allows configuring the FromSpec convert function using functional options.
-func WithFromSpec(f func(*schemaspec.Type) (schema.Type, error)) TypeSpecOption {
-	return func(spec *schemaspec.TypeSpec) {
+func WithFromSpec(f func(*Type) (schema.Type, error)) TypeSpecOption {
+	return func(spec *TypeSpec) {
 		spec.FromSpec = f
 	}
 }
 
 // WithToSpec allows configuring the ToSpec convert function using functional options.
-func WithToSpec(f func(schema.Type) (*schemaspec.Type, error)) TypeSpecOption {
-	return func(spec *schemaspec.TypeSpec) {
+func WithToSpec(f func(schema.Type) (*Type, error)) TypeSpecOption {
+	return func(spec *TypeSpec) {
 		spec.ToSpec = f
 	}
 }
 
-// TypeSpec returns a TypeSpec with the provided name.
-func TypeSpec(name string, opts ...TypeSpecOption) *schemaspec.TypeSpec {
+// NewTypeSpec returns a TypeSpec with the provided name.
+func NewTypeSpec(name string, opts ...TypeSpecOption) *TypeSpec {
 	return AliasTypeSpec(name, name, opts...)
 }
 
 // AliasTypeSpec returns a TypeSpec with the provided name.
-func AliasTypeSpec(name, dbType string, opts ...TypeSpecOption) *schemaspec.TypeSpec {
-	ts := &schemaspec.TypeSpec{
+func AliasTypeSpec(name, dbType string, opts ...TypeSpecOption) *TypeSpec {
+	ts := &TypeSpec{
 		Name: name,
 		T:    dbType,
 	}
@@ -351,8 +349,8 @@ func AliasTypeSpec(name, dbType string, opts ...TypeSpecOption) *schemaspec.Type
 }
 
 // SizeTypeAttr returns a TypeAttr for a size attribute.
-func SizeTypeAttr(required bool) *schemaspec.TypeAttr {
-	return &schemaspec.TypeAttr{
+func SizeTypeAttr(required bool) *TypeAttr {
+	return &TypeAttr{
 		Name:     "size",
 		Kind:     reflect.Int,
 		Required: required,
@@ -361,8 +359,8 @@ func SizeTypeAttr(required bool) *schemaspec.TypeAttr {
 
 // typeNonFuncArgs returns the type attributes that are NOT configured via arguments to the
 // type definition, `int unsigned`.
-func typeNonFuncArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
-	var args []*schemaspec.TypeAttr
+func typeNonFuncArgs(spec *TypeSpec) []*TypeAttr {
+	var args []*TypeAttr
 	for _, attr := range spec.Attributes {
 		// TODO(rotemtam): this should be defined on the TypeSpec.
 		if attr.Name == "unsigned" {
@@ -373,12 +371,12 @@ func typeNonFuncArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
 }
 
 // pickTypeAttrs returns the relevant Attrs matching the wanted TypeAttrs.
-func pickTypeAttrs(src []*schemaspec.Attr, wanted []*schemaspec.TypeAttr) []*schemaspec.Attr {
+func pickTypeAttrs(src []*Attr, wanted []*TypeAttr) []*Attr {
 	keys := make(map[string]struct{})
 	for _, w := range wanted {
 		keys[w.Name] = struct{}{}
 	}
-	var picked []*schemaspec.Attr
+	var picked []*Attr
 	for _, attr := range src {
 		if _, ok := keys[attr.K]; ok {
 			picked = append(picked, attr)
@@ -387,7 +385,7 @@ func pickTypeAttrs(src []*schemaspec.Attr, wanted []*schemaspec.TypeAttr) []*sch
 	return picked
 }
 
-func appendIfNotExist(base []*schemaspec.Attr, additional []*schemaspec.Attr) []*schemaspec.Attr {
+func appendIfNotExist(base []*Attr, additional []*Attr) []*Attr {
 	exists := make(map[string]struct{})
 	for _, attr := range base {
 		exists[attr.K] = struct{}{}
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index 3ef08f66..9543802a 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -10,7 +10,7 @@ import (
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
@@ -135,9 +135,9 @@ func Column(spec *sqlspec.Column, conv ConvertTypeFunc) (*schema.Column, error)
 	}
 	if spec.Default != nil {
 		switch d := spec.Default.(type) {
-		case *schemaspec.LiteralValue:
+		case *schemahcl.LiteralValue:
 			out.Default = &schema.Literal{V: d.V}
-		case *schemaspec.RawExpr:
+		case *schemahcl.RawExpr:
 			out.Default = &schema.RawExpr{X: d.X}
 		default:
 			return nil, fmt.Errorf("unsupported value type for default: %T", d)
@@ -344,7 +344,7 @@ func FromTable(t *schema.Table, colFn ColumnSpecFunc, pkFn PrimaryKeySpecFunc, i
 
 // FromPrimaryKey converts schema.Index to a sqlspec.PrimaryKey.
 func FromPrimaryKey(s *schema.Index) (*sqlspec.PrimaryKey, error) {
-	c := make([]*schemaspec.Ref, 0, len(s.Parts))
+	c := make([]*schemahcl.Ref, 0, len(s.Parts))
 	for _, v := range s.Parts {
 		c = append(c, ColumnRef(v.C.Name))
 	}
@@ -363,8 +363,8 @@ func FromColumn(col *schema.Column, columnTypeSpec ColumnTypeSpecFunc) (*sqlspec
 		Name: col.Name,
 		Type: ct.Type,
 		Null: col.Type.Null,
-		DefaultExtension: schemaspec.DefaultExtension{
-			Extra: schemaspec.Resource{Attrs: ct.DefaultExtension.Extra.Attrs},
+		DefaultExtension: schemahcl.DefaultExtension{
+			Extra: schemahcl.Resource{Attrs: ct.DefaultExtension.Extra.Attrs},
 		},
 	}
 	if col.Default != nil {
@@ -379,10 +379,10 @@ func FromColumn(col *schema.Column, columnTypeSpec ColumnTypeSpecFunc) (*sqlspec
 }
 
 // FromGenExpr returns the spec for a generated expression.
-func FromGenExpr(x schema.GeneratedExpr, t func(string) string) *schemaspec.Resource {
-	return &schemaspec.Resource{
+func FromGenExpr(x schema.GeneratedExpr, t func(string) string) *schemahcl.Resource {
+	return &schemahcl.Resource{
 		Type: "as",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*schemahcl.Attr{
 			StrAttr("expr", x.Expr),
 			VarAttr("type", t(x.Type)),
 		},
@@ -390,7 +390,7 @@ func FromGenExpr(x schema.GeneratedExpr, t func(string) string) *schemaspec.Reso
 }
 
 // ConvertGenExpr converts the "as" attribute or the block under the given resource.
-func ConvertGenExpr(r *schemaspec.Resource, c *schema.Column, t func(string) string) error {
+func ConvertGenExpr(r *schemahcl.Resource, c *schema.Column, t func(string) string) error {
 	asA, okA := r.Attr("as")
 	asR, okR := r.Resource("as")
 	switch {
@@ -421,20 +421,20 @@ func ConvertGenExpr(r *schemaspec.Resource, c *schema.Column, t func(string) str
 	return nil
 }
 
-func toValue(expr schema.Expr) (schemaspec.Value, error) {
+func toValue(expr schema.Expr) (schemahcl.Value, error) {
 	var (
 		v   string
 		err error
 	)
 	switch expr := expr.(type) {
 	case *schema.RawExpr:
-		return &schemaspec.RawExpr{X: expr.X}, nil
+		return &schemahcl.RawExpr{X: expr.X}, nil
 	case *schema.Literal:
 		v, err = normalizeQuotes(expr.V)
 		if err != nil {
 			return nil, err
 		}
-		return &schemaspec.LiteralValue{V: v}, nil
+		return &schemahcl.LiteralValue{V: v}, nil
 	default:
 		return nil, fmt.Errorf("converting expr %T to literal value", expr)
 	}
@@ -485,8 +485,8 @@ func FromIndex(idx *schema.Index, partFns ...func(*schema.IndexPart, *sqlspec.In
 	return spec, nil
 }
 
-func columnsOnly(idx *schema.Index) ([]*schemaspec.Ref, bool) {
-	parts := make([]*schemaspec.Ref, len(idx.Parts))
+func columnsOnly(idx *schema.Index) ([]*schemahcl.Ref, bool) {
+	parts := make([]*schemahcl.Ref, len(idx.Parts))
 	for i, p := range idx.Parts {
 		if p.C == nil || p.Desc {
 			return nil, false
@@ -498,11 +498,11 @@ func columnsOnly(idx *schema.Index) ([]*schemaspec.Ref, bool) {
 
 // FromForeignKey converts schema.ForeignKey to sqlspec.ForeignKey.
 func FromForeignKey(s *schema.ForeignKey) (*sqlspec.ForeignKey, error) {
-	c := make([]*schemaspec.Ref, 0, len(s.Columns))
+	c := make([]*schemahcl.Ref, 0, len(s.Columns))
 	for _, v := range s.Columns {
 		c = append(c, ColumnRef(v.Name))
 	}
-	r := make([]*schemaspec.Ref, 0, len(s.RefColumns))
+	r := make([]*schemahcl.Ref, 0, len(s.RefColumns))
 	for _, v := range s.RefColumns {
 		ref := ColumnRef(v.Name)
 		if s.Table != s.RefTable {
@@ -516,10 +516,10 @@ func FromForeignKey(s *schema.ForeignKey) (*sqlspec.ForeignKey, error) {
 		RefColumns: r,
 	}
 	if s.OnUpdate != "" {
-		fk.OnUpdate = &schemaspec.Ref{V: Var(string(s.OnUpdate))}
+		fk.OnUpdate = &schemahcl.Ref{V: Var(string(s.OnUpdate))}
 	}
 	if s.OnDelete != "" {
-		fk.OnDelete = &schemaspec.Ref{V: Var(string(s.OnDelete))}
+		fk.OnDelete = &schemahcl.Ref{V: Var(string(s.OnDelete))}
 	}
 	return fk, nil
 }
@@ -533,7 +533,7 @@ func FromCheck(s *schema.Check) *sqlspec.Check {
 }
 
 // SchemaName returns the name from a ref to a schema.
-func SchemaName(ref *schemaspec.Ref) (string, error) {
+func SchemaName(ref *schemahcl.Ref) (string, error) {
 	if ref == nil {
 		return "", errors.New("missing 'schema' attribute")
 	}
@@ -545,7 +545,7 @@ func SchemaName(ref *schemaspec.Ref) (string, error) {
 }
 
 // ColumnByRef returns a column from the table by its reference.
-func ColumnByRef(t *schema.Table, ref *schemaspec.Ref) (*schema.Column, error) {
+func ColumnByRef(t *schema.Table, ref *schemahcl.Ref) (*schema.Column, error) {
 	s := strings.Split(ref.V, "$column.")
 	if len(s) != 2 {
 		return nil, fmt.Errorf("specutil: failed to extract column name from %q", ref)
@@ -557,7 +557,7 @@ func ColumnByRef(t *schema.Table, ref *schemaspec.Ref) (*schema.Column, error) {
 	return c, nil
 }
 
-func externalRef(ref *schemaspec.Ref, sch *schema.Schema) (*schema.Table, *schema.Column, error) {
+func externalRef(ref *schemahcl.Ref, sch *schema.Schema) (*schema.Table, *schema.Column, error) {
 	tbl, err := findTable(ref, sch)
 	if err != nil {
 		return nil, nil, err
@@ -572,7 +572,7 @@ func externalRef(ref *schemaspec.Ref, sch *schema.Schema) (*schema.Table, *schem
 // findTable finds the table referenced by ref in the provided schema. If the table
 // is not in the provided schema.Schema other schemas in the connected schema.Realm
 // are searched as well.
-func findTable(ref *schemaspec.Ref, sch *schema.Schema) (*schema.Table, error) {
+func findTable(ref *schemahcl.Ref, sch *schema.Schema) (*schema.Table, error) {
 	qualifier, tblName, err := tableName(ref)
 	if err != nil {
 		return nil, err
@@ -600,7 +600,7 @@ func findTable(ref *schemaspec.Ref, sch *schema.Schema) (*schema.Table, error) {
 	return tbl, nil
 }
 
-func tableName(ref *schemaspec.Ref) (qualifier, name string, err error) {
+func tableName(ref *schemahcl.Ref) (qualifier, name string, err error) {
 	s := strings.Split(ref.V, "$column.")
 	if len(s) != 2 {
 		return "", "", fmt.Errorf("sqlspec: failed to split by column name from %q", ref)
@@ -618,27 +618,27 @@ func tableName(ref *schemaspec.Ref) (qualifier, name string, err error) {
 	return
 }
 
-func isLocalRef(r *schemaspec.Ref) bool {
+func isLocalRef(r *schemahcl.Ref) bool {
 	return strings.HasPrefix(r.V, "$column")
 }
 
 // ColumnRef returns the reference of a column by its name.
-func ColumnRef(cName string) *schemaspec.Ref {
-	return &schemaspec.Ref{V: "$column." + cName}
+func ColumnRef(cName string) *schemahcl.Ref {
+	return &schemahcl.Ref{V: "$column." + cName}
 }
 
-func externalColRef(cName string, tName string) *schemaspec.Ref {
-	return &schemaspec.Ref{V: "$table." + tName + ".$column." + cName}
+func externalColRef(cName string, tName string) *schemahcl.Ref {
+	return &schemahcl.Ref{V: "$table." + tName + ".$column." + cName}
 }
 
-// SchemaRef returns the schemaspec.Ref to the schema with the given name.
-func SchemaRef(n string) *schemaspec.Ref {
-	return &schemaspec.Ref{V: "$schema." + n}
+// SchemaRef returns the schemahcl.Ref to the schema with the given name.
+func SchemaRef(n string) *schemahcl.Ref {
+	return &schemahcl.Ref{V: "$schema." + n}
 }
 
 // Attrer is the interface that wraps the Attr method.
 type Attrer interface {
-	Attr(string) (*schemaspec.Attr, bool)
+	Attr(string) (*schemahcl.Attr, bool)
 }
 
 // convertCommentFromSpec converts a spec comment attribute to a schema element attribute.
@@ -654,7 +654,7 @@ func convertCommentFromSpec(spec Attrer, attrs *[]schema.Attr) error {
 }
 
 // convertCommentFromSchema converts a schema element comment attribute to a spec comment attribute.
-func convertCommentFromSchema(src []schema.Attr, trgt *[]*schemaspec.Attr) {
+func convertCommentFromSchema(src []schema.Attr, trgt *[]*schemahcl.Attr) {
 	var c schema.Comment
 	if sqlx.Has(src, &c) {
 		*trgt = append(*trgt, StrAttr("comment", c.Text))
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 9dfaae47..35734225 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -8,80 +8,80 @@ import (
 	"fmt"
 	"strconv"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+	"github.com/hashicorp/hcl/v2/hclparse"
 )
 
-// StrAttr is a helper method for constructing *schemaspec.Attr of type string.
-func StrAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// StrAttr is a helper method for constructing *schemahcl.Attr of type string.
+func StrAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: strconv.Quote(v)},
+		V: &schemahcl.LiteralValue{V: strconv.Quote(v)},
 	}
 }
 
-// BoolAttr is a helper method for constructing *schemaspec.Attr of type bool.
-func BoolAttr(k string, v bool) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// BoolAttr is a helper method for constructing *schemahcl.Attr of type bool.
+func BoolAttr(k string, v bool) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: strconv.FormatBool(v)},
+		V: &schemahcl.LiteralValue{V: strconv.FormatBool(v)},
 	}
 }
 
-// IntAttr is a helper method for constructing *schemaspec.Attr with the numeric value of v.
-func IntAttr(k string, v int) *schemaspec.Attr {
+// IntAttr is a helper method for constructing *schemahcl.Attr with the numeric value of v.
+func IntAttr(k string, v int) *schemahcl.Attr {
 	return Int64Attr(k, int64(v))
 }
 
-// Int64Attr is a helper method for constructing *schemaspec.Attr with the numeric value of v.
-func Int64Attr(k string, v int64) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// Int64Attr is a helper method for constructing *schemahcl.Attr with the numeric value of v.
+func Int64Attr(k string, v int64) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: strconv.FormatInt(v, 10)},
+		V: &schemahcl.LiteralValue{V: strconv.FormatInt(v, 10)},
 	}
 }
 
-// LitAttr is a helper method for constructing *schemaspec.Attr instances that contain literal values.
-func LitAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// LitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values.
+func LitAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.LiteralValue{V: v},
+		V: &schemahcl.LiteralValue{V: v},
 	}
 }
 
-// RawAttr is a helper method for constructing *schemaspec.Attr instances that contain sql expressions.
-func RawAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// RawAttr is a helper method for constructing *schemahcl.Attr instances that contain sql expressions.
+func RawAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.RawExpr{X: v},
+		V: &schemahcl.RawExpr{X: v},
 	}
 }
 
-// VarAttr is a helper method for constructing *schemaspec.Attr instances that contain a variable reference.
-func VarAttr(k, v string) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// VarAttr is a helper method for constructing *schemahcl.Attr instances that contain a variable reference.
+func VarAttr(k, v string) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
-		V: &schemaspec.Ref{V: v},
+		V: &schemahcl.Ref{V: v},
 	}
 }
 
-// RefAttr is a helper method for constructing *schemaspec.Attr instances that contain a reference.
-func RefAttr(k string, r *schemaspec.Ref) *schemaspec.Attr {
-	return &schemaspec.Attr{
+// RefAttr is a helper method for constructing *schemahcl.Attr instances that contain a reference.
+func RefAttr(k string, r *schemahcl.Ref) *schemahcl.Attr {
+	return &schemahcl.Attr{
 		K: k,
 		V: r,
 	}
 }
 
-// ListAttr is a helper method for constructing *schemaspec.Attr instances that contain list values.
-func ListAttr(k string, litValues ...string) *schemaspec.Attr {
-	lv := &schemaspec.ListValue{}
+// ListAttr is a helper method for constructing *schemahcl.Attr instances that contain list values.
+func ListAttr(k string, litValues ...string) *schemahcl.Attr {
+	lv := &schemahcl.ListValue{}
 	for _, v := range litValues {
-		lv.V = append(lv.V, &schemaspec.LiteralValue{V: v})
+		lv.V = append(lv.V, &schemahcl.LiteralValue{V: v})
 	}
-	return &schemaspec.Attr{
+	return &schemahcl.Attr{
 		K: k,
 		V: lv,
 	}
@@ -92,9 +92,9 @@ type doc struct {
 	Schemas []*sqlspec.Schema `spec:"schema"`
 }
 
-// Marshal marshals v into an Atlas DDL document using a schemaspec.Marshaler. Marshal uses the given
+// Marshal marshals v into an Atlas DDL document using a schemahcl.Marshaler. Marshal uses the given
 // schemaSpec function to convert a *schema.Schema into *sqlspec.Schema and []*sqlspec.Table.
-func Marshal(v interface{}, marshaler schemaspec.Marshaler, schemaSpec func(schem *schema.Schema) (*sqlspec.Schema, []*sqlspec.Table, error)) ([]byte, error) {
+func Marshal(v interface{}, marshaler schemahcl.Marshaler, schemaSpec func(schem *schema.Schema) (*sqlspec.Schema, []*sqlspec.Table, error)) ([]byte, error) {
 	d := &doc{}
 	switch s := v.(type) {
 	case *schema.Schema:
@@ -144,32 +144,14 @@ func QualifyDuplicates(tableSpecs []*sqlspec.Table) error {
 	return nil
 }
 
-// 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, evaluator schemahcl.Evaluator, v interface{}, input map[string]string,
-	convertTable ConvertTableFunc) error {
-	var d doc
-	if err := evaluator.Eval(data, &d, input); err != nil {
-		return err
-	}
-	switch v := v.(type) {
-	case *schema.Realm:
-		err := Scan(v, d.Schemas, d.Tables, convertTable)
-		if err != nil {
-			return fmt.Errorf("specutil: failed converting to *schema.Realm: %w", err)
-		}
-	case *schema.Schema:
-		if len(d.Schemas) != 1 {
-			return fmt.Errorf("specutil: expecting document to contain a single schema, got %d", len(d.Schemas))
-		}
-		var r schema.Realm
-		if err := Scan(&r, d.Schemas, d.Tables, convertTable); err != nil {
-			return err
+// HCLBytesFunc returns a helper that evaluates an HCL document from a byte slice instead
+// of from an hclparse.Parser instance.
+func HCLBytesFunc(ev schemahcl.Evaluator) func(b []byte, v interface{}, inp map[string]string) error {
+	return func(b []byte, v interface{}, inp map[string]string) error {
+		parser := hclparse.NewParser()
+		if _, diag := parser.ParseHCL(b, ""); diag.HasErrors() {
+			return diag
 		}
-		r.Schemas[0].Realm = nil
-		*v = *r.Schemas[0]
-	default:
-		return fmt.Errorf("specutil: failed unmarshaling spec. %T is not supported", v)
+		return ev.Eval(parser, v, inp)
 	}
-	return nil
 }
diff --git a/sql/mysql/internal/mysqlcheck/datadepend.go b/sql/mysql/internal/mysqlcheck/datadepend.go
index d316fd47..9897d5d5 100644
--- a/sql/mysql/internal/mysqlcheck/datadepend.go
+++ b/sql/mysql/internal/mysqlcheck/datadepend.go
@@ -10,7 +10,7 @@ import (
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlcheck"
@@ -26,7 +26,7 @@ type (
 		NotNull *bool `spec:"not_null,omitempty"`
 
 		// Allow drivers to extend the configuration.
-		schemaspec.DefaultExtension
+		schemahcl.DefaultExtension
 	}
 
 	// DataDepend checks data-dependent changes for MySQL.
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 9f06b9f1..6e5f917a 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -10,12 +10,12 @@ import (
 	"reflect"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+	"github.com/hashicorp/hcl/v2/hclparse"
 )
 
 type doc struct {
@@ -24,9 +24,9 @@ type doc struct {
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(data []byte, v interface{}, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
 	var d doc
-	if err := hclState.Eval(data, &d, input); err != nil {
+	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
 	}
 	switch v := v.(type) {
@@ -63,8 +63,8 @@ func evalSpec(data []byte, v interface{}, input map[string]string) error {
 	return nil
 }
 
-// MarshalSpec marshals v into an Atlas DDL document using a schemaspec.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemaspec.Marshaler) ([]byte, error) {
+// MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
+func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
 	return specutil.Marshal(v, marshaler, schemaSpec)
 }
 
@@ -76,16 +76,16 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_update", specutil.ReferenceVars...),
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
-	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	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) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
 	EvalHCL = schemahcl.EvalFunc(evalSpec)
+
+	// EvalHCLBytes is a helper that evaluates an HCL document from a byte slice instead
+	// of from an hclparse.Parser instance.
+	EvalHCLBytes = specutil.HCLBytesFunc(EvalHCL)
 )
 
 // convertTable converts a sqlspec.Table to a schema.Table. Table conversion is done without converting
@@ -167,7 +167,7 @@ func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error
 		return nil, err
 	}
 	if attr, ok := spec.Attr("on_update"); ok {
-		exp, ok := attr.V.(*schemaspec.RawExpr)
+		exp, ok := attr.V.(*schemahcl.RawExpr)
 		if !ok {
 			return nil, fmt.Errorf(`unexpected type %T for atrribute "on_update"`, attr.V)
 		}
@@ -363,66 +363,66 @@ var TypeRegistry = schemahcl.NewRegistry(
 	schemahcl.WithFormatter(FormatType),
 	schemahcl.WithParser(ParseType),
 	schemahcl.WithSpecs(
-		&schemaspec.TypeSpec{
+		&schemahcl.TypeSpec{
 			Name: TypeEnum,
 			T:    TypeEnum,
-			Attributes: []*schemaspec.TypeAttr{
+			Attributes: []*schemahcl.TypeAttr{
 				{Name: "values", Kind: reflect.Slice, Required: true},
 			},
 			RType: reflect.TypeOf(schema.EnumType{}),
 		},
-		&schemaspec.TypeSpec{
+		&schemahcl.TypeSpec{
 			Name: TypeSet,
 			T:    TypeSet,
-			Attributes: []*schemaspec.TypeAttr{
+			Attributes: []*schemahcl.TypeAttr{
 				{Name: "values", Kind: reflect.Slice, Required: true},
 			},
 			RType: reflect.TypeOf(SetType{}),
 		},
-		schemahcl.TypeSpec(TypeBool),
-		schemahcl.TypeSpec(TypeBoolean),
-		schemahcl.TypeSpec(TypeBit, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeTinyInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeSmallInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeMediumInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeBigInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeDecimal, schemahcl.WithAttributes(unsignedTypeAttr(), &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeNumeric, schemahcl.WithAttributes(unsignedTypeAttr(), &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeFloat, schemahcl.WithAttributes(unsignedTypeAttr(), &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeDouble, schemahcl.WithAttributes(unsignedTypeAttr(), &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeReal, schemahcl.WithAttributes(unsignedTypeAttr(), &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeTimestamp, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeDate, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeTime, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeDateTime, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeYear, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeVarchar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
-		schemahcl.TypeSpec(TypeChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeVarBinary, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeBinary, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeBlob, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeTinyBlob),
-		schemahcl.TypeSpec(TypeMediumBlob),
-		schemahcl.TypeSpec(TypeLongBlob),
-		schemahcl.TypeSpec(TypeJSON),
-		schemahcl.TypeSpec(TypeText, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeTinyText),
-		schemahcl.TypeSpec(TypeMediumText),
-		schemahcl.TypeSpec(TypeLongText),
-		schemahcl.TypeSpec(TypeGeometry),
-		schemahcl.TypeSpec(TypePoint),
-		schemahcl.TypeSpec(TypeMultiPoint),
-		schemahcl.TypeSpec(TypeLineString),
-		schemahcl.TypeSpec(TypeMultiLineString),
-		schemahcl.TypeSpec(TypePolygon),
-		schemahcl.TypeSpec(TypeMultiPolygon),
-		schemahcl.TypeSpec(TypeGeometryCollection),
+		schemahcl.NewTypeSpec(TypeBool),
+		schemahcl.NewTypeSpec(TypeBoolean),
+		schemahcl.NewTypeSpec(TypeBit, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeTinyInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeSmallInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeMediumInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeBigInt, schemahcl.WithAttributes(unsignedTypeAttr(), schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeDecimal, schemahcl.WithAttributes(unsignedTypeAttr(), &schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeNumeric, schemahcl.WithAttributes(unsignedTypeAttr(), &schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeFloat, schemahcl.WithAttributes(unsignedTypeAttr(), &schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeDouble, schemahcl.WithAttributes(unsignedTypeAttr(), &schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeReal, schemahcl.WithAttributes(unsignedTypeAttr(), &schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeTimestamp, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeDate, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeTime, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeDateTime, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeYear, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeVarchar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
+		schemahcl.NewTypeSpec(TypeChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeVarBinary, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeBinary, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeBlob, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeTinyBlob),
+		schemahcl.NewTypeSpec(TypeMediumBlob),
+		schemahcl.NewTypeSpec(TypeLongBlob),
+		schemahcl.NewTypeSpec(TypeJSON),
+		schemahcl.NewTypeSpec(TypeText, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeTinyText),
+		schemahcl.NewTypeSpec(TypeMediumText),
+		schemahcl.NewTypeSpec(TypeLongText),
+		schemahcl.NewTypeSpec(TypeGeometry),
+		schemahcl.NewTypeSpec(TypePoint),
+		schemahcl.NewTypeSpec(TypeMultiPoint),
+		schemahcl.NewTypeSpec(TypeLineString),
+		schemahcl.NewTypeSpec(TypeMultiLineString),
+		schemahcl.NewTypeSpec(TypePolygon),
+		schemahcl.NewTypeSpec(TypeMultiPolygon),
+		schemahcl.NewTypeSpec(TypeGeometryCollection),
 	),
 )
 
-func unsignedTypeAttr() *schemaspec.TypeAttr {
-	return &schemaspec.TypeAttr{
+func unsignedTypeAttr() *schemahcl.TypeAttr {
+	return &schemahcl.TypeAttr{
 		Name: "unsigned",
 		Kind: reflect.Bool,
 	}
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 3caab3b8..f21cf52f 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -10,12 +10,12 @@ import (
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+	"github.com/hashicorp/hcl/v2/hclparse"
 )
 
 type (
@@ -26,21 +26,21 @@ type (
 	}
 	// Enum holds a specification for an enum, that can be referenced as a column type.
 	Enum struct {
-		Name   string          `spec:",name"`
-		Schema *schemaspec.Ref `spec:"schema"`
-		Values []string        `spec:"values"`
-		schemaspec.DefaultExtension
+		Name   string         `spec:",name"`
+		Schema *schemahcl.Ref `spec:"schema"`
+		Values []string       `spec:"values"`
+		schemahcl.DefaultExtension
 	}
 )
 
 func init() {
-	schemaspec.Register("enum", &Enum{})
+	schemahcl.Register("enum", &Enum{})
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(data []byte, v interface{}, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
 	var d doc
-	if err := hclState.Eval(data, &d, input); err != nil {
+	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
 	}
 	switch v := v.(type) {
@@ -74,8 +74,8 @@ func evalSpec(data []byte, v interface{}, input map[string]string) error {
 	return nil
 }
 
-// MarshalSpec marshals v into an Atlas DDL document using a schemaspec.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemaspec.Marshaler) ([]byte, error) {
+// MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
+func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
 	var d doc
 	switch s := v.(type) {
 	case *schema.Schema:
@@ -116,16 +116,16 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_update", specutil.ReferenceVars...),
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
-	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	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) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
 	EvalHCL = schemahcl.EvalFunc(evalSpec)
+
+	// EvalHCLBytes is a helper that evaluates an HCL document from a byte slice instead
+	// of from an hclparse.Parser instance.
+	EvalHCLBytes = specutil.HCLBytesFunc(EvalHCL)
 )
 
 // convertTable converts a sqlspec.Table to a schema.Table. Table conversion is done without converting
@@ -143,17 +143,17 @@ func convertTable(spec *sqlspec.Table, parent *schema.Schema) (*schema.Table, er
 }
 
 // convertPartition converts and appends the partition block into the table attributes if exists.
-func convertPartition(spec schemaspec.Resource, table *schema.Table) error {
+func convertPartition(spec schemahcl.Resource, table *schema.Table) error {
 	r, ok := spec.Resource("partition")
 	if !ok {
 		return nil
 	}
 	var p struct {
-		Type    string            `spec:"type"`
-		Columns []*schemaspec.Ref `spec:"columns"`
+		Type    string           `spec:"type"`
+		Columns []*schemahcl.Ref `spec:"columns"`
 		Parts   []*struct {
-			Expr   string          `spec:"expr"`
-			Column *schemaspec.Ref `spec:"column"`
+			Expr   string         `spec:"expr"`
+			Column *schemahcl.Ref `spec:"column"`
 		} `spec:"by"`
 	}
 	if err := r.As(&p); err != nil {
@@ -199,29 +199,29 @@ func convertPartition(spec schemaspec.Resource, table *schema.Table) error {
 }
 
 // fromPartition returns the resource spec for representing the partition block.
-func fromPartition(p Partition) *schemaspec.Resource {
-	key := &schemaspec.Resource{
+func fromPartition(p Partition) *schemahcl.Resource {
+	key := &schemahcl.Resource{
 		Type: "partition",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*schemahcl.Attr{
 			specutil.VarAttr("type", strings.ToUpper(specutil.Var(p.T))),
 		},
 	}
-	columns, ok := func() (*schemaspec.ListValue, bool) {
-		parts := make([]schemaspec.Value, 0, len(p.Parts))
+	columns, ok := func() (*schemahcl.ListValue, bool) {
+		parts := make([]schemahcl.Value, 0, len(p.Parts))
 		for _, p := range p.Parts {
 			if p.C == nil {
 				return nil, false
 			}
 			parts = append(parts, specutil.ColumnRef(p.C.Name))
 		}
-		return &schemaspec.ListValue{V: parts}, true
+		return &schemahcl.ListValue{V: parts}, true
 	}()
 	if ok {
-		key.Attrs = append(key.Attrs, &schemaspec.Attr{K: "columns", V: columns})
+		key.Attrs = append(key.Attrs, &schemahcl.Attr{K: "columns", V: columns})
 		return key
 	}
 	for _, p := range p.Parts {
-		part := &schemaspec.Resource{Type: "by"}
+		part := &schemahcl.Resource{Type: "by"}
 		switch {
 		case p.C != nil:
 			part.Attrs = append(part.Attrs, specutil.RefAttr("column", specutil.ColumnRef(p.C.Name)))
@@ -255,7 +255,7 @@ func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error
 	return c, nil
 }
 
-func convertIdentity(r *schemaspec.Resource) (*Identity, error) {
+func convertIdentity(r *schemahcl.Resource) (*Identity, error) {
 	var spec struct {
 		Generation string `spec:"generated"`
 		Start      int64  `spec:"start"`
@@ -276,8 +276,8 @@ func convertIdentity(r *schemaspec.Resource) (*Identity, error) {
 
 // fixDefaultQuotes fixes the quotes on the Default field to be single quotes
 // instead of double quotes.
-func fixDefaultQuotes(value schemaspec.Value) error {
-	lv, ok := value.(*schemaspec.LiteralValue)
+func fixDefaultQuotes(value schemahcl.Value) error {
+	lv, ok := value.(*schemahcl.LiteralValue)
 	if !ok {
 		return nil
 	}
@@ -368,7 +368,7 @@ func convertEnums(tbls []*sqlspec.Table, enums []*Enum, sch *schema.Schema) erro
 }
 
 // resolveEnum returns the first Enum that matches the name referenced by the given column type.
-func resolveEnum(ref *schemaspec.Type, enums []*Enum) (*Enum, error) {
+func resolveEnum(ref *schemahcl.Type, enums []*Enum) (*Enum, error) {
 	n, err := enumName(ref)
 	if err != nil {
 		return nil, err
@@ -382,7 +382,7 @@ func resolveEnum(ref *schemaspec.Type, enums []*Enum) (*Enum, error) {
 }
 
 // enumName extracts the name of the referenced Enum from the reference string.
-func enumName(ref *schemaspec.Type) (string, error) {
+func enumName(ref *schemahcl.Type) (string, error) {
 	s := strings.Split(ref.T, "$enum.")
 	if len(s) != 2 {
 		return "", fmt.Errorf("postgres: failed to extract enum name from %q", ref.T)
@@ -391,8 +391,8 @@ func enumName(ref *schemaspec.Type) (string, error) {
 }
 
 // enumRef returns a reference string to the given enum name.
-func enumRef(n string) *schemaspec.Ref {
-	return &schemaspec.Ref{
+func enumRef(n string) *schemahcl.Ref {
+	return &schemahcl.Ref{
 		V: "$enum." + n,
 	}
 }
@@ -478,10 +478,10 @@ func columnSpec(c *schema.Column, _ *schema.Table) (*sqlspec.Column, error) {
 }
 
 // fromIdentity returns the resource spec for representing the identity attributes.
-func fromIdentity(i *Identity) *schemaspec.Resource {
-	id := &schemaspec.Resource{
+func fromIdentity(i *Identity) *schemahcl.Resource {
+	id := &schemahcl.Resource{
 		Type: "identity",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*schemahcl.Attr{
 			specutil.VarAttr("generated", strings.ToUpper(specutil.Var(i.Generation))),
 		},
 	}
@@ -500,7 +500,7 @@ func fromIdentity(i *Identity) *schemaspec.Resource {
 func columnTypeSpec(t schema.Type) (*sqlspec.Column, error) {
 	// Handle postgres enum types. They cannot be put into the TypeRegistry since their name is dynamic.
 	if e, ok := t.(*schema.EnumType); ok {
-		return &sqlspec.Column{Type: &schemaspec.Type{
+		return &sqlspec.Column{Type: &schemahcl.Type{
 			T:     enumRef(e.T).V,
 			IsRef: true,
 		}}, nil
@@ -517,75 +517,75 @@ var TypeRegistry = schemahcl.NewRegistry(
 	schemahcl.WithSpecFunc(typeSpec),
 	schemahcl.WithParser(ParseType),
 	schemahcl.WithSpecs(
-		schemahcl.TypeSpec(TypeBit, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64})),
-		schemahcl.AliasTypeSpec("bit_varying", TypeBitVar, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64})),
-		schemahcl.TypeSpec(TypeVarChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeBit, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "len", Kind: reflect.Int64})),
+		schemahcl.AliasTypeSpec("bit_varying", TypeBitVar, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "len", Kind: reflect.Int64})),
+		schemahcl.NewTypeSpec(TypeVarChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
 		schemahcl.AliasTypeSpec("character_varying", TypeCharVar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
-		schemahcl.TypeSpec(TypeCharacter, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
-		schemahcl.TypeSpec(TypeInt2),
-		schemahcl.TypeSpec(TypeInt4),
-		schemahcl.TypeSpec(TypeInt8),
-		schemahcl.TypeSpec(TypeInt),
-		schemahcl.TypeSpec(TypeInteger),
-		schemahcl.TypeSpec(TypeSmallInt),
-		schemahcl.TypeSpec(TypeBigInt),
-		schemahcl.TypeSpec(TypeText),
-		schemahcl.TypeSpec(TypeBoolean),
-		schemahcl.TypeSpec(TypeBool),
-		schemahcl.TypeSpec(TypeBytea),
-		schemahcl.TypeSpec(TypeCIDR),
-		schemahcl.TypeSpec(TypeInet),
-		schemahcl.TypeSpec(TypeMACAddr),
-		schemahcl.TypeSpec(TypeMACAddr8),
-		schemahcl.TypeSpec(TypeCircle),
-		schemahcl.TypeSpec(TypeLine),
-		schemahcl.TypeSpec(TypeLseg),
-		schemahcl.TypeSpec(TypeBox),
-		schemahcl.TypeSpec(TypePath),
-		schemahcl.TypeSpec(TypePoint),
-		schemahcl.TypeSpec(TypeDate),
-		schemahcl.TypeSpec(TypeTime, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
-		schemahcl.TypeSpec(TypeTimeTZ, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
-		schemahcl.TypeSpec(TypeTimestampTZ, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
-		schemahcl.TypeSpec(TypeTimestamp, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
+		schemahcl.NewTypeSpec(TypeChar, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
+		schemahcl.NewTypeSpec(TypeCharacter, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(true))),
+		schemahcl.NewTypeSpec(TypeInt2),
+		schemahcl.NewTypeSpec(TypeInt4),
+		schemahcl.NewTypeSpec(TypeInt8),
+		schemahcl.NewTypeSpec(TypeInt),
+		schemahcl.NewTypeSpec(TypeInteger),
+		schemahcl.NewTypeSpec(TypeSmallInt),
+		schemahcl.NewTypeSpec(TypeBigInt),
+		schemahcl.NewTypeSpec(TypeText),
+		schemahcl.NewTypeSpec(TypeBoolean),
+		schemahcl.NewTypeSpec(TypeBool),
+		schemahcl.NewTypeSpec(TypeBytea),
+		schemahcl.NewTypeSpec(TypeCIDR),
+		schemahcl.NewTypeSpec(TypeInet),
+		schemahcl.NewTypeSpec(TypeMACAddr),
+		schemahcl.NewTypeSpec(TypeMACAddr8),
+		schemahcl.NewTypeSpec(TypeCircle),
+		schemahcl.NewTypeSpec(TypeLine),
+		schemahcl.NewTypeSpec(TypeLseg),
+		schemahcl.NewTypeSpec(TypeBox),
+		schemahcl.NewTypeSpec(TypePath),
+		schemahcl.NewTypeSpec(TypePoint),
+		schemahcl.NewTypeSpec(TypeDate),
+		schemahcl.NewTypeSpec(TypeTime, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
+		schemahcl.NewTypeSpec(TypeTimeTZ, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
+		schemahcl.NewTypeSpec(TypeTimestampTZ, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
+		schemahcl.NewTypeSpec(TypeTimestamp, schemahcl.WithAttributes(precisionTypeAttr()), formatTime()),
 		schemahcl.AliasTypeSpec("double_precision", TypeDouble),
-		schemahcl.TypeSpec(TypeReal),
-		schemahcl.TypeSpec(TypeFloat8),
-		schemahcl.TypeSpec(TypeFloat4),
-		schemahcl.TypeSpec(TypeNumeric, schemahcl.WithAttributes(precisionTypeAttr(), &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeDecimal, schemahcl.WithAttributes(precisionTypeAttr(), &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeSmallSerial),
-		schemahcl.TypeSpec(TypeSerial),
-		schemahcl.TypeSpec(TypeBigSerial),
-		schemahcl.TypeSpec(TypeSerial2),
-		schemahcl.TypeSpec(TypeSerial4),
-		schemahcl.TypeSpec(TypeSerial8),
-		schemahcl.TypeSpec(TypeXML),
-		schemahcl.TypeSpec(TypeJSON),
-		schemahcl.TypeSpec(TypeJSONB),
-		schemahcl.TypeSpec(TypeUUID),
-		schemahcl.TypeSpec(TypeMoney),
-		schemahcl.TypeSpec("hstore"),
-		schemahcl.TypeSpec("sql", schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "def", Required: true, Kind: reflect.String})),
+		schemahcl.NewTypeSpec(TypeReal),
+		schemahcl.NewTypeSpec(TypeFloat8),
+		schemahcl.NewTypeSpec(TypeFloat4),
+		schemahcl.NewTypeSpec(TypeNumeric, schemahcl.WithAttributes(precisionTypeAttr(), &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeDecimal, schemahcl.WithAttributes(precisionTypeAttr(), &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeSmallSerial),
+		schemahcl.NewTypeSpec(TypeSerial),
+		schemahcl.NewTypeSpec(TypeBigSerial),
+		schemahcl.NewTypeSpec(TypeSerial2),
+		schemahcl.NewTypeSpec(TypeSerial4),
+		schemahcl.NewTypeSpec(TypeSerial8),
+		schemahcl.NewTypeSpec(TypeXML),
+		schemahcl.NewTypeSpec(TypeJSON),
+		schemahcl.NewTypeSpec(TypeJSONB),
+		schemahcl.NewTypeSpec(TypeUUID),
+		schemahcl.NewTypeSpec(TypeMoney),
+		schemahcl.NewTypeSpec("hstore"),
+		schemahcl.NewTypeSpec("sql", schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "def", Required: true, Kind: reflect.String})),
 	),
-	schemahcl.WithSpecs(func() (specs []*schemaspec.TypeSpec) {
+	schemahcl.WithSpecs(func() (specs []*schemahcl.TypeSpec) {
 		opts := []schemahcl.TypeSpecOption{
-			schemahcl.WithToSpec(func(t schema.Type) (*schemaspec.Type, error) {
+			schemahcl.WithToSpec(func(t schema.Type) (*schemahcl.Type, error) {
 				i, ok := t.(*IntervalType)
 				if !ok {
 					return nil, fmt.Errorf("postgres: unexpected interval type %T", t)
 				}
-				spec := &schemaspec.Type{T: TypeInterval}
+				spec := &schemahcl.Type{T: TypeInterval}
 				if i.F != "" {
 					spec.T = specutil.Var(strings.ToLower(i.F))
 				}
 				if p := i.Precision; p != nil && *p != defaultTimePrecision {
-					spec.Attrs = []*schemaspec.Attr{specutil.IntAttr("precision", *p)}
+					spec.Attrs = []*schemahcl.Attr{specutil.IntAttr("precision", *p)}
 				}
 				return spec, nil
 			}),
-			schemahcl.WithFromSpec(func(t *schemaspec.Type) (schema.Type, error) {
+			schemahcl.WithFromSpec(func(t *schemahcl.Type) (schema.Type, error) {
 				i := &IntervalType{T: TypeInterval}
 				if t.T != TypeInterval {
 					i.F = specutil.FromVar(t.T)
@@ -603,24 +603,24 @@ var TypeRegistry = schemahcl.NewRegistry(
 			}),
 		}
 		for _, f := range []string{"interval", "second", "day to second", "hour to second", "minute to second"} {
-			specs = append(specs, schemahcl.TypeSpec(specutil.Var(f), append(opts, schemahcl.WithAttributes(precisionTypeAttr()))...))
+			specs = append(specs, schemahcl.NewTypeSpec(specutil.Var(f), append(opts, schemahcl.WithAttributes(precisionTypeAttr()))...))
 		}
 		for _, f := range []string{"year", "month", "day", "hour", "minute", "year to month", "day to hour", "day to minute", "hour to minute"} {
-			specs = append(specs, schemahcl.TypeSpec(specutil.Var(f), opts...))
+			specs = append(specs, schemahcl.NewTypeSpec(specutil.Var(f), opts...))
 		}
 		return specs
 	}()...),
 )
 
-func precisionTypeAttr() *schemaspec.TypeAttr {
-	return &schemaspec.TypeAttr{
+func precisionTypeAttr() *schemahcl.TypeAttr {
+	return &schemahcl.TypeAttr{
 		Name:     "precision",
 		Kind:     reflect.Int,
 		Required: false,
 	}
 }
 
-func attr(typ *schemaspec.Type, key string) (*schemaspec.Attr, bool) {
+func attr(typ *schemahcl.Type, key string) (*schemahcl.Attr, bool) {
 	for _, a := range typ.Attrs {
 		if a.K == key {
 			return a, true
@@ -629,11 +629,11 @@ func attr(typ *schemaspec.Type, key string) (*schemaspec.Attr, bool) {
 	return nil, false
 }
 
-func typeSpec(t schema.Type) (*schemaspec.Type, error) {
+func typeSpec(t schema.Type) (*schemahcl.Type, error) {
 	if t, ok := t.(*schema.TimeType); ok && t.T != TypeDate {
-		spec := &schemaspec.Type{T: timeAlias(t.T)}
+		spec := &schemahcl.Type{T: timeAlias(t.T)}
 		if p := t.Precision; p != nil && *p != defaultTimePrecision {
-			spec.Attrs = []*schemaspec.Attr{specutil.IntAttr("precision", *p)}
+			spec.Attrs = []*schemahcl.Attr{specutil.IntAttr("precision", *p)}
 		}
 		return spec, nil
 	}
@@ -641,12 +641,12 @@ func typeSpec(t schema.Type) (*schemaspec.Type, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &schemaspec.Type{T: s}, nil
+	return &schemahcl.Type{T: s}, nil
 }
 
 // formatTime overrides the default printing logic done by schemahcl.hclType.
 func formatTime() schemahcl.TypeSpecOption {
-	return schemahcl.WithTypeFormatter(func(t *schemaspec.Type) (string, error) {
+	return schemahcl.WithTypeFormatter(func(t *schemahcl.Type) (string, error) {
 		a, ok := attr(t, "precision")
 		if !ok {
 			return t.T, nil
diff --git a/sql/sqlcheck/datadepend/datadepend.go b/sql/sqlcheck/datadepend/datadepend.go
index ce17e032..ecff0ae1 100644
--- a/sql/sqlcheck/datadepend/datadepend.go
+++ b/sql/sqlcheck/datadepend/datadepend.go
@@ -9,7 +9,7 @@ import (
 	"fmt"
 	"sync"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlcheck"
 )
@@ -23,7 +23,7 @@ type (
 		UniqueIndex *bool `spec:"drop_schema,omitempty"`
 
 		// Allow drivers to extend the configuration.
-		schemaspec.DefaultExtension
+		schemahcl.DefaultExtension
 	}
 
 	// Analyzer checks data-dependent changes.
diff --git a/sql/sqlcheck/destructive/destructive.go b/sql/sqlcheck/destructive/destructive.go
index 44deec23..0874c642 100644
--- a/sql/sqlcheck/destructive/destructive.go
+++ b/sql/sqlcheck/destructive/destructive.go
@@ -9,7 +9,7 @@ import (
 	"fmt"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlcheck"
@@ -29,7 +29,7 @@ type (
 		DropColumn *bool `spec:"drop_column,omitempty"`
 
 		// Allow drivers to extend the configuration.
-		schemaspec.DefaultExtension
+		schemahcl.DefaultExtension
 	}
 
 	// Analyzer checks for destructive changes.
diff --git a/sql/sqlclient/client.go b/sql/sqlclient/client.go
index f5ee1a41..ac922908 100644
--- a/sql/sqlclient/client.go
+++ b/sql/sqlclient/client.go
@@ -13,11 +13,9 @@ import (
 	"net/url"
 	"sync"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
-	"ariga.io/atlas/sql/schema"
-
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/migrate"
+	"ariga.io/atlas/sql/schema"
 )
 
 type (
@@ -41,7 +39,7 @@ type (
 
 		// Marshal and Evaluator functions for decoding
 		// and encoding the schema documents.
-		schemaspec.Marshaler
+		schemahcl.Marshaler
 		schemahcl.Evaluator
 
 		// A func to open a migrate.Driver with a given schema.ExecQuerier. Used when creating a TxClient.
@@ -225,7 +223,7 @@ type (
 		parser     URLParser
 		flavours   []string
 		codec      interface {
-			schemaspec.Marshaler
+			schemahcl.Marshaler
 			schemahcl.Evaluator
 		}
 	}
@@ -252,10 +250,10 @@ func RegisterURLParser(p URLParser) RegisterOption {
 
 // RegisterCodec registers static codec for attaching into
 // the client after it is opened.
-func RegisterCodec(m schemaspec.Marshaler, e schemahcl.Evaluator) RegisterOption {
+func RegisterCodec(m schemahcl.Marshaler, e schemahcl.Evaluator) RegisterOption {
 	return func(opts *registerOptions) {
 		opts.codec = struct {
-			schemaspec.Marshaler
+			schemahcl.Marshaler
 			schemahcl.Evaluator
 		}{
 			Marshaler: m,
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 1641ae6e..19d77134 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -393,7 +393,7 @@ func (i *inspect) databases(ctx context.Context, opts *schema.InspectRealmOption
 		query = databasesQuery
 	)
 	if opts != nil && len(opts.Schemas) > 0 {
-		query += " WHERE name IN (" + strings.Repeat("?, ", len(opts.Schemas)-1) + "?)"
+		query = fmt.Sprintf(databasesQueryArgs, strings.Repeat("?, ", len(opts.Schemas)-1)+"?")
 		for _, s := range opts.Schemas {
 			args = append(args, s)
 		}
@@ -704,7 +704,8 @@ const (
 	// Name of main database file.
 	mainFile = "main"
 	// Query to list attached database files.
-	databasesQuery = "SELECT `name`, `file` FROM pragma_database_list()"
+	databasesQuery     = "SELECT `name`, `file` FROM pragma_database_list() WHERE `name` <> 'temp'"
+	databasesQueryArgs = "SELECT `name`, `file` FROM pragma_database_list() WHERE `name` IN (%s)"
 	// Query to list database tables.
 	tablesQuery = "SELECT `name`, `sql` FROM sqlite_master WHERE `type` = 'table' AND `name` NOT LIKE 'sqlite_%'"
 	// Query to list table information.
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index 88170a6b..44891637 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -5,25 +5,49 @@
 package sqlite
 
 import (
+	"fmt"
 	"reflect"
 	"strconv"
 	"strings"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+	"github.com/hashicorp/hcl/v2/hclparse"
 )
 
 // 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)
+func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
+	var d doc
+	if err := hclState.Eval(p, &d, input); err != nil {
+		return err
+	}
+	switch v := v.(type) {
+	case *schema.Realm:
+		err := specutil.Scan(v, d.Schemas, d.Tables, convertTable)
+		if err != nil {
+			return fmt.Errorf("specutil: failed converting to *schema.Realm: %w", err)
+		}
+	case *schema.Schema:
+		if len(d.Schemas) != 1 {
+			return fmt.Errorf("specutil: expecting document to contain a single schema, got %d", len(d.Schemas))
+		}
+		var r schema.Realm
+		if err := specutil.Scan(&r, d.Schemas, d.Tables, convertTable); err != nil {
+			return err
+		}
+		r.Schemas[0].Realm = nil
+		*v = *r.Schemas[0]
+	default:
+		return fmt.Errorf("specutil: failed unmarshaling spec. %T is not supported", v)
+	}
+	return nil
 }
 
-// MarshalSpec marshals v into an Atlas DDL document using a schemaspec.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemaspec.Marshaler) ([]byte, error) {
+// MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
+func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
 	return specutil.Marshal(v, marshaler, schemaSpec)
 }
 
@@ -133,35 +157,35 @@ var TypeRegistry = schemahcl.NewRegistry(
 	schemahcl.WithFormatter(FormatType),
 	schemahcl.WithParser(ParseType),
 	schemahcl.WithSpecs(
-		schemahcl.TypeSpec(TypeReal, schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec(TypeBlob, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeText, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec(TypeInteger, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("int", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("tinyint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("smallint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("mediumint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("bigint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeReal, schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec(TypeBlob, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeText, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec(TypeInteger, schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("int", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("tinyint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("smallint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("mediumint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("bigint", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
 		schemahcl.AliasTypeSpec("unsigned_big_int", "unsigned big int", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("int2", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("int8", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("double", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("int2", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("int8", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("double", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
 		schemahcl.AliasTypeSpec("double_precision", "double precision", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("float", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("character", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("varchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("float", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("character", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("varchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
 		schemahcl.AliasTypeSpec("varying_character", "varying character", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("nchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("nchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
 		schemahcl.AliasTypeSpec("native_character", "native character", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("nvarchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("clob", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
-		schemahcl.TypeSpec("numeric", schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec("decimal", schemahcl.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
-		schemahcl.TypeSpec("boolean"),
-		schemahcl.TypeSpec("date"),
-		schemahcl.TypeSpec("datetime"),
-		schemahcl.TypeSpec("json"),
-		schemahcl.TypeSpec("uuid"),
+		schemahcl.NewTypeSpec("nvarchar", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("clob", schemahcl.WithAttributes(schemahcl.SizeTypeAttr(false))),
+		schemahcl.NewTypeSpec("numeric", schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec("decimal", schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemahcl.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		schemahcl.NewTypeSpec("boolean"),
+		schemahcl.NewTypeSpec("date"),
+		schemahcl.NewTypeSpec("datetime"),
+		schemahcl.NewTypeSpec("json"),
+		schemahcl.NewTypeSpec("uuid"),
 	),
 )
 
@@ -172,16 +196,16 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_update", specutil.ReferenceVars...),
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
-	// UnmarshalHCL unmarshals an Atlas HCL DDL document into v.
-	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) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
 	EvalHCL = schemahcl.EvalFunc(evalSpec)
+
+	// EvalHCLBytes is a helper that evaluates an HCL document from a byte slice instead
+	// of from an hclparse.Parser instance.
+	EvalHCLBytes = specutil.HCLBytesFunc(EvalHCL)
 )
 
 // storedOrVirtual returns a STORED or VIRTUAL
@@ -192,3 +216,8 @@ func storedOrVirtual(s string) string {
 	}
 	return s
 }
+
+type doc struct {
+	Tables  []*sqlspec.Table  `spec:"table"`
+	Schemas []*sqlspec.Schema `spec:"schema"`
+}
diff --git a/sql/sqlspec/migratespec.go b/sql/sqlspec/migratespec.go
index c835defc..9b81e321 100644
--- a/sql/sqlspec/migratespec.go
+++ b/sql/sqlspec/migratespec.go
@@ -5,7 +5,7 @@
 package sqlspec
 
 import (
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 )
 
 type (
@@ -30,6 +30,6 @@ type (
 )
 
 func init() {
-	schemaspec.Register("modify_table", &ModifyTable{})
-	schemaspec.Register("add_column", &AddColumn{})
+	schemahcl.Register("modify_table", &ModifyTable{})
+	schemahcl.Register("add_column", &AddColumn{})
 }
diff --git a/sql/sqlspec/sqlspec.go b/sql/sqlspec/sqlspec.go
index 974f261f..21625713 100644
--- a/sql/sqlspec/sqlspec.go
+++ b/sql/sqlspec/sqlspec.go
@@ -5,76 +5,76 @@
 package sqlspec
 
 import (
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 )
 
 type (
 	// Schema holds a specification for a Schema.
 	Schema struct {
 		Name string `spec:"name,name"`
-		schemaspec.DefaultExtension
+		schemahcl.DefaultExtension
 	}
 
 	// Table holds a specification for an SQL table.
 	Table struct {
-		Name        string          `spec:",name"`
-		Qualifier   string          `spec:",qualifier"`
-		Schema      *schemaspec.Ref `spec:"schema"`
-		Columns     []*Column       `spec:"column"`
-		PrimaryKey  *PrimaryKey     `spec:"primary_key"`
-		ForeignKeys []*ForeignKey   `spec:"foreign_key"`
-		Indexes     []*Index        `spec:"index"`
-		Checks      []*Check        `spec:"check"`
-		schemaspec.DefaultExtension
+		Name        string         `spec:",name"`
+		Qualifier   string         `spec:",qualifier"`
+		Schema      *schemahcl.Ref `spec:"schema"`
+		Columns     []*Column      `spec:"column"`
+		PrimaryKey  *PrimaryKey    `spec:"primary_key"`
+		ForeignKeys []*ForeignKey  `spec:"foreign_key"`
+		Indexes     []*Index       `spec:"index"`
+		Checks      []*Check       `spec:"check"`
+		schemahcl.DefaultExtension
 	}
 
 	// Column holds a specification for a column in an SQL table.
 	Column struct {
-		Name    string           `spec:",name"`
-		Null    bool             `spec:"null"`
-		Type    *schemaspec.Type `spec:"type"`
-		Default schemaspec.Value `spec:"default"`
-		schemaspec.DefaultExtension
+		Name    string          `spec:",name"`
+		Null    bool            `spec:"null"`
+		Type    *schemahcl.Type `spec:"type"`
+		Default schemahcl.Value `spec:"default"`
+		schemahcl.DefaultExtension
 	}
 
 	// PrimaryKey holds a specification for the primary key of a table.
 	PrimaryKey struct {
-		Columns []*schemaspec.Ref `spec:"columns"`
-		schemaspec.DefaultExtension
+		Columns []*schemahcl.Ref `spec:"columns"`
+		schemahcl.DefaultExtension
 	}
 
 	// Index holds a specification for the index key of a table.
 	Index struct {
-		Name    string            `spec:",name"`
-		Unique  bool              `spec:"unique,omitempty"`
-		Parts   []*IndexPart      `spec:"on"`
-		Columns []*schemaspec.Ref `spec:"columns"`
-		schemaspec.DefaultExtension
+		Name    string           `spec:",name"`
+		Unique  bool             `spec:"unique,omitempty"`
+		Parts   []*IndexPart     `spec:"on"`
+		Columns []*schemahcl.Ref `spec:"columns"`
+		schemahcl.DefaultExtension
 	}
 
 	// IndexPart holds a specification for the index key part.
 	IndexPart struct {
-		Desc   bool            `spec:"desc,omitempty"`
-		Column *schemaspec.Ref `spec:"column"`
-		Expr   string          `spec:"expr,omitempty"`
-		schemaspec.DefaultExtension
+		Desc   bool           `spec:"desc,omitempty"`
+		Column *schemahcl.Ref `spec:"column"`
+		Expr   string         `spec:"expr,omitempty"`
+		schemahcl.DefaultExtension
 	}
 
 	// Check holds a specification for a check constraint on a table.
 	Check struct {
 		Name string `spec:",name"`
 		Expr string `spec:"expr"`
-		schemaspec.DefaultExtension
+		schemahcl.DefaultExtension
 	}
 
 	// ForeignKey holds a specification for the Foreign key of a table.
 	ForeignKey struct {
-		Symbol     string            `spec:",name"`
-		Columns    []*schemaspec.Ref `spec:"columns"`
-		RefColumns []*schemaspec.Ref `spec:"ref_columns"`
-		OnUpdate   *schemaspec.Ref   `spec:"on_update"`
-		OnDelete   *schemaspec.Ref   `spec:"on_delete"`
-		schemaspec.DefaultExtension
+		Symbol     string           `spec:",name"`
+		Columns    []*schemahcl.Ref `spec:"columns"`
+		RefColumns []*schemahcl.Ref `spec:"ref_columns"`
+		OnUpdate   *schemahcl.Ref   `spec:"on_update"`
+		OnDelete   *schemahcl.Ref   `spec:"on_delete"`
+		schemahcl.DefaultExtension
 	}
 
 	// Type represents a database agnostic column type.
@@ -82,6 +82,6 @@ type (
 )
 
 func init() {
-	schemaspec.Register("table", &Table{})
-	schemaspec.Register("schema", &Schema{})
+	schemahcl.Register("table", &Table{})
+	schemahcl.Register("schema", &Schema{})
 }


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/project_test.go b/cmd/atlas/internal/cmdapi/project_test.go
index 8e65af93..c4830849 100644
--- a/cmd/atlas/internal/cmdapi/project_test.go
+++ b/cmd/atlas/internal/cmdapi/project_test.go
@@ -10,7 +10,7 @@ import (
 	"sort"
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"github.com/stretchr/testify/require"
 )
 
@@ -58,12 +58,12 @@ env "local" {
 				URL:    "file://migrations",
 				Format: formatAtlas,
 			},
-			DefaultExtension: schemaspec.DefaultExtension{
-				Extra: schemaspec.Resource{
-					Attrs: []*schemaspec.Attr{
-						{K: "bool", V: &schemaspec.LiteralValue{V: "true"}},
-						{K: "integer", V: &schemaspec.LiteralValue{V: "42"}},
-						{K: "str", V: &schemaspec.LiteralValue{V: `"hello"`}},
+			DefaultExtension: schemahcl.DefaultExtension{
+				Extra: schemahcl.Resource{
+					Attrs: []*schemahcl.Attr{
+						{K: "bool", V: &schemahcl.LiteralValue{V: "true"}},
+						{K: "integer", V: &schemahcl.LiteralValue{V: "42"}},
+						{K: "str", V: &schemahcl.LiteralValue{V: `"hello"`}},
 					},
 				},
 			},
diff --git a/internal/integration/cockroach_test.go b/internal/integration/cockroach_test.go
index c8b3ae84..d635aee4 100644
--- a/internal/integration/cockroach_test.go
+++ b/internal/integration/cockroach_test.go
@@ -384,7 +384,7 @@ func TestCockroach_CLI(t *testing.T) {
 			}`
 	t.Run("SchemaInspect", func(t *testing.T) {
 		crdbRun(t, func(t *crdbTest) {
-			testCLISchemaInspect(t, h, t.dsn(), postgres.UnmarshalHCL, "-s", "public")
+			testCLISchemaInspect(t, h, t.dsn(), postgres.EvalHCL, "-s", "public")
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -456,14 +456,14 @@ func TestCockroach_CLI_MultiSchema(t *testing.T) {
 		crdbRun(t, func(t *crdbTest) {
 			t.dropSchemas("test2")
 			t.dropTables("users")
-			testCLIMultiSchemaInspect(t, h, t.dsn(), []string{"public", "test2"}, postgres.UnmarshalHCL)
+			testCLIMultiSchemaInspect(t, h, t.dsn(), []string{"public", "test2"}, postgres.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
 		crdbRun(t, func(t *crdbTest) {
 			t.dropSchemas("test2")
 			t.dropTables("users")
-			testCLIMultiSchemaApply(t, h, t.dsn(), []string{"public", "test2"}, postgres.UnmarshalHCL)
+			testCLIMultiSchemaApply(t, h, t.dsn(), []string{"public", "test2"}, postgres.EvalHCL)
 		})
 	})
 }
@@ -489,7 +489,7 @@ create table atlas_defaults
 		spec, err := postgres.MarshalHCL(realm.Schemas[0])
 		require.NoError(t, err)
 		var s schema.Schema
-		err = postgres.UnmarshalHCL(spec, &s)
+		err = postgres.EvalHCLBytes(spec, &s, nil)
 		require.NoError(t, err)
 		t.dropTables(n)
 		t.applyHcl(string(spec))
@@ -840,7 +840,7 @@ func (t *crdbTest) revisionsStorage() migrate.RevisionReadWriter {
 func (t *crdbTest) applyHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Schema
-	err := postgres.UnmarshalHCL([]byte(spec), &desired)
+	err := postgres.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	existing := realm.Schemas[0]
 	diff, err := t.drv.SchemaDiff(existing, &desired)
@@ -1000,7 +1000,7 @@ func (t *crdbTest) dropSchemas(names ...string) {
 func (t *crdbTest) applyRealmHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Realm
-	err := postgres.UnmarshalHCL([]byte(spec), &desired)
+	err := postgres.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	diff, err := t.drv.RealmDiff(realm, &desired)
 	require.NoError(t, err)
diff --git a/internal/integration/hclsqlspec/hclsqlspec_test.go b/internal/integration/hclsqlspec/hclsqlspec_test.go
index a228a87a..bfe269e7 100644
--- a/internal/integration/hclsqlspec/hclsqlspec_test.go
+++ b/internal/integration/hclsqlspec/hclsqlspec_test.go
@@ -7,7 +7,7 @@ package hclsqlspec
 import (
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/postgres"
 	"ariga.io/atlas/sql/schema"
@@ -19,23 +19,23 @@ import (
 
 var dialects = []struct {
 	name string
-	schemaspec.Marshaler
-	schemaspec.Unmarshaler
+	schemahcl.Marshaler
+	schemahcl.Evaluator
 }{
 	{
-		name:        "mysql",
-		Marshaler:   mysql.MarshalHCL,
-		Unmarshaler: mysql.UnmarshalHCL,
+		name:      "mysql",
+		Marshaler: mysql.MarshalHCL,
+		Evaluator: mysql.EvalHCL,
 	},
 	{
-		name:        "postgres",
-		Marshaler:   postgres.MarshalHCL,
-		Unmarshaler: postgres.UnmarshalHCL,
+		name:      "postgres",
+		Marshaler: postgres.MarshalHCL,
+		Evaluator: postgres.EvalHCL,
 	},
 	{
-		name:        "sqlite",
-		Marshaler:   sqlite.MarshalHCL,
-		Unmarshaler: sqlite.UnmarshalHCL,
+		name:      "sqlite",
+		Marshaler: sqlite.MarshalHCL,
+		Evaluator: sqlite.EvalHCL,
 	},
 }
 
@@ -140,35 +140,35 @@ table "accounts" {
 		Tables: []*sqlspec.Table{
 			{
 				Name:   "users",
-				Schema: &schemaspec.Ref{V: "$schema.hi"},
+				Schema: &schemahcl.Ref{V: "$schema.hi"},
 				Columns: []*sqlspec.Column{
 					{
 						Name:    "id",
-						Type:    &schemaspec.Type{T: "int"},
+						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "123"},
+						Default: &schemahcl.LiteralValue{V: "123"},
 					},
 					{
 						Name:    "age",
-						Type:    &schemaspec.Type{T: "int"},
+						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "10"},
+						Default: &schemahcl.LiteralValue{V: "10"},
 					},
 					{
 						Name:    "active",
-						Type:    &schemaspec.Type{T: "boolean"},
+						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "true"},
+						Default: &schemahcl.LiteralValue{V: "true"},
 					},
 					{
 						Name:    "account_active",
-						Type:    &schemaspec.Type{T: "boolean"},
+						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "true"},
+						Default: &schemahcl.LiteralValue{V: "true"},
 					},
 				},
 				PrimaryKey: &sqlspec.PrimaryKey{
-					Columns: []*schemaspec.Ref{
+					Columns: []*schemahcl.Ref{
 						{
 							V: "$table.users.$column.id",
 						},
@@ -181,7 +181,7 @@ table "accounts" {
 					{
 						Name:   "age",
 						Unique: true,
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.users.$column.age",
 							},
@@ -190,7 +190,7 @@ table "accounts" {
 					{
 						Name:   "active",
 						Unique: false,
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.users.$column.active",
 							},
@@ -200,51 +200,51 @@ table "accounts" {
 				ForeignKeys: []*sqlspec.ForeignKey{
 					{
 						Symbol: "fk",
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.users.$column.account_active",
 							},
 						},
-						RefColumns: []*schemaspec.Ref{
+						RefColumns: []*schemahcl.Ref{
 							{
 								V: "$table.accounts.$column.active",
 							},
 						},
-						OnDelete: &schemaspec.Ref{V: string(schema.SetNull)},
+						OnDelete: &schemahcl.Ref{V: string(schema.SetNull)},
 					},
 				},
 			},
 			{
 				Name:   "accounts",
-				Schema: &schemaspec.Ref{V: "$schema.hi"},
+				Schema: &schemahcl.Ref{V: "$schema.hi"},
 				Columns: []*sqlspec.Column{
 					{
 						Name:    "id",
-						Type:    &schemaspec.Type{T: "int"},
+						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "123"},
+						Default: &schemahcl.LiteralValue{V: "123"},
 					},
 					{
 						Name:    "age",
-						Type:    &schemaspec.Type{T: "int"},
+						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "10"},
+						Default: &schemahcl.LiteralValue{V: "10"},
 					},
 					{
 						Name:    "active",
-						Type:    &schemaspec.Type{T: "boolean"},
+						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "true"},
+						Default: &schemahcl.LiteralValue{V: "true"},
 					},
 					{
 						Name:    "user_active",
-						Type:    &schemaspec.Type{T: "boolean"},
+						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemaspec.LiteralValue{V: "true"},
+						Default: &schemahcl.LiteralValue{V: "true"},
 					},
 				},
 				PrimaryKey: &sqlspec.PrimaryKey{
-					Columns: []*schemaspec.Ref{
+					Columns: []*schemahcl.Ref{
 						{
 							V: "$table.accounts.$column.id",
 						},
@@ -254,7 +254,7 @@ table "accounts" {
 					{
 						Name:   "age",
 						Unique: true,
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.accounts.$column.age",
 							},
@@ -263,7 +263,7 @@ table "accounts" {
 					{
 						Name:   "active",
 						Unique: false,
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.accounts.$column.active",
 							},
@@ -273,17 +273,17 @@ table "accounts" {
 				ForeignKeys: []*sqlspec.ForeignKey{
 					{
 						Symbol: "fk",
-						Columns: []*schemaspec.Ref{
+						Columns: []*schemahcl.Ref{
 							{
 								V: "$table.accounts.$column.user_active",
 							},
 						},
-						RefColumns: []*schemaspec.Ref{
+						RefColumns: []*schemahcl.Ref{
 							{
 								V: "$table.users.$column.active",
 							},
 						},
-						OnDelete: &schemaspec.Ref{V: string(schema.SetNull)},
+						OnDelete: &schemahcl.Ref{V: string(schema.SetNull)},
 					},
 				},
 			},
@@ -302,10 +302,10 @@ schema "hi" {
 		Schemas: []*sqlspec.Schema{
 			{
 				Name: "hi",
-				DefaultExtension: schemaspec.DefaultExtension{
-					Extra: schemaspec.Resource{
-						Attrs: []*schemaspec.Attr{
-							{K: "x", V: &schemaspec.LiteralValue{V: "1"}},
+				DefaultExtension: schemahcl.DefaultExtension{
+					Extra: schemahcl.Resource{
+						Attrs: []*schemahcl.Attr{
+							{K: "x", V: &schemahcl.LiteralValue{V: "1"}},
 						},
 					},
 				},
@@ -371,7 +371,9 @@ table "t2" {
 	for _, tt := range dialects {
 		t.Run(tt.name, func(t *testing.T) {
 			var r schema.Realm
-			err := tt.UnmarshalSpec([]byte(f), &r)
+			parsed, err := schemahcl.ParseBytes([]byte(f))
+			require.NoError(t, err)
+			err = tt.Eval(parsed, &r, nil)
 			require.NoError(t, err)
 			exp := &schema.Realm{
 				Schemas: []*schema.Schema{
@@ -397,7 +399,9 @@ table "t2" {
 			hcl, err := tt.MarshalSpec(&r)
 			require.NoError(t, err)
 			var after schema.Realm
-			err = tt.UnmarshalSpec(hcl, &after)
+			parsed, err = schemahcl.ParseBytes(hcl)
+			require.NoError(t, err)
+			err = tt.Eval(parsed, &after, nil)
 			require.NoError(t, err)
 			require.EqualValues(t, exp, &after)
 		})
@@ -418,7 +422,9 @@ func TestUnsignedImmutability(t *testing.T) {
 schema "test" {
 }`
 	var s schema.Schema
-	err := mysql.UnmarshalHCL([]byte(f), &s)
+	parsed, err := schemahcl.ParseBytes([]byte(f))
+	require.NoError(t, err)
+	err = mysql.EvalHCL(parsed, &s, nil)
 	require.NoError(t, err)
 	tbl := s.Tables[0]
 	require.EqualValues(t, &schema.IntegerType{T: "bigint", Unsigned: true}, tbl.Columns[0].Type.Type)
@@ -459,7 +465,9 @@ table "b" "users" {
 }
 `
 	var r schema.Realm
-	err := mysql.UnmarshalHCL([]byte(h), &r)
+	parsed, err := schemahcl.ParseBytes([]byte(h))
+	require.NoError(t, err)
+	err = mysql.EvalHCL(parsed, &r, nil)
 	require.NoError(t, err)
 
 	require.EqualValues(t, r.Schemas[0].Tables[0].Columns[0], r.Schemas[1].Tables[0].ForeignKeys[0].RefColumns[0])
@@ -521,7 +529,7 @@ schema "c" {
 
 func decode(f string) (*db, error) {
 	d := &db{}
-	if err := hcl.UnmarshalSpec([]byte(f), d); err != nil {
+	if err := hcl.EvalBytes([]byte(f), d, nil); err != nil {
 		return nil, err
 	}
 	return d, nil
diff --git a/internal/integration/hclsqlspec/migrate_test.go b/internal/integration/hclsqlspec/migrate_test.go
index 3fd56328..cdd55502 100644
--- a/internal/integration/hclsqlspec/migrate_test.go
+++ b/internal/integration/hclsqlspec/migrate_test.go
@@ -7,8 +7,7 @@ package hclsqlspec
 import (
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/postgres"
 	"ariga.io/atlas/sql/sqlspec"
 
@@ -31,7 +30,7 @@ modify_table {
 	var test struct {
 		Changes []sqlspec.Change `spec:""`
 	}
-	err := hcl.UnmarshalSpec([]byte(f), &test)
+	err := hcl.EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, &sqlspec.ModifyTable{
 		Table: "users",
@@ -40,7 +39,7 @@ modify_table {
 				Column: &sqlspec.Column{
 					Name: "id",
 					Null: false,
-					Type: &schemaspec.Type{T: "int"},
+					Type: &schemahcl.Type{T: "int"},
 				},
 			},
 		},
diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go
index b076f687..7485fc7e 100644
--- a/internal/integration/integration_test.go
+++ b/internal/integration/integration_test.go
@@ -21,12 +21,13 @@ import (
 	"text/template"
 	"time"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/schema"
 	entsql "entgo.io/ent/dialect/sql"
 	entschema "entgo.io/ent/dialect/sql/schema"
 	"entgo.io/ent/entc/integration/ent"
+	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/stretchr/testify/require"
 )
 
@@ -177,12 +178,12 @@ func testHCLIntegration(t T, full string, empty string) {
 	require.Empty(t, t.realm().Schemas[0].Tables)
 }
 
-func testCLISchemaInspect(t T, h string, dsn string, unmarshaler schemaspec.Unmarshaler, args ...string) {
+func testCLISchemaInspect(t T, h string, dsn string, eval schemahcl.Evaluator, args ...string) {
 	err := initCLI()
 	require.NoError(t, err)
 	t.dropTables("users")
 	var expected schema.Schema
-	err = unmarshaler.UnmarshalSpec([]byte(h), &expected)
+	err = evalBytes([]byte(h), &expected, eval)
 	require.NoError(t, err)
 	t.applyHcl(h)
 	runArgs := []string{
@@ -199,18 +200,18 @@ func testCLISchemaInspect(t T, h string, dsn string, unmarshaler schemaspec.Unma
 	cmd.Stdout = stdout
 	require.NoError(t, cmd.Run(), stderr.String())
 	var actual schema.Schema
-	err = unmarshaler.UnmarshalSpec(stdout.Bytes(), &actual)
+	err = evalBytes(stdout.Bytes(), &actual, eval)
 	require.NoError(t, err)
 	require.Empty(t, stderr.String())
 	require.Equal(t, expected, actual)
 }
 
-func testCLISchemaInspectEnv(t T, h string, env string, unmarshaler schemaspec.Unmarshaler) {
+func testCLISchemaInspectEnv(t T, h string, env string, eval schemahcl.Evaluator) {
 	err := initCLI()
 	require.NoError(t, err)
 	t.dropTables("users")
 	var expected schema.Schema
-	err = unmarshaler.UnmarshalSpec([]byte(h), &expected)
+	err = evalBytes([]byte(h), &expected, eval)
 	require.NoError(t, err)
 	t.applyHcl(h)
 	cmd := exec.Command("go", "run", "ariga.io/atlas/cmd/atlas",
@@ -224,7 +225,7 @@ func testCLISchemaInspectEnv(t T, h string, env string, unmarshaler schemaspec.U
 	cmd.Stdout = stdout
 	require.NoError(t, cmd.Run(), stderr.String())
 	var actual schema.Schema
-	err = unmarshaler.UnmarshalSpec(stdout.Bytes(), &actual)
+	err = evalBytes(stdout.Bytes(), &actual, eval)
 	require.NoError(t, err)
 	require.Empty(t, stderr.String())
 	require.Equal(t, expected, actual)
@@ -241,14 +242,14 @@ func initCLI() error {
 	return err
 }
 
-func testCLIMultiSchemaApply(t T, h string, dsn string, schemas []string, unmarshaler schemaspec.Unmarshaler) {
+func testCLIMultiSchemaApply(t T, h string, dsn string, schemas []string, eval schemahcl.Evaluator) {
 	err := initCLI()
 	f := filepath.Join(t.TempDir(), "atlas.hcl")
 	err = ioutil.WriteFile(f, []byte(h), 0644)
 	require.NoError(t, err)
 	require.NoError(t, err)
 	var expected schema.Realm
-	err = unmarshaler.UnmarshalSpec([]byte(h), &expected)
+	err = evalBytes([]byte(h), &expected, eval)
 	require.NoError(t, err)
 	cmd := exec.Command("go", "run", "ariga.io/atlas/cmd/atlas",
 		"schema",
@@ -271,11 +272,11 @@ func testCLIMultiSchemaApply(t T, h string, dsn string, schemas []string, unmars
 	require.Contains(t, stdout.String(), `-- Add new schema named "test2"`)
 }
 
-func testCLIMultiSchemaInspect(t T, h string, dsn string, schemas []string, unmarshaler schemaspec.Unmarshaler) {
+func testCLIMultiSchemaInspect(t T, h string, dsn string, schemas []string, eval schemahcl.Evaluator) {
 	err := initCLI()
 	require.NoError(t, err)
 	var expected schema.Realm
-	err = unmarshaler.UnmarshalSpec([]byte(h), &expected)
+	err = evalBytes([]byte(h), &expected, eval)
 	require.NoError(t, err)
 	t.applyRealmHcl(h)
 	cmd := exec.Command("go", "run", "ariga.io/atlas/cmd/atlas",
@@ -291,7 +292,7 @@ func testCLIMultiSchemaInspect(t T, h string, dsn string, schemas []string, unma
 	cmd.Stdout = stdout
 	require.NoError(t, cmd.Run(), stderr.String())
 	var actual schema.Realm
-	err = unmarshaler.UnmarshalSpec(stdout.Bytes(), &actual)
+	err = evalBytes(stdout.Bytes(), &actual, eval)
 	require.NoError(t, err)
 	require.Empty(t, stderr.String())
 	require.Equal(t, expected, actual)
@@ -552,3 +553,11 @@ func buildCmd(t *testing.T) (string, error) {
 	}
 	return filepath.Join(td, "atlas"), nil
 }
+
+func evalBytes(b []byte, v interface{}, ev schemahcl.Evaluator) error {
+	p := hclparse.NewParser()
+	if _, diag := p.ParseHCL(b, ""); diag.HasErrors() {
+		return diag
+	}
+	return ev.Eval(p, v, nil)
+}
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index 3c9bb745..f577175b 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -581,7 +581,7 @@ func TestMySQL_CLI(t *testing.T) {
 		myRun(t, func(t *myTest) {
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLISchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V), t.dsn("test"), mysql.UnmarshalHCL)
+			testCLISchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V), t.dsn("test"), mysql.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -665,7 +665,7 @@ func TestMySQL_CLI_MultiSchema(t *testing.T) {
 			t.dropTables("users")
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLIMultiSchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.UnmarshalHCL)
+			testCLIMultiSchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -674,7 +674,7 @@ func TestMySQL_CLI_MultiSchema(t *testing.T) {
 			t.dropTables("users")
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLIMultiSchemaApply(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.UnmarshalHCL)
+			testCLIMultiSchemaApply(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.EvalHCL)
 		})
 	})
 }
@@ -719,7 +719,7 @@ create table atlas_defaults
 		spec, err := mysql.MarshalHCL(realm.Schemas[0])
 		require.NoError(t, err)
 		var s schema.Realm
-		err = mysql.UnmarshalHCL(spec, &s)
+		err = mysql.EvalHCLBytes(spec, &s, nil)
 		require.NoError(t, err)
 		t.dropTables(n)
 		t.applyHcl(string(spec))
@@ -1205,7 +1205,7 @@ func (t *myTest) revisionsStorage() migrate.RevisionReadWriter {
 func (t *myTest) applyHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Schema
-	err := mysql.UnmarshalHCL([]byte(spec), &desired)
+	err := mysql.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	existing := realm.Schemas[0]
 	require.NoError(t, err)
@@ -1218,7 +1218,7 @@ func (t *myTest) applyHcl(spec string) {
 func (t *myTest) applyRealmHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Realm
-	err := mysql.UnmarshalHCL([]byte(spec), &desired)
+	err := mysql.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	diff, err := t.drv.RealmDiff(realm, &desired)
 	require.NoError(t, err)
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index 504a4db4..027a61c1 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -550,7 +550,7 @@ schema "second" {
 func (t *pgTest) applyRealmHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Realm
-	err := postgres.UnmarshalHCL([]byte(spec), &desired)
+	err := postgres.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	diff, err := t.drv.RealmDiff(realm, &desired)
 	require.NoError(t, err)
@@ -573,7 +573,7 @@ func TestPostgres_CLI(t *testing.T) {
 			}`
 	t.Run("SchemaInspect", func(t *testing.T) {
 		pgRun(t, func(t *pgTest) {
-			testCLISchemaInspect(t, h, t.dsn(), postgres.UnmarshalHCL)
+			testCLISchemaInspect(t, h, t.dsn(), postgres.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -645,14 +645,14 @@ func TestPostgres_CLI_MultiSchema(t *testing.T) {
 		pgRun(t, func(t *pgTest) {
 			t.dropSchemas("test2")
 			t.dropTables("users")
-			testCLIMultiSchemaInspect(t, h, t.dsn(), []string{"public", "test2"}, postgres.UnmarshalHCL)
+			testCLIMultiSchemaInspect(t, h, t.dsn(), []string{"public", "test2"}, postgres.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
 		pgRun(t, func(t *pgTest) {
 			t.dropSchemas("test2")
 			t.dropTables("users")
-			testCLIMultiSchemaApply(t, h, t.dsn(), []string{"public", "test2"}, postgres.UnmarshalHCL)
+			testCLIMultiSchemaApply(t, h, t.dsn(), []string{"public", "test2"}, postgres.EvalHCL)
 		})
 	})
 }
@@ -678,7 +678,7 @@ create table atlas_defaults
 		spec, err := postgres.MarshalHCL(realm.Schemas[0])
 		require.NoError(t, err)
 		var s schema.Schema
-		err = postgres.UnmarshalHCL(spec, &s)
+		err = postgres.EvalHCLBytes(spec, &s, nil)
 		require.NoError(t, err)
 		t.dropTables(n)
 		t.applyHcl(string(spec))
@@ -1096,7 +1096,7 @@ func (t *pgTest) revisionsStorage() migrate.RevisionReadWriter {
 func (t *pgTest) applyHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Schema
-	err := postgres.UnmarshalHCL([]byte(spec), &desired)
+	err := postgres.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	existing := realm.Schemas[0]
 	diff, err := t.drv.SchemaDiff(existing, &desired)
diff --git a/internal/integration/script_test.go b/internal/integration/script_test.go
index a7d55079..6d1ffef3 100644
--- a/internal/integration/script_test.go
+++ b/internal/integration/script_test.go
@@ -506,7 +506,7 @@ func (t *myTest) hclDiff(ts *testscript.TestScript, name string) ([]schema.Chang
 		ctx     = context.Background()
 		r       = strings.NewReplacer("$charset", ts.Getenv("charset"), "$collate", ts.Getenv("collate"), "$db", ts.Getenv("db"))
 	)
-	ts.Check(mysql.UnmarshalHCL([]byte(r.Replace(f)), desired))
+	ts.Check(mysql.EvalHCLBytes([]byte(r.Replace(f)), desired, nil))
 	current, err := t.drv.InspectSchema(ctx, desired.Name, nil)
 	ts.Check(err)
 	desired, err = t.drv.(schema.Normalizer).NormalizeSchema(ctx, desired)
@@ -536,7 +536,7 @@ func (t *pgTest) hclDiff(ts *testscript.TestScript, name string) ([]schema.Chang
 		ctx     = context.Background()
 		f       = strings.ReplaceAll(ts.ReadFile(name), "$db", ts.Getenv("db"))
 	)
-	ts.Check(postgres.UnmarshalHCL([]byte(f), desired))
+	ts.Check(postgres.EvalHCLBytes([]byte(f), desired, nil))
 	current, err := t.drv.InspectSchema(ctx, desired.Name, nil)
 	ts.Check(err)
 	desired, err = t.drv.(schema.Normalizer).NormalizeSchema(ctx, desired)
@@ -566,7 +566,7 @@ func (t *liteTest) hclDiff(ts *testscript.TestScript, name string) ([]schema.Cha
 		f       = ts.ReadFile(name)
 		drv     = ts.Value(keyDrv).(*sqlite.Driver)
 	)
-	ts.Check(sqlite.UnmarshalHCL([]byte(f), desired))
+	ts.Check(sqlite.EvalHCLBytes([]byte(f), desired, nil))
 	current, err := drv.InspectSchema(context.Background(), desired.Name, nil)
 	ts.Check(err)
 	changes, err := drv.SchemaDiff(current, desired)
diff --git a/internal/integration/sqlite_test.go b/internal/integration/sqlite_test.go
index c1275807..d871f51b 100644
--- a/internal/integration/sqlite_test.go
+++ b/internal/integration/sqlite_test.go
@@ -470,7 +470,7 @@ create table atlas_defaults
 		spec, err := sqlite.MarshalHCL(realm.Schemas[0])
 		require.NoError(t, err)
 		var s schema.Schema
-		err = sqlite.UnmarshalHCL(spec, &s)
+		err = sqlite.EvalHCLBytes(spec, &s, nil)
 		require.NoError(t, err)
 		t.dropTables(n)
 		t.applyHcl(string(spec))
@@ -504,12 +504,12 @@ env "hello" {
 			})
 			require.NoError(t, err)
 
-			testCLISchemaInspectEnv(t, h, "hello", sqlite.UnmarshalHCL)
+			testCLISchemaInspectEnv(t, h, "hello", sqlite.EvalHCL)
 		})
 	})
 	t.Run("SchemaInspect", func(t *testing.T) {
 		liteRun(t, func(t *liteTest) {
-			testCLISchemaInspect(t, h, t.dsn(), sqlite.UnmarshalHCL)
+			testCLISchemaInspect(t, h, t.dsn(), sqlite.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -814,7 +814,7 @@ func (t *liteTest) dropSchemas(...string) {}
 func (t *liteTest) applyHcl(spec string) {
 	realm := t.loadRealm()
 	var desired schema.Schema
-	err := sqlite.UnmarshalHCL([]byte(spec), &desired)
+	err := sqlite.EvalHCLBytes([]byte(spec), &desired, nil)
 	require.NoError(t, err)
 	existing := realm.Schemas[0]
 	diff, err := t.drv.SchemaDiff(existing, &desired)
diff --git a/internal/integration/tidb_test.go b/internal/integration/tidb_test.go
index 79002c63..f50275ca 100644
--- a/internal/integration/tidb_test.go
+++ b/internal/integration/tidb_test.go
@@ -509,7 +509,7 @@ create table atlas_defaults
 		spec, err := mysql.MarshalHCL(realm.Schemas[0])
 		require.NoError(t, err)
 		var s schema.Realm
-		err = mysql.UnmarshalHCL(spec, &s)
+		err = mysql.EvalHCLBytes(spec, &s, nil)
 		require.NoError(t, err)
 		t.dropTables(n)
 		t.applyHcl(string(spec))
@@ -551,7 +551,7 @@ func TestTiDB_CLI_MultiSchema(t *testing.T) {
 			t.dropTables("users")
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLIMultiSchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.UnmarshalHCL)
+			testCLIMultiSchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
@@ -560,7 +560,7 @@ func TestTiDB_CLI_MultiSchema(t *testing.T) {
 			t.dropTables("users")
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLIMultiSchemaApply(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.UnmarshalHCL)
+			testCLIMultiSchemaApply(t, fmt.Sprintf(h, charset.V, collate.V, charset.V, collate.V), t.dsn(""), []string{"test", "test2"}, mysql.EvalHCL)
 		})
 	})
 }
@@ -584,7 +584,7 @@ func TestTiDB_CLI(t *testing.T) {
 		tidbRun(t, func(t *myTest) {
 			attrs := t.defaultAttrs()
 			charset, collate := attrs[0].(*schema.Charset), attrs[1].(*schema.Collation)
-			testCLISchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V), t.dsn("test"), mysql.UnmarshalHCL)
+			testCLISchemaInspect(t, fmt.Sprintf(h, charset.V, collate.V), t.dsn("test"), mysql.EvalHCL)
 		})
 	})
 	t.Run("SchemaApply", func(t *testing.T) {
diff --git a/schema/schemaspec/schemahcl/context_test.go b/schemahcl/context_test.go
similarity index 76%
rename from schema/schemaspec/schemahcl/context_test.go
rename to schemahcl/context_test.go
index e7055793..d02ead43 100644
--- a/schema/schemaspec/schemahcl/context_test.go
+++ b/schemahcl/context_test.go
@@ -8,9 +8,6 @@ import (
 	"reflect"
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/internal/schemautil"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -60,7 +57,7 @@ config "defaults" {
 		Backends  []*Backend  `spec:"backend"`
 		Endpoints []*Endpoint `spec:"endpoint"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, []*Endpoint{
 		{
@@ -107,15 +104,15 @@ continent = country.israel.metadata.geo.continent
 			Metadata []*Metadata `spec:"metadata"`
 		}
 		Test struct {
-			Countries    []*Country      `spec:"country"`
-			MetadataRef  *schemaspec.Ref `spec:"metadata"`
-			PhonePrefix  string          `spec:"phone_prefix"`
-			PhonePrefix2 string          `spec:"phone_prefix_2"`
-			Continent    string          `spec:"continent"`
+			Countries    []*Country `spec:"country"`
+			MetadataRef  *Ref       `spec:"metadata"`
+			PhonePrefix  string     `spec:"phone_prefix"`
+			PhonePrefix2 string     `spec:"phone_prefix_2"`
+			Continent    string     `spec:"continent"`
 		}
 	)
 	var test Test
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, test, Test{
 		Countries: []*Country{
@@ -127,7 +124,7 @@ continent = country.israel.metadata.geo.continent
 				},
 			},
 		},
-		MetadataRef:  &schemaspec.Ref{V: "$country.israel.$metadata.0"},
+		MetadataRef:  &Ref{V: "$country.israel.$metadata.0"},
 		PhonePrefix:  "972",
 		PhonePrefix2: "123",
 		Continent:    "asia",
@@ -161,7 +158,7 @@ country "israel" {
 	var test struct {
 		Countries []*Country `spec:"country"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	israel := &Country{
 		Name: "israel",
 		Cities: []*City{
@@ -187,21 +184,21 @@ pet "garfield" {
 			Name string `spec:",name"`
 		}
 		Pet struct {
-			Name  string          `spec:",name"`
-			Type  string          `spec:"type"`
-			Owner *schemaspec.Ref `spec:"owner"`
+			Name  string `spec:",name"`
+			Type  string `spec:"type"`
+			Owner *Ref   `spec:"owner"`
 		}
 	)
 	var test struct {
 		People []*Person `spec:"person"`
 		Pets   []*Pet    `spec:"pet"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, &Pet{
 		Name:  "garfield",
 		Type:  "cat",
-		Owner: &schemaspec.Ref{V: "$person.jon"},
+		Owner: &Ref{V: "$person.jon"},
 	}, test.Pets[0])
 	marshal, err := Marshal(&test)
 	require.NoError(t, err)
@@ -228,19 +225,19 @@ group "lion_kings" {
 			Name string `spec:",name"`
 		}
 		Group struct {
-			Name    string            `spec:",name"`
-			Members []*schemaspec.Ref `spec:"members"`
+			Name    string `spec:",name"`
+			Members []*Ref `spec:"members"`
 		}
 	)
 	var test struct {
 		Users  []*User  `spec:"user"`
 		Groups []*Group `spec:"group"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, &Group{
 		Name: "lion_kings",
-		Members: []*schemaspec.Ref{
+		Members: []*Ref{
 			{V: "$user.simba"},
 			{V: "$user.mufasa"},
 		},
@@ -287,7 +284,7 @@ person "jane" {
 	var test struct {
 		People []*Person `spec:"person"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	john := &Person{
 		Name:     "john",
@@ -313,14 +310,14 @@ person "jane" {
 
 func TestSchemaRefParse(t *testing.T) {
 	type Point struct {
-		Z []*schemaspec.Ref `spec:"z"`
+		Z []*Ref `spec:"z"`
 	}
 	var test = struct {
 		Points []*Point `spec:"point"`
 	}{
 		Points: []*Point{
-			{Z: []*schemaspec.Ref{{V: "$a"}}},
-			{Z: []*schemaspec.Ref{{V: "b"}}},
+			{Z: []*Ref{{V: "$a"}}},
+			{Z: []*Ref{{V: "b"}}},
 		},
 	}
 	b, err := Marshal(&test)
@@ -345,12 +342,12 @@ variadic = enum("a","b","c")
 `
 	s := New(
 		WithTypes(
-			[]*schemaspec.TypeSpec{
+			[]*TypeSpec{
 				{Name: "bool", T: "bool"},
 				{
 					Name: "int",
 					T:    "int",
-					Attributes: []*schemaspec.TypeAttr{
+					Attributes: []*TypeAttr{
 						{Name: "size", Kind: reflect.Int, Required: false},
 						{Name: "unsigned", Kind: reflect.Bool, Required: false},
 					},
@@ -358,14 +355,14 @@ variadic = enum("a","b","c")
 				{
 					Name: "varchar",
 					T:    "varchar",
-					Attributes: []*schemaspec.TypeAttr{
+					Attributes: []*TypeAttr{
 						{Name: "size", Kind: reflect.Int, Required: false},
 					},
 				},
 				{
 					Name: "enum",
 					T:    "enum",
-					Attributes: []*schemaspec.TypeAttr{
+					Attributes: []*TypeAttr{
 						{Name: "values", Kind: reflect.Slice, Required: false},
 					},
 				},
@@ -373,41 +370,41 @@ variadic = enum("a","b","c")
 		),
 	)
 	var test struct {
-		First    *schemaspec.Type `spec:"first"`
-		Second   *schemaspec.Type `spec:"second"`
-		Third    *schemaspec.Type `spec:"third"`
-		Varchar  *schemaspec.Type `spec:"sized"`
-		Variadic *schemaspec.Type `spec:"variadic"`
+		First    *Type `spec:"first"`
+		Second   *Type `spec:"second"`
+		Third    *Type `spec:"third"`
+		Varchar  *Type `spec:"sized"`
+		Variadic *Type `spec:"variadic"`
 	}
-	err := s.UnmarshalSpec([]byte(f), &test)
+	err := s.EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, "int", test.First.T)
 	require.EqualValues(t, "bool", test.Second.T)
-	require.EqualValues(t, &schemaspec.Type{
+	require.EqualValues(t, &Type{
 		T: "varchar",
-		Attrs: []*schemaspec.Attr{
-			{K: "size", V: &schemaspec.LiteralValue{V: "255"}},
+		Attrs: []*Attr{
+			{K: "size", V: &LiteralValue{V: "255"}},
 		},
 	}, test.Varchar)
-	require.EqualValues(t, &schemaspec.Type{
+	require.EqualValues(t, &Type{
 		T: "enum",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*Attr{
 			{
 				K: "values",
-				V: &schemaspec.ListValue{
-					V: []schemaspec.Value{
-						&schemaspec.LiteralValue{V: `"a"`},
-						&schemaspec.LiteralValue{V: `"b"`},
-						&schemaspec.LiteralValue{V: `"c"`},
+				V: &ListValue{
+					V: []Value{
+						&LiteralValue{V: `"a"`},
+						&LiteralValue{V: `"b"`},
+						&LiteralValue{V: `"c"`},
 					},
 				},
 			},
 		},
 	}, test.Variadic)
-	require.EqualValues(t, &schemaspec.Type{
+	require.EqualValues(t, &Type{
 		T: "int",
-		Attrs: []*schemaspec.Attr{
-			{K: "size", V: &schemaspec.LiteralValue{V: "10"}},
+		Attrs: []*Attr{
+			{K: "size", V: &LiteralValue{V: "10"}},
 		},
 	}, test.Third)
 	after, err := s.MarshalSpec(&test)
@@ -417,11 +414,11 @@ variadic = enum("a","b","c")
 
 func TestOptionalArgs(t *testing.T) {
 	s := New(
-		WithTypes([]*schemaspec.TypeSpec{
+		WithTypes([]*TypeSpec{
 			{
 				T:    "float",
 				Name: "float",
-				Attributes: []*schemaspec.TypeAttr{
+				Attributes: []*TypeAttr{
 					{Name: "precision", Kind: reflect.Int, Required: false},
 					{Name: "scale", Kind: reflect.Int, Required: false},
 				},
@@ -433,19 +430,19 @@ arg_1 = float(10)
 arg_2 = float(10,2)
 `
 	var test struct {
-		Arg0 *schemaspec.Type `spec:"arg_0"`
-		Arg1 *schemaspec.Type `spec:"arg_1"`
-		Arg2 *schemaspec.Type `spec:"arg_2"`
+		Arg0 *Type `spec:"arg_0"`
+		Arg1 *Type `spec:"arg_1"`
+		Arg2 *Type `spec:"arg_2"`
 	}
-	err := s.UnmarshalSpec([]byte(f), &test)
+	err := s.EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.Nil(t, test.Arg0.Attrs)
-	require.EqualValues(t, []*schemaspec.Attr{
-		schemautil.LitAttr("precision", "10"),
+	require.EqualValues(t, []*Attr{
+		LitAttr("precision", "10"),
 	}, test.Arg1.Attrs)
-	require.EqualValues(t, []*schemaspec.Attr{
-		schemautil.LitAttr("precision", "10"),
-		schemautil.LitAttr("scale", "2"),
+	require.EqualValues(t, []*Attr{
+		LitAttr("precision", "10"),
+		LitAttr("scale", "2"),
 	}, test.Arg2.Attrs)
 }
 
@@ -457,10 +454,10 @@ v = user.atlas.cli.version
 r = user.atlas.cli
 `
 	var test struct {
-		V string          `spec:"v"`
-		R *schemaspec.Ref `spec:"r"`
+		V string `spec:"v"`
+		R *Ref   `spec:"r"`
 	}
-	err := Unmarshal([]byte(h), &test)
+	err := New().EvalBytes([]byte(h), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, "v0.3.9", test.V)
 	require.EqualValues(t, "$user.atlas.cli", test.R.V)
@@ -497,7 +494,7 @@ bool = var.bool
 		Int     int    `spec:"int"`
 		Bool    bool   `spec:"bool"`
 	}
-	err := state.Eval([]byte(h), &test, map[string]string{
+	err := state.EvalBytes([]byte(h), &test, map[string]string{
 		"name": "rotemtam",
 		"int":  "42",
 		"bool": "true",
diff --git a/schema/schemaspec/extension_test.go b/schemahcl/extension_test.go
similarity index 51%
rename from schema/schemaspec/extension_test.go
rename to schemahcl/extension_test.go
index e749fe3c..b6fa5fdb 100644
--- a/schema/schemaspec/extension_test.go
+++ b/schemahcl/extension_test.go
@@ -2,31 +2,29 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package schemaspec_test
+package schemahcl_test
 
 import (
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/internal/schemautil"
-
+	"ariga.io/atlas/schemahcl"
 	"github.com/stretchr/testify/require"
 )
 
 type OwnerBlock struct {
-	schemaspec.DefaultExtension
-	ID        string                   `spec:",name"`
-	FirstName string                   `spec:"first_name"`
-	Born      int                      `spec:"born"`
-	Active    bool                     `spec:"active"`
-	BoolPtr   *bool                    `spec:"bool_ptr"`
-	OmitBool1 bool                     `spec:"omit_bool1,omitempty"`
-	OmitBool2 bool                     `spec:"omit_bool2,omitempty"`
-	Lit       *schemaspec.LiteralValue `spec:"lit"`
+	schemahcl.DefaultExtension
+	ID        string                  `spec:",name"`
+	FirstName string                  `spec:"first_name"`
+	Born      int                     `spec:"born"`
+	Active    bool                    `spec:"active"`
+	BoolPtr   *bool                   `spec:"bool_ptr"`
+	OmitBool1 bool                    `spec:"omit_bool1,omitempty"`
+	OmitBool2 bool                    `spec:"omit_bool2,omitempty"`
+	Lit       *schemahcl.LiteralValue `spec:"lit"`
 }
 
 type PetBlock struct {
-	schemaspec.DefaultExtension
+	schemahcl.DefaultExtension
 	ID        string        `spec:",name"`
 	Breed     string        `spec:"breed"`
 	Born      int           `spec:"born"`
@@ -35,29 +33,29 @@ type PetBlock struct {
 }
 
 func TestInvalidExt(t *testing.T) {
-	r := &schemaspec.Resource{}
+	r := &schemahcl.Resource{}
 	err := r.As(1)
-	require.EqualError(t, err, "schemaspec: expected target to be a pointer")
+	require.EqualError(t, err, "schemahcl: expected target to be a pointer")
 	var p *string
 	err = r.As(p)
-	require.EqualError(t, err, "schemaspec: expected target to be a pointer to a struct")
+	require.EqualError(t, err, "schemahcl: expected target to be a pointer to a struct")
 }
 
 func TestExtension(t *testing.T) {
-	schemaspec.Register("owner", &OwnerBlock{})
-	original := &schemaspec.Resource{
+	schemahcl.Register("owner", &OwnerBlock{})
+	original := &schemahcl.Resource{
 		Name: "name",
 		Type: "owner",
-		Attrs: []*schemaspec.Attr{
-			schemautil.StrLitAttr("first_name", "tzuri"),
-			schemautil.LitAttr("born", "2019"),
-			schemautil.LitAttr("active", "true"),
-			schemautil.LitAttr("bool_ptr", "true"),
-			schemautil.LitAttr("omit_bool1", "true"),
-			schemautil.LitAttr("lit", "1000"),
-			schemautil.LitAttr("extra", "true"),
+		Attrs: []*schemahcl.Attr{
+			schemahcl.StrLitAttr("first_name", "tzuri"),
+			schemahcl.LitAttr("born", "2019"),
+			schemahcl.LitAttr("active", "true"),
+			schemahcl.LitAttr("bool_ptr", "true"),
+			schemahcl.LitAttr("omit_bool1", "true"),
+			schemahcl.LitAttr("lit", "1000"),
+			schemahcl.LitAttr("extra", "true"),
 		},
-		Children: []*schemaspec.Resource{
+		Children: []*schemahcl.Resource{
 			{
 				Name: "extra",
 				Type: "extra",
@@ -73,44 +71,44 @@ func TestExtension(t *testing.T) {
 	require.EqualValues(t, true, owner.Active)
 	require.NotNil(t, owner.BoolPtr)
 	require.EqualValues(t, true, *owner.BoolPtr)
-	require.EqualValues(t, schemautil.LitAttr("lit", "1000").V, owner.Lit)
+	require.EqualValues(t, schemahcl.LitAttr("lit", "1000").V, owner.Lit)
 	attr, ok := owner.Remain().Attr("extra")
 	require.True(t, ok)
 	eb, err := attr.Bool()
 	require.NoError(t, err)
 	require.True(t, eb)
 
-	scan := &schemaspec.Resource{}
+	scan := &schemahcl.Resource{}
 	err = scan.Scan(&owner)
 	require.NoError(t, err)
 	require.EqualValues(t, original, scan)
 }
 
 func TestNested(t *testing.T) {
-	schemaspec.Register("pet", &PetBlock{})
-	pet := &schemaspec.Resource{
+	schemahcl.Register("pet", &PetBlock{})
+	pet := &schemahcl.Resource{
 		Name: "donut",
 		Type: "pet",
-		Attrs: []*schemaspec.Attr{
-			schemautil.StrLitAttr("breed", "golden retriever"),
-			schemautil.LitAttr("born", "2002"),
+		Attrs: []*schemahcl.Attr{
+			schemahcl.StrLitAttr("breed", "golden retriever"),
+			schemahcl.LitAttr("born", "2002"),
 		},
-		Children: []*schemaspec.Resource{
+		Children: []*schemahcl.Resource{
 			{
 				Name: "rotemtam",
 				Type: "owner",
-				Attrs: []*schemaspec.Attr{
-					schemautil.StrLitAttr("first_name", "rotem"),
-					schemautil.LitAttr("born", "1985"),
-					schemautil.LitAttr("active", "true"),
+				Attrs: []*schemahcl.Attr{
+					schemahcl.StrLitAttr("first_name", "rotem"),
+					schemahcl.LitAttr("born", "1985"),
+					schemahcl.LitAttr("active", "true"),
 				},
 			},
 			{
 				Name: "gonnie",
 				Type: "role_model",
-				Attrs: []*schemaspec.Attr{
-					schemautil.StrLitAttr("breed", "golden retriever"),
-					schemautil.LitAttr("born", "1998"),
+				Attrs: []*schemahcl.Attr{
+					schemahcl.StrLitAttr("breed", "golden retriever"),
+					schemahcl.LitAttr("born", "1998"),
 				},
 			},
 		},
@@ -131,7 +129,7 @@ func TestNested(t *testing.T) {
 			Born:  1998,
 		},
 	}, pb)
-	scan := &schemaspec.Resource{}
+	scan := &schemahcl.Resource{}
 	err = scan.Scan(&pb)
 	require.NoError(t, err)
 	require.EqualValues(t, pet, scan)
@@ -142,25 +140,25 @@ func TestNested(t *testing.T) {
 
 func TestRef(t *testing.T) {
 	type A struct {
-		Name string          `spec:",name"`
-		User *schemaspec.Ref `spec:"user"`
+		Name string         `spec:",name"`
+		User *schemahcl.Ref `spec:"user"`
 	}
-	schemaspec.Register("a", &A{})
-	resource := &schemaspec.Resource{
+	schemahcl.Register("a", &A{})
+	resource := &schemahcl.Resource{
 		Name: "x",
 		Type: "a",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*schemahcl.Attr{
 			{
 				K: "user",
-				V: &schemaspec.Ref{V: "$user.rotemtam"},
+				V: &schemahcl.Ref{V: "$user.rotemtam"},
 			},
 		},
 	}
 	var a A
 	err := resource.As(&a)
 	require.NoError(t, err)
-	require.EqualValues(t, &schemaspec.Ref{V: "$user.rotemtam"}, a.User)
-	scan := &schemaspec.Resource{}
+	require.EqualValues(t, &schemahcl.Ref{V: "$user.rotemtam"}, a.User)
+	scan := &schemahcl.Resource{}
 	err = scan.Scan(&a)
 	require.NoError(t, err)
 	require.EqualValues(t, resource, scan)
@@ -168,20 +166,20 @@ func TestRef(t *testing.T) {
 
 func TestListRef(t *testing.T) {
 	type B struct {
-		Name  string            `spec:",name"`
-		Users []*schemaspec.Ref `spec:"users"`
+		Name  string           `spec:",name"`
+		Users []*schemahcl.Ref `spec:"users"`
 	}
-	schemaspec.Register("b", &B{})
-	resource := &schemaspec.Resource{
+	schemahcl.Register("b", &B{})
+	resource := &schemahcl.Resource{
 		Name: "x",
 		Type: "b",
-		Attrs: []*schemaspec.Attr{
+		Attrs: []*schemahcl.Attr{
 			{
 				K: "users",
-				V: &schemaspec.ListValue{
-					V: []schemaspec.Value{
-						&schemaspec.Ref{V: "$user.a8m"},
-						&schemaspec.Ref{V: "$user.rotemtam"},
+				V: &schemahcl.ListValue{
+					V: []schemahcl.Value{
+						&schemahcl.Ref{V: "$user.a8m"},
+						&schemahcl.Ref{V: "$user.rotemtam"},
 					},
 				},
 			},
@@ -192,11 +190,11 @@ func TestListRef(t *testing.T) {
 	err := resource.As(&b)
 	require.NoError(t, err)
 	require.Len(t, b.Users, 2)
-	require.EqualValues(t, []*schemaspec.Ref{
+	require.EqualValues(t, []*schemahcl.Ref{
 		{V: "$user.a8m"},
 		{V: "$user.rotemtam"},
 	}, b.Users)
-	scan := &schemaspec.Resource{}
+	scan := &schemahcl.Resource{}
 	err = scan.Scan(&b)
 	require.NoError(t, err)
 	require.EqualValues(t, resource, scan)
@@ -206,12 +204,12 @@ func TestNameAttr(t *testing.T) {
 	type Named struct {
 		Name string `spec:"name,name"`
 	}
-	schemaspec.Register("named", &Named{})
-	resource := &schemaspec.Resource{
+	schemahcl.Register("named", &Named{})
+	resource := &schemahcl.Resource{
 		Name: "id",
 		Type: "named",
-		Attrs: []*schemaspec.Attr{
-			schemautil.StrLitAttr("name", "atlas"),
+		Attrs: []*schemahcl.Attr{
+			schemahcl.StrLitAttr("name", "atlas"),
 		},
 	}
 	var tgt Named
diff --git a/schema/schemaspec/schemahcl/hcl_test.go b/schemahcl/hcl_test.go
similarity index 90%
rename from schema/schemaspec/schemahcl/hcl_test.go
rename to schemahcl/hcl_test.go
index 285add40..a417f329 100644
--- a/schema/schemaspec/schemahcl/hcl_test.go
+++ b/schemahcl/hcl_test.go
@@ -11,8 +11,6 @@ import (
 	"path/filepath"
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -30,7 +28,7 @@ bl = [true, false]
 		StringList []string `spec:"sl"`
 		BoolList   []bool   `spec:"bl"`
 	}
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, 1, test.Int)
 	require.EqualValues(t, true, test.Bool)
@@ -69,7 +67,7 @@ func TestResource(t *testing.T) {
 		}
 	)
 	var test File
-	err := Unmarshal([]byte(f), &test)
+	err := New().EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.Len(t, test.Endpoints, 1)
 	expected := &Endpoint{
@@ -118,7 +116,7 @@ show "seinfeld" {
 			WithScopedEnums("show.day", "SUN", "MON", "TUE"),
 		}
 	)
-	err := New(opts...).UnmarshalSpec([]byte(f), &test)
+	err := New(opts...).EvalBytes([]byte(f), &test, nil)
 	if err != nil {
 		panic(err)
 	}
@@ -181,8 +179,8 @@ func TestInterface(t *testing.T) {
 			Animal Animal `spec:""`
 		}
 	)
-	schemaspec.Register("lion", &Lion{})
-	schemaspec.Register("parrot", &Parrot{})
+	Register("lion", &Lion{})
+	Register("parrot", &Parrot{})
 	t.Run("single", func(t *testing.T) {
 		f := `
 cast "lion_king" {
@@ -194,7 +192,7 @@ cast "lion_king" {
 		var test struct {
 			Cast *Cast `spec:"cast"`
 		}
-		err := Unmarshal([]byte(f), &test)
+		err := New().EvalBytes([]byte(f), &test, nil)
 		require.NoError(t, err)
 		require.EqualValues(t, &Cast{
 			Animal: &Lion{
@@ -217,7 +215,7 @@ zoo "ramat_gan" {
 		var test struct {
 			Zoo *Zoo `spec:"zoo"`
 		}
-		err := Unmarshal([]byte(f), &test)
+		err := New().EvalBytes([]byte(f), &test, nil)
 		require.NoError(t, err)
 		require.EqualValues(t, &Zoo{
 			Animals: []Animal{
@@ -245,7 +243,7 @@ func TestQualified(t *testing.T) {
 	h := `person "dr" "jekyll" {
 }
 `
-	err := Unmarshal([]byte(h), &test)
+	err := New().EvalBytes([]byte(h), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, test.Person, &Person{
 		Title: "dr",
@@ -270,7 +268,7 @@ ref = named.block_id.name
 		Named *Named `spec:"named"`
 		Ref   string `spec:"ref"`
 	}
-	err := Unmarshal([]byte(h), &test)
+	err := New().EvalBytes([]byte(h), &test, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, &Named{
 		Name: "atlas",
@@ -284,12 +282,12 @@ func TestRefPatch(t *testing.T) {
 			Name string `spec:"name,name"`
 		}
 		Person struct {
-			Name   string          `spec:",name"`
-			Family *schemaspec.Ref `spec:"family"`
+			Name   string `spec:",name"`
+			Family *Ref   `spec:"family"`
 		}
 	)
-	schemaspec.Register("family", &Family{})
-	schemaspec.Register("person", &Person{})
+	Register("family", &Family{})
+	Register("person", &Person{})
 	var test struct {
 		Families []*Family `spec:"family"`
 		People   []*Person `spec:"person"`
@@ -307,7 +305,7 @@ person "rotem" {
 	family = family.default
 }
 `
-	err := New().Eval([]byte(h), &test, map[string]string{"family_name": "tam"})
+	err := New().EvalBytes([]byte(h), &test, map[string]string{"family_name": "tam"})
 	require.NoError(t, err)
 	require.EqualValues(t, "$family.tam", test.People[0].Family.V)
 }
diff --git a/schema/schemaspec/spec_test.go b/schemahcl/spec_test.go
similarity index 97%
rename from schema/schemaspec/spec_test.go
rename to schemahcl/spec_test.go
index eef214e7..d92cc73e 100644
--- a/schema/schemaspec/spec_test.go
+++ b/schemahcl/spec_test.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package schemaspec
+package schemahcl
 
 import (
 	"testing"
diff --git a/schema/schemaspec/schemahcl/types_test.go b/schemahcl/types_test.go
similarity index 59%
rename from schema/schemaspec/schemahcl/types_test.go
rename to schemahcl/types_test.go
index d4384b81..1e6d473f 100644
--- a/schema/schemaspec/schemahcl/types_test.go
+++ b/schemahcl/types_test.go
@@ -8,54 +8,52 @@ import (
 	"reflect"
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/internal/schemautil"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestTypePrint(t *testing.T) {
-	intSpec := &schemaspec.TypeSpec{
+	intSpec := &TypeSpec{
 		Name: "int",
 		T:    "int",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			unsignedTypeAttr(),
 		},
 	}
 	for _, tt := range []struct {
-		spec     *schemaspec.TypeSpec
-		typ      *schemaspec.Type
+		spec     *TypeSpec
+		typ      *Type
 		expected string
 	}{
 		{
 			spec:     intSpec,
-			typ:      &schemaspec.Type{T: "int"},
+			typ:      &Type{T: "int"},
 			expected: "int",
 		},
 		{
 			spec:     intSpec,
-			typ:      &schemaspec.Type{T: "int", Attrs: []*schemaspec.Attr{schemautil.LitAttr("unsigned", "true")}},
+			typ:      &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
 			expected: "int unsigned",
 		},
 		{
-			spec: &schemaspec.TypeSpec{
+			spec: &TypeSpec{
 				Name:       "float",
 				T:          "float",
-				Attributes: []*schemaspec.TypeAttr{unsignedTypeAttr()},
+				Attributes: []*TypeAttr{unsignedTypeAttr()},
 			},
-			typ:      &schemaspec.Type{T: "float", Attrs: []*schemaspec.Attr{schemautil.LitAttr("unsigned", "true")}},
+			typ:      &Type{T: "float", Attrs: []*Attr{LitAttr("unsigned", "true")}},
 			expected: "float unsigned",
 		},
 		{
-			spec: &schemaspec.TypeSpec{
+			spec: &TypeSpec{
 				T:    "varchar",
 				Name: "varchar",
-				Attributes: []*schemaspec.TypeAttr{
+				Attributes: []*TypeAttr{
 					{Name: "size", Kind: reflect.Int, Required: true},
 				},
 			},
-			typ:      &schemaspec.Type{T: "varchar", Attrs: []*schemaspec.Attr{schemautil.LitAttr("size", "255")}},
+			typ:      &Type{T: "varchar", Attrs: []*Attr{LitAttr("size", "255")}},
 			expected: "varchar(255)",
 		},
 	} {
@@ -72,7 +70,7 @@ func TestTypePrint(t *testing.T) {
 
 func TestRegistry(t *testing.T) {
 	r := &TypeRegistry{}
-	text := &schemaspec.TypeSpec{Name: "text", T: "text"}
+	text := &TypeSpec{Name: "text", T: "text"}
 	err := r.Register(text)
 	require.NoError(t, err)
 	err = r.Register(text)
@@ -84,54 +82,54 @@ func TestRegistry(t *testing.T) {
 
 func TestValidSpec(t *testing.T) {
 	registry := &TypeRegistry{}
-	err := registry.Register(&schemaspec.TypeSpec{
+	err := registry.Register(&TypeSpec{
 		Name: "X",
 		T:    "X",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "a", Required: false, Kind: reflect.Slice},
 			{Name: "b", Required: true},
 		},
 	})
 	require.EqualError(t, err, `specutil: invalid typespec "X": attr "a" is of kind slice but not last`)
-	err = registry.Register(&schemaspec.TypeSpec{
+	err = registry.Register(&TypeSpec{
 		Name: "Z",
 		T:    "Z",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "b", Required: true},
 			{Name: "a", Required: false, Kind: reflect.Slice},
 		},
 	})
 	require.NoError(t, err)
-	err = registry.Register(&schemaspec.TypeSpec{
+	err = registry.Register(&TypeSpec{
 		Name: "Z2",
 		T:    "Z2",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "a", Required: false, Kind: reflect.Slice},
 		},
 	})
 	require.NoError(t, err)
-	err = registry.Register(&schemaspec.TypeSpec{
+	err = registry.Register(&TypeSpec{
 		Name: "X",
 		T:    "X",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "a", Required: false},
 			{Name: "b", Required: true},
 		},
 	})
 	require.EqualError(t, err, `specutil: invalid typespec "X": attr "b" required after optional attr`)
-	err = registry.Register(&schemaspec.TypeSpec{
+	err = registry.Register(&TypeSpec{
 		Name: "X",
 		T:    "X",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "a", Required: true},
 			{Name: "b", Required: false},
 		},
 	})
 	require.NoError(t, err)
-	err = registry.Register(&schemaspec.TypeSpec{
+	err = registry.Register(&TypeSpec{
 		Name: "Y",
 		T:    "Y",
-		Attributes: []*schemaspec.TypeAttr{
+		Attributes: []*TypeAttr{
 			{Name: "a", Required: false},
 			{Name: "b", Required: false},
 		},
@@ -142,24 +140,24 @@ func TestValidSpec(t *testing.T) {
 func TestRegistryConvert(t *testing.T) {
 	r := &TypeRegistry{}
 	err := r.Register(
-		TypeSpec("varchar", WithAttributes(SizeTypeAttr(true))),
-		TypeSpec("int", WithAttributes(unsignedTypeAttr())),
-		TypeSpec(
+		NewTypeSpec("varchar", WithAttributes(SizeTypeAttr(true))),
+		NewTypeSpec("int", WithAttributes(unsignedTypeAttr())),
+		NewTypeSpec(
 			"decimal",
 			WithAttributes(
-				&schemaspec.TypeAttr{
+				&TypeAttr{
 					Name:     "precision",
 					Kind:     reflect.Int,
 					Required: false,
 				},
-				&schemaspec.TypeAttr{
+				&TypeAttr{
 					Name:     "scale",
 					Kind:     reflect.Int,
 					Required: false,
 				},
 			),
 		),
-		TypeSpec("enum", WithAttributes(&schemaspec.TypeAttr{
+		NewTypeSpec("enum", WithAttributes(&TypeAttr{
 			Name:     "values",
 			Kind:     reflect.Slice,
 			Required: true,
@@ -168,54 +166,54 @@ func TestRegistryConvert(t *testing.T) {
 	require.NoError(t, err)
 	for _, tt := range []struct {
 		typ         schema.Type
-		expected    *schemaspec.Type
+		expected    *Type
 		expectedErr string
 	}{
 		{
 			typ:      &schema.StringType{T: "varchar", Size: 255},
-			expected: &schemaspec.Type{T: "varchar", Attrs: []*schemaspec.Attr{schemautil.LitAttr("size", "255")}},
+			expected: &Type{T: "varchar", Attrs: []*Attr{LitAttr("size", "255")}},
 		},
 		{
 			typ:      &schema.IntegerType{T: "int", Unsigned: true},
-			expected: &schemaspec.Type{T: "int", Attrs: []*schemaspec.Attr{schemautil.LitAttr("unsigned", "true")}},
+			expected: &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
 		},
 		{
 			typ:      &schema.IntegerType{T: "int", Unsigned: true},
-			expected: &schemaspec.Type{T: "int", Attrs: []*schemaspec.Attr{schemautil.LitAttr("unsigned", "true")}},
+			expected: &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Precision: 10, Scale: 2}, // decimal(10,2)
-			expected: &schemaspec.Type{T: "decimal", Attrs: []*schemaspec.Attr{
-				schemautil.LitAttr("precision", "10"),
-				schemautil.LitAttr("scale", "2"),
+			expected: &Type{T: "decimal", Attrs: []*Attr{
+				LitAttr("precision", "10"),
+				LitAttr("scale", "2"),
 			}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Precision: 10}, // decimal(10)
-			expected: &schemaspec.Type{T: "decimal", Attrs: []*schemaspec.Attr{
-				schemautil.LitAttr("precision", "10"),
+			expected: &Type{T: "decimal", Attrs: []*Attr{
+				LitAttr("precision", "10"),
 			}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Scale: 2}, // decimal(0,2)
-			expected: &schemaspec.Type{T: "decimal", Attrs: []*schemaspec.Attr{
-				schemautil.LitAttr("precision", "0"),
-				schemautil.LitAttr("scale", "2"),
+			expected: &Type{T: "decimal", Attrs: []*Attr{
+				LitAttr("precision", "0"),
+				LitAttr("scale", "2"),
 			}},
 		},
 		{
 			typ:      &schema.DecimalType{T: "decimal"}, // decimal
-			expected: &schemaspec.Type{T: "decimal"},
+			expected: &Type{T: "decimal"},
 		},
 		{
 			typ: &schema.EnumType{T: "enum", Values: []string{"on", "off"}},
-			expected: &schemaspec.Type{T: "enum", Attrs: []*schemaspec.Attr{
-				schemautil.ListAttr("values", `"on"`, `"off"`),
+			expected: &Type{T: "enum", Attrs: []*Attr{
+				ListAttr("values", `"on"`, `"off"`),
 			}},
 		},
 		{
 			typ:         nil,
-			expected:    &schemaspec.Type{},
+			expected:    &Type{},
 			expectedErr: "specutil: invalid schema.Type on Convert",
 		},
 	} {
@@ -231,8 +229,8 @@ func TestRegistryConvert(t *testing.T) {
 	}
 }
 
-func unsignedTypeAttr() *schemaspec.TypeAttr {
-	return &schemaspec.TypeAttr{
+func unsignedTypeAttr() *TypeAttr {
+	return &TypeAttr{
 		Name: "unsigned",
 		Kind: reflect.Bool,
 	}
diff --git a/sql/internal/spectest/spectest.go b/sql/internal/spectest/spectest.go
index 8b12014b..b2ca5e4e 100644
--- a/sql/internal/spectest/spectest.go
+++ b/sql/internal/spectest/spectest.go
@@ -8,16 +8,15 @@ import (
 	"reflect"
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/schema/schemaspec/schemahcl"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/schema"
-
+	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/stretchr/testify/require"
 )
 
-// RegistrySanityTest runs a sanity for a TypeRegistry, generated a dummy *schemaspec.Type
-// then converting it to a schema.Type and back to a *schemaspec.Type.
+// RegistrySanityTest runs a sanity for a TypeRegistry, generated a dummy *schemahcl.Type
+// then converting it to a schema.Type and back to a *schemahcl.Type.
 func RegistrySanityTest(t *testing.T, registry *schemahcl.TypeRegistry, skip []string) {
 	for _, ts := range registry.Specs() {
 		if contains(ts.Name, skip) {
@@ -62,7 +61,10 @@ table "users" {
 }
 `
 	var test schema.Realm
-	err := evaluator.Eval([]byte(h), &test, map[string]string{"tenant": "rotemtam"})
+	p := hclparse.NewParser()
+	_, diag := p.ParseHCL([]byte(h), "")
+	require.False(t, diag.HasErrors())
+	err := evaluator.Eval(p, &test, map[string]string{"tenant": "rotemtam"})
 	require.NoError(t, err)
 	require.EqualValues(t, "rotemtam", test.Schemas[0].Name)
 	require.Len(t, test.Schemas[0].Tables, 1)
@@ -77,10 +79,10 @@ func contains(s string, l []string) bool {
 	return false
 }
 
-func dummyType(t *testing.T, ts *schemaspec.TypeSpec) *schemaspec.Type {
-	spec := &schemaspec.Type{T: ts.T}
+func dummyType(t *testing.T, ts *schemahcl.TypeSpec) *schemahcl.Type {
+	spec := &schemahcl.Type{T: ts.T}
 	for _, attr := range ts.Attributes {
-		var a *schemaspec.Attr
+		var a *schemahcl.Attr
 		switch attr.Kind {
 		case reflect.Int, reflect.Int64:
 			a = specutil.LitAttr(attr.Name, "2")
diff --git a/sql/internal/specutil/convert_test.go b/sql/internal/specutil/convert_test.go
index 422d56e5..03da4348 100644
--- a/sql/internal/specutil/convert_test.go
+++ b/sql/internal/specutil/convert_test.go
@@ -7,7 +7,7 @@ package specutil
 import (
 	"testing"
 
-	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
 
@@ -65,14 +65,14 @@ func TestFromForeignKey(t *testing.T) {
 	require.NoError(t, err)
 	require.EqualValues(t, &sqlspec.ForeignKey{
 		Symbol: "fk",
-		Columns: []*schemaspec.Ref{
+		Columns: []*schemahcl.Ref{
 			{V: "$column.parent_id"},
 		},
-		RefColumns: []*schemaspec.Ref{
+		RefColumns: []*schemahcl.Ref{
 			{V: "$column.id"},
 		},
-		OnUpdate: &schemaspec.Ref{V: "NO_ACTION"},
-		OnDelete: &schemaspec.Ref{V: "CASCADE"},
+		OnUpdate: &schemahcl.Ref{V: "NO_ACTION"},
+		OnDelete: &schemahcl.Ref{V: "CASCADE"},
 	}, key)
 
 	fk.OnDelete = ""
@@ -81,10 +81,10 @@ func TestFromForeignKey(t *testing.T) {
 	require.NoError(t, err)
 	require.EqualValues(t, &sqlspec.ForeignKey{
 		Symbol: "fk",
-		Columns: []*schemaspec.Ref{
+		Columns: []*schemahcl.Ref{
 			{V: "$column.parent_id"},
 		},
-		RefColumns: []*schemaspec.Ref{
+		RefColumns: []*schemahcl.Ref{
 			{V: "$column.id"},
 		},
 	}, key)
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index 86ab2a4f..410e155a 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -10,7 +10,6 @@ import (
 
 	"ariga.io/atlas/sql/internal/spectest"
 	"ariga.io/atlas/sql/schema"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -102,7 +101,7 @@ table "accounts" {
 }
 `
 	var s schema.Schema
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 
 	exp := &schema.Schema{
@@ -379,7 +378,7 @@ schema "test" {
 			&schema.Collation{V: "utf8mb4_0900_ai_ci"},
 		}
 	)
-	require.NoError(t, UnmarshalHCL(buf, &s2))
+	require.NoError(t, EvalHCLBytes(buf, &s2, nil))
 	require.Equal(t, utf8mb4, s2.Attrs)
 	posts, ok := s2.Table("posts")
 	require.True(t, ok)
@@ -565,7 +564,7 @@ table "users" {
 }
 `
 	)
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	c := schema.NewStringColumn("name", "text")
 	exp := schema.New("test").
@@ -758,7 +757,7 @@ table "users" {
 }
 `
 	)
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	exp := schema.New("test").
 		AddTables(
@@ -1143,14 +1142,14 @@ schema "test" {
 }
 `, tt.typeExpr, lineIfSet(tt.extraAttr))
 			var test schema.Schema
-			err := UnmarshalHCL([]byte(doc), &test)
+			err := EvalHCLBytes([]byte(doc), &test, nil)
 			require.NoError(t, err)
 			colspec := test.Tables[0].Columns[0]
 			require.EqualValues(t, tt.expected, colspec.Type.Type)
 			spec, err := MarshalHCL(&test)
 			require.NoError(t, err)
 			var after schema.Schema
-			err = UnmarshalHCL(spec, &after)
+			err = EvalHCLBytes(spec, &after, nil)
 			require.NoError(t, err)
 			require.EqualValues(t, tt.expected, after.Tables[0].Columns[0].Type.Type)
 		})
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index 8d269761..98209096 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -11,7 +11,6 @@ import (
 
 	"ariga.io/atlas/sql/internal/spectest"
 	"ariga.io/atlas/sql/schema"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -98,7 +97,7 @@ enum "account_type" {
 }
 `
 	var s schema.Schema
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	exp := &schema.Schema{
 		Name: "schema",
@@ -275,7 +274,7 @@ table "t" {
 `
 	t.Run("Invalid", func(t *testing.T) {
 		f := fmt.Sprintf(f, "UNK")
-		err := UnmarshalHCL([]byte(f), &schema.Schema{})
+		err := EvalHCLBytes([]byte(f), &schema.Schema{}, nil)
 		require.Error(t, err)
 	})
 	t.Run("Valid", func(t *testing.T) {
@@ -283,7 +282,7 @@ table "t" {
 			s schema.Schema
 			f = fmt.Sprintf(f, "HASH")
 		)
-		err := UnmarshalHCL([]byte(f), &s)
+		err := EvalHCLBytes([]byte(f), &s, nil)
 		require.NoError(t, err)
 		idx := s.Tables[0].Indexes[0]
 		require.Equal(t, IndexTypeHash, idx.Attrs[0].(*IndexType).T)
@@ -306,7 +305,7 @@ table "t" {
 }
 `
 	var s schema.Schema
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	idx := s.Tables[0].Indexes[0]
 	require.Equal(t, IndexTypeBRIN, idx.Attrs[0].(*IndexType).T)
@@ -333,7 +332,7 @@ table "logs" {
 }
 `
 		)
-		err := UnmarshalHCL([]byte(f), s)
+		err := EvalHCLBytes([]byte(f), s, nil)
 		require.NoError(t, err)
 		c := schema.NewStringColumn("name", "text")
 		expected := schema.New("test").
@@ -363,7 +362,7 @@ table "logs" {
 }
 `
 		)
-		err := UnmarshalHCL([]byte(f), s)
+		err := EvalHCLBytes([]byte(f), s, nil)
 		require.NoError(t, err)
 		c := schema.NewStringColumn("name", "text")
 		expected := schema.New("test").
@@ -372,7 +371,7 @@ table "logs" {
 	})
 
 	t.Run("Invalid", func(t *testing.T) {
-		err := UnmarshalHCL([]byte(`
+		err := EvalHCLBytes([]byte(`
 			schema "test" {}
 			table "logs" {
 				schema = schema.test
@@ -381,10 +380,10 @@ table "logs" {
 					columns = [column.name]
 				}
 			}
-		`), &schema.Schema{})
+		`), &schema.Schema{}, nil)
 		require.EqualError(t, err, "missing attribute logs.partition.type")
 
-		err = UnmarshalHCL([]byte(`
+		err = EvalHCLBytes([]byte(`
 			schema "test" {}
 			table "logs" {
 				schema = schema.test
@@ -393,10 +392,10 @@ table "logs" {
 					type = HASH
 				}
 			}
-		`), &schema.Schema{})
+		`), &schema.Schema{}, nil)
 		require.EqualError(t, err, `missing columns or expressions for logs.partition`)
 
-		err = UnmarshalHCL([]byte(`
+		err = EvalHCLBytes([]byte(`
 			schema "test" {}
 			table "logs" {
 				schema = schema.test
@@ -407,7 +406,7 @@ table "logs" {
 					by { column = column.name }
 				}
 			}
-		`), &schema.Schema{})
+		`), &schema.Schema{}, nil)
 		require.EqualError(t, err, `multiple definitions for logs.partition, use "columns" or "by"`)
 	})
 }
@@ -581,7 +580,7 @@ table "t" {
 `
 	t.Run("Invalid", func(t *testing.T) {
 		f := fmt.Sprintf(f, "UNK")
-		err := UnmarshalHCL([]byte(f), &schema.Schema{})
+		err := EvalHCLBytes([]byte(f), &schema.Schema{}, nil)
 		require.Error(t, err)
 	})
 	t.Run("Valid", func(t *testing.T) {
@@ -589,7 +588,7 @@ table "t" {
 			s schema.Schema
 			f = fmt.Sprintf(f, "ALWAYS")
 		)
-		err := UnmarshalHCL([]byte(f), &s)
+		err := EvalHCLBytes([]byte(f), &s, nil)
 		require.NoError(t, err)
 		id := s.Tables[0].Columns[0].Attrs[0].(*Identity)
 		require.Equal(t, GeneratedTypeAlways, id.Generation)
@@ -663,7 +662,7 @@ table "users" {
 }
 `
 	)
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	exp := schema.New("test").
 		AddTables(
@@ -1047,14 +1046,14 @@ func TestTypes(t *testing.T) {
 schema "test" {
 }
 `, tt.typeExpr)
-			err := UnmarshalHCL([]byte(doc), &test)
+			err := EvalHCLBytes([]byte(doc), &test, nil)
 			require.NoError(t, err)
 			colspec := test.Tables[0].Columns[0]
 			require.EqualValues(t, tt.expected, colspec.Type.Type)
 			spec, err := MarshalHCL(&test)
 			require.NoError(t, err)
 			var after schema.Schema
-			err = UnmarshalHCL(spec, &after)
+			err = EvalHCLBytes(spec, &after, nil)
 			require.NoError(t, err)
 			require.EqualValues(t, tt.expected, after.Tables[0].Columns[0].Type.Type)
 		})
diff --git a/sql/sqlite/inspect_test.go b/sql/sqlite/inspect_test.go
index f8ee9d79..09f5f7e6 100644
--- a/sql/sqlite/inspect_test.go
+++ b/sql/sqlite/inspect_test.go
@@ -520,7 +520,7 @@ func (m mock) systemVars(version string) {
 }
 
 func (m mock) tableExists(table string, exists bool, stmt ...string) {
-	m.ExpectQuery(sqltest.Escape(databasesQuery + " WHERE name IN (?)")).
+	m.ExpectQuery(sqltest.Escape(fmt.Sprintf(databasesQueryArgs, "?"))).
 		WithArgs("main").
 		WillReturnRows(sqltest.Rows(`
  name |   file    
diff --git a/sql/sqlite/sqlspec_test.go b/sql/sqlite/sqlspec_test.go
index 8fc97d43..665eb4aa 100644
--- a/sql/sqlite/sqlspec_test.go
+++ b/sql/sqlite/sqlspec_test.go
@@ -10,7 +10,6 @@ import (
 
 	"ariga.io/atlas/sql/internal/spectest"
 	"ariga.io/atlas/sql/schema"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -175,7 +174,7 @@ table "accounts" {
 	}
 
 	var s schema.Schema
-	err := UnmarshalHCL([]byte(f), &s)
+	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)
 	require.EqualValues(t, exp, &s)
 }
@@ -407,14 +406,14 @@ func TestTypes(t *testing.T) {
 schema "test" {
 }
 `, tt.typeExpr)
-			err := UnmarshalHCL([]byte(doc), &test)
+			err := EvalHCLBytes([]byte(doc), &test, nil)
 			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 = UnmarshalHCL(spec, &after)
+			err = EvalHCLBytes(spec, &after, nil)
 			require.NoError(t, err)
 			require.EqualValues(t, tt.expected, after.Tables[0].Columns[0].Type.Type)
 		})