Repository: ariga/atlas
Pull request #1664 (2023-05-16 11:11:31)

doc/website/blog: vscode extension


Patch:
diff --git a/doc/website/blog/2023-05-16-vscode-extension.md b/doc/website/blog/2023-05-16-vscode-extension.md
new file mode 100644
index 00000000..244a159c
--- /dev/null
+++ b/doc/website/blog/2023-05-16-vscode-extension.md
@@ -0,0 +1,101 @@
+---
+title: "Announcing the Atlas HCL VSCode Extension"
+authors: datdao
+tags: [vscode, hcl]
+---
+
+## Introduction
+
+Atlas supports many ways to describe database schemas. Using [Schema Loaders](https://atlasgo.io/blog/2023/01/05/atlas-v090#schema-loaders),
+Atlas gives users the flexibility to define schemas using plain SQL, a connection to another database or using 
+[Atlas HCL](https://atlasgo.io/atlas-schema/sql-resources). 
+
+## What is Atlas HCL?
+
+Atlas includes a data-definition language based on a Terraform-like [syntax](https://atlasgo.io/guides/ddl#hcl). This
+language, which was designed specifically for describing database schemas is based on HashiCorp's [HCL](https://github.com/hashicorp/hcl)
+toolkit for creating structured configuration languages.
+
+This data definition language is easy to read, edit, and visualize, and it allows us to organize our schema data in a
+efficient way that has many benefits to using plain SQL.
+
+## What is the Atlas HCL VSCode Extension?
+
+To make it easier for users to define schemas using Atlas HCL, we're happy to announce the release of the official
+[Atlas HCL VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Ariga.atlas-hcl). 
+This extension provides a rich set of features that will help users define schemas using Atlas HCL.
+
+In this post, we'll take a closer look at the features of this extension
+and how it can benefit your work.
+
+## Features
+
+### Auto Completion
+
+The extension provides auto completion support for all SQL resources available within Atlas. For a comprehensive
+list of the supported resources, please visit this
+document: [SQL Resources](https://atlasgo.io/atlas-schema/sql-resources).
+
+![Completion](https://atlasgo.io/uploads/images/vscode/vscode-completion.gif)
+
+![Batch Completion](https://atlasgo.io/uploads/images/vscode/vscode-batch-completion.gif)
+
+### Referencing Qualified Tables
+
+This refers to a feature that allows for referencing tables and their columns in a structured manner, either relative to
+the current location (e.g. **_column.id_**) or absolute to the table (e.g. **_table.user.column.id_**).
+
+![Referencing Qualified Tables](https://atlasgo.io/uploads/images/vscode/vscode-references.gif)
+
+### Linting
+
+To ensure that errors are caught from the beginning, the Atlas HCL VSCode Extension offers a range of validations that
+enhance type safety for users. The specific validations provided will vary depending on the SQL dialect being used. This
+feature is designed to help users identify and fix issues early on, resulting in a more efficient and effective
+schema-building process.
+
+![Linting](https://atlasgo.io/uploads/images/vscode/vscode-validation.png)
+
+## Usage
+
+To use this extension, you can either install it directly from the VSCode desktop or download it from
+the [Marketplace](https://marketplace.visualstudio.com/items?itemName=Ariga.atlas-hcl). The extension will appear in the
+extensions list as **Atlas HCL**.
+
+![Extension](https://atlasgo.io/uploads/images/vscode/vscode-extension.png)
+
+After installing the Atlas HCL extension, it automatically registers for all the languages that Atlas supports. These
+languages include:
+
+| Name              | Language ID             | Description           
+|-------------------|-------------------------|-----------------------|
+| Atlas SQLite      | atlas-schema-sqlite     | SQLite dialect        
+| Atlas MySQL       | atlas-schema-mysql      | MySQL dialect         
+| Atlas PostgresQL  | atlas-schema-postgresql | PostgresQL dialect    
+| Atlas MariaDB     | atlas-schema-mariadb    | MariaDB dialect       
+| Atlas Config File | atlas-config            | Project Configuration 
+
+By default, the extension will auto-detect atlas.hcl files in your project.
+However, if you have any schema files in your project, you must choose the language for them. You can do this in three
+ways in vscode:
+
+1. Click on "Change Language" for your schema files in this section at bottom. (See image below)
+
+![Installation](https://atlasgo.io/uploads/images/vscode/vscode-installation.png)
+
+2. Use the Command Palette and choose "Change language mode" (See image below)
+
+![Installation by command palette](https://atlasgo.io/uploads/images/vscode/vscode-installation-2.png)
+
+3. Use [Language Identifiers](https://code.visualstudio.com/docs/languages/identifiers)
+
+## Wrapping Up
+
+In this post, we've introduced **The Atlas HCL Extension**. There's much more inside, and we invite you to install it
+today and give it a try.
+
+## What next?
+
+* Follow the [Getting Started](https://atlasgo.io/cli/getting-started/setting-up) guide.
+* Join our [Discord Server](https://discord.gg/zZ6sWVg6NT).
+* Follow us [on Twitter](https://twitter.com/ariga_io).
diff --git a/doc/website/blog/authors.yml b/doc/website/blog/authors.yml
index 653a963e..4c613851 100644
--- a/doc/website/blog/authors.yml
+++ b/doc/website/blog/authors.yml
@@ -34,3 +34,7 @@ shani-a:
   name: Shani Cohen
   url: "https://github.com/shani-a"
   image_url: "https://avatars.githubusercontent.com/u/117578627?v=4"
+datdao:
+  name: Dat Dao
+  url: "https://github.com/datdao"
+  image_url: "https://avatars.githubusercontent.com/u/16095902?v=4"
diff --git a/doc/website/sidebars.js b/doc/website/sidebars.js
index 1f9b9b86..20e99614 100644
--- a/doc/website/sidebars.js
+++ b/doc/website/sidebars.js
@@ -35,7 +35,16 @@ module.exports = {
             collapsed: false,
             items: [
                 {type: 'doc', id: 'versioned/diff', label: 'Generating Migrations'},
-                {type: 'doc', id: 'versioned/lint', label: 'Migration Linting'},
+                {
+                    type: 'category', label: 'Migration Linting',
+                    link: {
+                        type: 'doc',
+                        id: 'versioned/lint',
+                    },
+                    items: [
+                        {type: 'doc', id: 'lint/analyzers', label: 'Analyzers and Checks'},
+                    ]
+                },
                 {type: 'doc', id: 'versioned/new', label: 'Manual Migrations'},
                 {type: 'doc', id: 'versioned/apply', label: 'Applying Migrations'},
                 {type: 'doc', id: 'versioned/troubleshoot', label: 'Migration Troubleshooting'},
diff --git a/sql/mysql/diff.go b/sql/mysql/diff.go
index cf8f5649..05585bf2 100644
--- a/sql/mysql/diff.go
+++ b/sql/mysql/diff.go
@@ -68,6 +68,9 @@ func (d *diff) TableAttrDiff(from, to *schema.Table) ([]schema.Change, error) {
 	if change := d.collationChange(from.Attrs, from.Schema.Attrs, to.Attrs); change != noChange {
 		changes = append(changes, change)
 	}
+	if change := d.engineChange(from.Attrs, to.Attrs); change != noChange {
+		changes = append(changes, change)
+	}
 	if !d.SupportsCheck() && sqlx.Has(to.Attrs, &schema.Check{}) {
 		return nil, fmt.Errorf("version %q does not support CHECK constraints", d.V)
 	}
@@ -275,6 +278,35 @@ func (*diff) collationChange(from, top, to []schema.Attr) schema.Change {
 	return noChange
 }
 
+// engineChange returns the schema change for migrating the table engine in case
+// it was changed.
+func (*diff) engineChange(from, to []schema.Attr) schema.Change {
+	var fromE, toE Engine
+	switch fromHas, toHas := sqlx.Has(from, &fromE), sqlx.Has(to, &toE); {
+	// Both engines are defined but different.
+	case fromHas && toHas && strings.ToLower(fromE.V) != strings.ToLower(toE.V):
+		return &schema.ModifyAttr{
+			From: &fromE,
+			To:   &toE,
+		}
+	// If the engine attribute has been removed from the desired state (e.g., HCL), and the current state
+	// is not the default, we change the engine to InnoDB (the default for MySQL, unless configured otherwise).
+	case fromHas && !toHas && !fromE.Default && strings.ToLower(fromE.V) != strings.ToLower(EngineInnoDB):
+		return &schema.ModifyAttr{
+			From: &fromE,
+			To:   &Engine{V: EngineInnoDB, Default: true},
+		}
+	// In case the engine attribute was added to the desired state (e.g., HCL)
+	// and it is not the default, we modify the engine to the desired value.
+	case !fromHas && toHas && !toE.Default && strings.ToLower(fromE.V) != strings.ToLower(EngineInnoDB):
+		return &schema.ModifyAttr{
+			From: &Engine{V: EngineInnoDB, Default: true},
+			To:   &toE,
+		}
+	}
+	return noChange
+}
+
 // charsetChange returns the schema change for migrating the collation if
 // it was changed, and it is not the default attribute inherited from its parent.
 func (*diff) charsetChange(from, top, to []schema.Attr) schema.Change {
diff --git a/sql/mysql/migrate.go b/sql/mysql/migrate.go
index 1f79534e..dcde0f30 100644
--- a/sql/mysql/migrate.go
+++ b/sql/mysql/migrate.go
@@ -646,12 +646,13 @@ func (s *state) tableAttr(b *sqlx.Builder, c schema.Change, attrs ...schema.Attr
 		case *CreateOptions:
 			b.P(a.V)
 		case *AutoIncrement:
-			// Update the AUTO_INCREMENT if it is an update change or it is not the default.
+			// Update the AUTO_INCREMENT if it is a table modification, or it is not the default.
 			if _, ok := c.(*schema.ModifyAttr); ok || a.V > 1 {
 				b.P("AUTO_INCREMENT", strconv.FormatInt(a.V, 10))
 			}
 		case *Engine:
-			if !a.Default {
+			// Update the ENGINE if it is a table modification, or it is not the default.
+			if _, ok := c.(*schema.ModifyAttr); ok || !a.Default {
 				b.P("ENGINE", a.V)
 			}
 		case *schema.Check:


Test patch:
diff --git a/internal/integration/testdata/mysql/table-engine.txt b/internal/integration/testdata/mysql/table-engine.txt
new file mode 100644
index 00000000..da251ad7
--- /dev/null
+++ b/internal/integration/testdata/mysql/table-engine.txt
@@ -0,0 +1,99 @@
+only mysql8
+
+# InnoDB as the default storage engine.
+apply 1.hcl
+cmphcl 1.inspect.hcl
+
+# Change the InnoDB (default storage engine) to MyISAM.
+apply 2.hcl
+cmphcl 2.inspect.hcl
+
+# Drop MyISAM changes the engine to the default (InnoDB).
+apply 3.hcl
+cmphcl 3.inspect.hcl
+
+-- 1.hcl --
+schema "script_table_engine" {
+    charset = "$charset"
+    collate = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    engine = InnoDB
+    column "name" {
+      null = false
+      type = varchar(255)
+    }
+    charset = "$charset"
+    collate = "$collate"
+}
+
+-- 1.inspect.hcl --
+table "users" {
+  schema = schema.script_table_engine
+  column "name" {
+    null = false
+    type = varchar(255)
+  }
+}
+schema "script_table_engine" {
+  charset = "utf8mb4"
+  collate = "utf8mb4_0900_ai_ci"
+}
+-- 2.hcl --
+schema "script_table_engine" {
+    charset = "$charset"
+    collate = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    engine = MyISAM
+    column "name" {
+      null = false
+      type = varchar(255)
+    }
+    charset = "$charset"
+    collate = "$collate"
+}
+-- 2.inspect.hcl --
+table "users" {
+  schema = schema.script_table_engine
+  engine = MyISAM
+  column "name" {
+    null = false
+    type = varchar(255)
+  }
+}
+schema "script_table_engine" {
+  charset = "utf8mb4"
+  collate = "utf8mb4_0900_ai_ci"
+}
+-- 3.hcl --
+schema "script_table_engine" {
+    charset = "$charset"
+    collate = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "name" {
+      null = false
+      type = varchar(255)
+    }
+    charset = "$charset"
+    collate = "$collate"
+}
+-- 3.inspect.hcl --
+table "users" {
+  schema = schema.script_table_engine
+  column "name" {
+    null = false
+    type = varchar(255)
+  }
+}
+schema "script_table_engine" {
+  charset = "utf8mb4"
+  collate = "utf8mb4_0900_ai_ci"
+}
\ No newline at end of file
diff --git a/sql/mysql/diff_test.go b/sql/mysql/diff_test.go
index f6b3714e..9e586bdf 100644
--- a/sql/mysql/diff_test.go
+++ b/sql/mysql/diff_test.go
@@ -48,6 +48,72 @@ func TestDiff_TableDiff(t *testing.T) {
 				},
 			},
 		},
+		// Attributes are specified and the same.
+		{
+			name: "no engine changes",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB}}},
+		},
+		// Attributes are specified and represent the same engine.
+		{
+			name: "no engine changes",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: "INNODB"}}},
+		},
+		// Attribute was dropped from the desired state, but the current state if the default (assumed one).
+		{
+			name: "no engine changes",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}},
+		},
+		// Attribute was dropped from the desired state, but the current state if the default (explicitly specific as default).
+		{
+			name: "no engine changes",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineMyISAM, Default: true}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}},
+		},
+		// The current state has no engine specified (unlikely case), and the desired state is set to the default (assumed one).
+		{
+			name: "no engine changes",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB, Default: true}}},
+		},
+		// Attributes are specified, but they do not represent the same engine.
+		{
+			name: "engine changed",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineInnoDB}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineMyISAM}}},
+			wantChanges: []schema.Change{
+				&schema.ModifyAttr{
+					From: &Engine{V: EngineInnoDB},
+					To:   &Engine{V: EngineMyISAM},
+				},
+			},
+		},
+		// The desired state has no engine specified, and the current state is not the default.
+		{
+			name: "engine changed",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineMyISAM}}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}},
+			wantChanges: []schema.Change{
+				&schema.ModifyAttr{
+					From: &Engine{V: EngineMyISAM},
+					To:   &Engine{V: EngineInnoDB, Default: true}, // Assume InnoDB is the default.
+				},
+			},
+		},
+		// The current state has no engine specified (unlikely case), and the desired state is not the default.
+		{
+			name: "engine changed",
+			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}},
+			to:   &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&Engine{V: EngineMyISAM}}},
+			wantChanges: []schema.Change{
+				&schema.ModifyAttr{
+					From: &Engine{V: EngineInnoDB, Default: true}, // Assume InnoDB is the default.
+					To:   &Engine{V: EngineMyISAM},
+				},
+			},
+		},
 		{
 			name: "add collation",
 			from: &schema.Table{Name: "users", Schema: &schema.Schema{Name: "public"}, Attrs: []schema.Attr{&schema.Charset{V: "latin1"}}},