Repository: ariga/atlas
Pull request #2644 (2024-03-26 09:39:53)

doc/md: add redshift
Updated for Redshiftdata Urls:
<img width="928" alt="Screenshot 2024-05-01 at 12 25 44" src="https://github.com/ariga/atlas/assets/16095902/b917dcf3-c70f-436e-950f-2a91943aea90">

Updated for guide

https://github.com/ariga/atlas/assets/16095902/379a4bf1-7378-43cd-ac75-03f6a6cc5944





Patch:
diff --git a/README.md b/README.md
index 2a44736a..ac28d9ff 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 ## Atlas: manage your database schema as code
 
-[![Twitter](https://img.shields.io/twitter/url.svg?label=Follow%20%40ariga%2Fatlas&style=social&url=https%3A%2F%2Ftwitter.com%2Fariga_io)](https://twitter.com/ariga_io)
+[![Twitter](https://img.shields.io/twitter/url.svg?label=Follow%20%40ariga%2Fatlas&style=social&url=https%3A%2F%2Ftwitter.com%2Fatlasgo_io)](https://twitter.com/atlasgo_io)
 [![Discord](https://img.shields.io/discord/930720389120794674?label=discord&logo=discord&style=flat-square&logoColor=white)](https://discord.com/invite/zZ6sWVg6NT)
 
 <p>
@@ -297,7 +297,7 @@ Atlas offers additional commands to assist users managing their database migrati
 `migrate status`, and more. For more information, see the versioned migration documentation at https://atlasgo.io/versioned/diff.
 
 ### Supported databases
-MySQL, MariaDB, PostgresSQL, SQLite, TiDB, CockroachDB, SQL Server, ClickHouse.
+MySQL, MariaDB, PostgresSQL, SQLite, TiDB, CockroachDB, SQL Server, ClickHouse, Redshift.
 
 [HCL]: https://atlasgo.io/atlas-schema/hcl
 [SQL]: https://atlasgo.io/atlas-schema/sql
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index 4c36d2f5..ac6a3dda 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -132,7 +132,7 @@ If run with the "--dry-run" flag, atlas will not execute any SQL.`,
 					}()
 					return cmdEnvsRun(envs, setMigrateEnvFlags, cmd, func(env *Env) error {
 						// Report deployments only if one of the migration directories is a cloud directory.
-						if u, err := url.Parse(flags.dirURL); err == nil && u.Scheme != cmdmigrate.DirTypeFile {
+						if u, err := url.Parse(flags.dirURL); err == nil && u.Scheme == cmdmigrate.DirTypeAtlas {
 							hasRemote = true
 						}
 						return migrateApplyRun(cmd, args, flags, env, set.ReportFor(flags, env))
diff --git a/cmd/atlas/main.go b/cmd/atlas/main.go
index bb6ed0fb..d69f6594 100644
--- a/cmd/atlas/main.go
+++ b/cmd/atlas/main.go
@@ -52,7 +52,7 @@ func main() {
 	update := checkForUpdate(ctx)
 	err := cmdapi.Root.ExecuteContext(ctx)
 	if u := update(); u != "" {
-		fmt.Println(u)
+		fmt.Fprintln(os.Stderr, u)
 	}
 	done(err)
 	if err != nil {
diff --git a/doc/md/atlas-schema/hcl-types.md b/doc/md/atlas-schema/hcl-types.md
index 1b9ea133..9f4f62e3 100644
--- a/doc/md/atlas-schema/hcl-types.md
+++ b/doc/md/atlas-schema/hcl-types.md
@@ -1655,4 +1655,220 @@ table "t" {
 ```
 
 :::info 
-The `AggregateFunction` and `SimpleAggregateFunction` are complex data types. Therefore, we recommend using a [Dev Database](../../md/concepts//dev.mdx) to normalize these types.
\ No newline at end of file
+The `AggregateFunction` and `SimpleAggregateFunction` are complex data types. Therefore, we recommend using a [Dev Database](../../md/concepts//dev.mdx) to normalize these types.
+:::
+
+## Redshift
+
+### Boolean
+The `boolean` and `bool` types allow creating standard SQL boolean columns.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = boolean
+  }
+  column "c2" {
+    # Alias to boolean.
+    type = bool
+  }
+}
+```
+
+### Binary
+The `binary_varying`, `varbinary` and `varbyte` types allow creating binary string columns.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = binary_varying(255)
+  }
+  column "c2" {
+    # Alias to binary_varying
+    type = varbinary(255)
+  }
+  column "c3" {
+    # Alias to binary_varying
+    type = varbyte(255)
+  }
+}
+```
+
+### Date, Time and Interval
+Atlas supports the standard Redshift types for creating date, time and interval columns.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = date
+  }
+  column "c2" {
+    # Equals to "time without time zone".
+    type = time
+  }
+  column "c3" {
+    # Equals to "time with time zone".
+    type = timetz
+  }
+  column "c4" {
+    # Equals "timestamp without time zone".
+    type = timestamp
+  }
+  column "c5" {
+    # Equals "timestamp with time zone".
+    type = timestamptz
+  }
+  column "c6" {
+    type = sql("interval year to month")
+  }
+}
+```
+
+### Fixed Point (Decimal)
+The `decimal` and `numeric` types are supported for storing exact numeric values. Note that in Redshift the two types are identical.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    # Equals to numeric.
+    type = decimal
+  }
+  column "c2" {
+    # Equals to numeric(5).
+    type = decimal(5)
+  }
+  column "c3" {
+    # Equals to numeric(5,2).
+    type = decimal(5,2)
+  }
+}
+```
+
+### Floating Point (Float)
+The `real` and `double_precision` types are supported for storing approximate numeric values.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = real
+  }
+  column "c2" {
+    type = double_precision
+  }
+  column "c3" {
+    type = float(10)
+  }
+  column "c4" {
+    type = float(30)
+  }
+  column "c5" {
+    # Alias to real.
+    type = float4
+  }
+  column "c6" {
+    # Alias to double_precision.
+    type = float8
+  }
+}
+```
+
+### Integer
+The `smallint`, `integer` / `int`, `bigint` types allow creating integer types.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = smallint
+  }
+  column "c2" {
+    type = integer
+  }
+  column "c3" {
+    type = int
+  }
+  column "c4" {
+    type = bigint
+  }
+  column "c5" {
+    # Alias to smallint.
+    type = int2
+  }
+  column "c6" {
+    # Alias to integer.
+    type = int4
+  }
+  column "c7" {
+    # Alias to bigint.
+    type = int8
+  }
+}
+```
+
+### String
+The `varchar`, `nvarchar`, `char`, `nchar`, `bpchar`, `character_varying`, `character` and `text` types allow creating string columns.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    # Equals character_varying(256).
+    type = varchar
+  }
+  column "c2" {
+    # Alias to character_varying(255).
+    type = varchar(255)
+  }
+  column "c3" {
+    # Equals to character_varying(255).
+    type = nvarchar(255)
+  }
+  column "c4" {
+    # Equals to char(1).
+    type = char
+  }
+  column "c5" {
+    # Equals to char(5).
+    type = nchar(5)
+  }
+  column "c6" {
+    # Alias to character(5).
+    type = char(5)
+  }
+  column "c7" {
+    # Alias to character(5).
+    type = bpchar(5)
+  }
+  column "c8" {
+    # Equals to character_varying(256).
+    type = text
+  }
+}
+```
+
+### Other Types
+
+The `hllsketch`, `super`, `geometry` and `geography` types are supported by Atlas.
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "c1" {
+    type = hllsketch
+  }
+  column "c2" {
+    type = super
+  }
+  column "c3" {
+    type = geometry
+  }
+  column "c4" {
+    type = geography
+  }
+}
+```
\ No newline at end of file
diff --git a/doc/md/atlas-schema/hcl.mdx b/doc/md/atlas-schema/hcl.mdx
index bf5ecafb..786f0b1b 100644
--- a/doc/md/atlas-schema/hcl.mdx
+++ b/doc/md/atlas-schema/hcl.mdx
@@ -87,6 +87,17 @@ schema "atomic" {
 }
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```hcl
+schema "public" {
+  comment = "A schema comment"
+}
+
+schema "private" {}
+```
+
 </TabItem>
 </Tabs>
 
@@ -230,6 +241,63 @@ table "b" "users" {
 }
 ```
 
+### Distribution
+
+The `distribution` attribute allows for specifying the distribution method of the table.
+
+:::note
+Distribution is currently supported only by the Redshift driver. Support for the remaining drivers will be added in future versions.
+:::
+
+<Tabs>
+<TabItem label="Redshift" value="redshift">
+
+```hcl
+table "users" {
+  schema = schema.public
+  // highlight-next-line
+  column "id" {
+    type = int
+  }
+  distribution {
+    style = KEY // EVEN | ALL | AUTO
+    key = column.id // only for KEY style
+  }
+}
+```
+
+</TabItem>
+</Tabs>
+
+### Sorting
+
+The `sort` attribute allows for specifying the sorting method of the table.
+
+:::note
+Sorting is currently supported only by the Redshift driver. Support for the remaining drivers will be added in future versions.
+:::
+
+<Tabs>
+<TabItem label="Redshift" value="redshift">
+
+```hcl
+table "users" {
+  schema = schema.public
+  // highlight-next-line
+  column "id" {
+    type = int
+  }
+  sort {
+    style = COMPOUND // INTERLEAVED | COMPOUND | AUTO
+    columns = [column.id]
+  }
+}
+
+```
+
+</TabItem>
+</Tabs>
+
 ## View
 
 A `view` is a virtual table in the database, defined by a statement that queries rows from one or more existing
@@ -356,6 +424,28 @@ Note that modifying the materialized view structure
 after the initial creation is not supported by Atlas currently.
 :::
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```hcl
+materialized "mat_view" {
+  schema = schema.public
+  column "c1" {
+    null = true
+    type = smallint
+  }
+  as      = "SELECT * FROM t1;"
+  comment = "example materialized view"
+  distribution {
+    style = EVEN
+  }
+  sort {
+    style = AUTO
+  }
+  depends_on = [table.t1]
+}
+```
+
 </TabItem>
 </Tabs>
 
@@ -495,6 +585,25 @@ table "users" {
 Note, it is recommended to use the [`--dev-url`](../concepts/dev-database) option when generated columns are used.
 :::
 
+### Encodings
+
+Encodings are used to define the compression algorithm for the column data. Supported by ClickHouse and Redshift.
+
+<Tabs>
+<TabItem label="Redshift" value="redshift">
+
+```hcl
+table "users" {
+  schema = schema.public
+  column "name" {
+    type = text
+    encode = LZ4 // AZ64 | RAW | LZ4 | ZSTD
+  }
+}
+```
+</TabItem>
+</Tabs>
+
 ## Column Types
 
 The SQL dialects supported by Atlas (Postgres, MySQL, MariaDB, and SQLite) vary in
@@ -1008,7 +1117,8 @@ atlas login
 ```
 :::
 
-The `function` block allows defining SQL functions in HCL format.
+The `function` block allows defining functions in HCL format. The `lang` attribute specifies the language of the
+function. For example, `PLpgSQL`, `SQL`, `CRL`, etc.
 
 <Tabs>
 <TabItem label="PostgreSQL" value="postgres">
@@ -1049,6 +1159,7 @@ function "sql_body2" {
   volatility = IMMUTABLE // STABLE | VOLATILE
   leakproof  = true      // NOT LEAKPROOF | LEAKPROOF
   strict     = true      // (CALLED | RETURNS NULL) ON NULL INPUT
+  security   = INVOKER   // DEFINER | INVOKER
 }
 ```
 
@@ -1066,8 +1177,9 @@ function "add2" {
   }
   return        = int
   as            = "return a + b"
-  deterministic = true   // NOT DETERMINISTIC | DETERMINISTIC
-  data_access   = NO_SQL // CONTAINS_SQL | NO_SQL | READS_SQL_DATA | MODIFIES_SQL_DATA
+  deterministic = true     // NOT DETERMINISTIC | DETERMINISTIC
+  data_access   = NO_SQL   // CONTAINS_SQL | NO_SQL | READS_SQL_DATA | MODIFIES_SQL_DATA
+  security      = INVOKER  // DEFINER | INVOKER
 }
 
 function "f1" {
@@ -1197,6 +1309,39 @@ function "fn_return_table" {
 </TabItem>
 </Tabs>
 
+#### Aggregate Functions
+
+The `aggregate` block defines a function that computes a single result from a set of values. Supported by
+[PostgreSQL](https://www.postgresql.org/docs/current/sql-createaggregate.html).
+
+```hcl {1-8}
+aggregate "sum_of_squares" {
+  schema = schema.public
+  arg {
+    type = double_precision
+  }
+  state_type = double_precision
+  state_func = function.sum_squares_sfunc
+}
+
+function "sum_squares_sfunc" {
+  schema = schema.public
+  lang   = PLpgSQL
+  arg "state" {
+    type = double_precision
+  }
+  arg "value" {
+    type = double_precision
+  }
+  return = double_precision
+  as     = <<-SQL
+  BEGIN
+      RETURN state + value * value;
+  END;
+  SQL
+}
+```
+
 ## Procedure
 
 :::info LOGIN REQUIRED
diff --git a/doc/md/atlas-schema/projects.mdx b/doc/md/atlas-schema/projects.mdx
index e58edd8e..d73278bc 100644
--- a/doc/md/atlas-schema/projects.mdx
+++ b/doc/md/atlas-schema/projects.mdx
@@ -158,6 +158,30 @@ env "dev" {
 }
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+// Define an environment named "local"
+
+```hcl
+env "local" {
+  // Declare where the schema definition resides.
+  // Also supported: ["file://multi.hcl", "file://schema.hcl"].
+  src = "file://project/schema.hcl"
+
+  // Define the URL of the database which is managed
+  // in this environment.
+  url = "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable"
+
+  // Define the URL of the Dev Database for this environment
+  // See: https://atlasgo.io/concepts/dev-database
+  dev = "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+}
+
+env "dev" {
+  // ... a different env
+}
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/atlas-schema/sql.mdx b/doc/md/atlas-schema/sql.mdx
index ef7cc015..fa929d94 100644
--- a/doc/md/atlas-schema/sql.mdx
+++ b/doc/md/atlas-schema/sql.mdx
@@ -87,6 +87,19 @@ few examples of how to use the docker driver:
 --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+
+```shell
+# When working on a single database schema, use the auth-created
+# "public" schema as the search path.
+--dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public"
+
+# When working on multiple database schemas.
+--dev-url "redshift://user:pass@redshift-cluster:5439/dev"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/components/card.tsx b/doc/md/components/card.tsx
index e4e75199..813c47d0 100644
--- a/doc/md/components/card.tsx
+++ b/doc/md/components/card.tsx
@@ -8,6 +8,7 @@ export enum CardImage {
     Action = "action",
     CI = "ci",
     ClickHouse = "clickhouse",
+    Redshift = "redshift",
     Cloud = "cloud",
     Config = "config",
     DataSource = "datasource",
diff --git a/doc/md/components/migrate-push-command.mdx b/doc/md/components/migrate-push-command.mdx
index cebd3b6f..cba52829 100644
--- a/doc/md/components/migrate-push-command.mdx
+++ b/doc/md/components/migrate-push-command.mdx
@@ -49,5 +49,13 @@ atlas migrate push app \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate push app \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev"
+```
+
 </TabItem>
 </Tabs>
\ No newline at end of file
diff --git a/doc/md/concepts/dev.mdx b/doc/md/concepts/dev.mdx
index 4b035a1d..0f5993cb 100644
--- a/doc/md/concepts/dev.mdx
+++ b/doc/md/concepts/dev.mdx
@@ -161,6 +161,21 @@ docker+clickhouse://user/image:tag
 docker+clickhouse://_/clickhousedb:latest
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+Redshift is a managed service and does not support running in a Docker container. To user dev-database with Redshift,
+we recommend using a real Redshift cluster with separate database.
+
+```shell
+# When working on a single database schema, use the auth-created
+# "public" schema as the search path.
+--dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public"
+
+# When working on multiple database schemas.
+--dev-url "redshift://user:pass@redshift-cluster:5439/dev"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/concepts/url.mdx b/doc/md/concepts/url.mdx
index 4c0d6f33..d4a966d3 100644
--- a/doc/md/concepts/url.mdx
+++ b/doc/md/concepts/url.mdx
@@ -25,6 +25,7 @@ values={[
 {label: 'SQLServer', value: 'sqlserver'},
 {label: 'SQLite', value: 'sqlite'},
 {label: 'ClickHouse', value: 'clickhouse'},
+{label: 'Redshift', value: 'redshift'},
 {label: 'Docker', value: 'docker'},
 ]}>
 <TabItem value="mysql">
@@ -157,6 +158,62 @@ we need to use native protocol port [`9440`](https://clickhouse.com/docs/en/guid
 clickhouse://user:pass@CLICKHOUSE-CLOUD-HOST:9440/schema?secure=true
 ```
 
+</TabItem>
+<TabItem value="redshift">
+
+Connecting to a specific Redshift cluster with a schema named `public`:
+```shell
+redshift://user:pass@redshift-cluster:5439/database?search_path=public
+```
+
+Connecting to a specific Redshift cluster with a schema named `public` with SSL disabled:
+```shell
+redshift://user:pass@redshift-cluster:5439/database?search_path=public&sslmode=disable
+```
+
+If you want to connect Redshift though Data API you can use the following URL:
+:::info 
+AWS credentials are required to connect to Redshift via Data API. In this protocol,
+atlas doesn't support changing the schema on URL, the schema is based on default schema of the user.
+If you want to bind the connection to a specific schema, you can use the following SQL command:
+
+```
+ALTER USER [username] SET search_path = [schema];
+```
+:::
+
+Connecting to Serverless via IAM Identity:
+```shell
+redshift+http://workgroup([workgroup-name])/[database]
+```
+
+Connecting to Serverless via Secret ARN:
+```shell
+redshift+http://[arn]@workgroup([workgroup-name])/[database]
+```
+
+Connecting to provisioned Redshift cluster via IAM Identity:
+```shell
+redshift+http://cluster([cluster-name])/[database]
+```
+
+Connecting to provisioned Redshift cluster with database username
+```shell
+redshift+http://[dbuser]@cluster([cluster-name])/[database]
+```
+
+Connecting to provided Redshift cluster via Secret ARN:
+```shell
+redshift+http://[arn]@cluster([cluster-name])/[database]
+```
+
+:::note
+- The default `mode` is `schema`.
+- To change the connection to realm mode, use `mode=database`.
+- Use `timeout=5m` to set the timeout for the http client. Default is 5 minutes.
+- Use `polling=50ms` to set the polling interval when fetching the query results. Default is 50ms.
+:::
+
 </TabItem>
 <TabItem value="docker">
 
diff --git a/doc/md/declarative/apply.mdx b/doc/md/declarative/apply.mdx
index db9d0f0b..1e823114 100644
--- a/doc/md/declarative/apply.mdx
+++ b/doc/md/declarative/apply.mdx
@@ -184,6 +184,16 @@ atlas schema apply \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema apply \
+  --url "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable" \
+  --to "file://schema.hcl" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -252,6 +262,16 @@ atlas schema apply \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema apply \
+  --url "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable" \
+  --to "file://schema.sql" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -328,6 +348,16 @@ atlas schema apply \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema apply \
+  --url "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable" \
+  --to "file://migrations" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/declarative/diff.mdx b/doc/md/declarative/diff.mdx
index 7eb23775..0909a2e9 100644
--- a/doc/md/declarative/diff.mdx
+++ b/doc/md/declarative/diff.mdx
@@ -151,6 +151,15 @@ atlas schema diff \
   --to "clickhouse://user:pass@remote:9000"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "redshift://user:pass@redshift-cluster:5439/database?search_path=public&sslmode=disable" \
+  --to "redshift://user:pass@redshift-cluster-2:5439/database?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -214,6 +223,18 @@ atlas schema diff \
   --to "clickhouse://user:pass@remote:9000/example"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+Compare two Redshift clusters with a schema named `public` under the
+`example` database:
+
+```shell
+atlas schema diff \
+  --from "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable" \
+  --to "redshift://user:pass@redshift-cluster-2:5439/example?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -279,6 +300,16 @@ atlas schema diff \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "file://schema1.hcl" \
+  --to "file://schema2.hcl" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -364,6 +395,16 @@ atlas schema diff \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "file://schema1.sql" \
+  --to "file://schema2.sql" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -450,6 +491,16 @@ atlas schema diff \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "file://migrations1" \
+  --to "file://migrations2" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -515,6 +566,16 @@ atlas schema diff \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "file://schema.hcl" \
+  --to "file://schema.sql" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -581,6 +642,16 @@ atlas schema diff \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema diff \
+  --from "file://migrations" \
+  --to "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/declarative/inspect.mdx b/doc/md/declarative/inspect.mdx
index c246b8d8..a204c521 100644
--- a/doc/md/declarative/inspect.mdx
+++ b/doc/md/declarative/inspect.mdx
@@ -102,6 +102,15 @@ atlas schema inspect -u "clickhouse://localhost:9000"
 atlas schema inspect -u "clickhouse://user:pass@0.0.0.0:9000?secure=true"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database"
+
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database?sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -154,6 +163,15 @@ atlas schema inspect -u "clickhouse://localhost:9000/database"
 atlas schema inspect -u "clickhouse://user:pass@0.0.0.0:9000/database?secure=true"
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database?search_path=public"
+
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -206,6 +224,15 @@ atlas schema inspect -u "clickhouse://localhost:9000/database" --schema schema1
 atlas schema inspect -u "clickhouse://user:pass@0.0.0.0:9000/database?secure=true" -s schema1,schema2
 ```
 
+</TabItem>
+<TabItem label="Redshift" value="redshift">
+
+```shell
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database" --schema schema1 --schema schema2
+
+atlas schema inspect -u "redshift://user:pass@redshift-cluster:5439/database?search_path=public&sslmode=disable" -s schema1,schema2
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/guides/guides.mdx b/doc/md/guides/guides.mdx
index 8fb7e527..34f08c29 100644
--- a/doc/md/guides/guides.mdx
+++ b/doc/md/guides/guides.mdx
@@ -207,6 +207,17 @@ import { Card, CardImage } from "../components/card";
   />
 </div>
 
+## Redshift
+
+<div className="row">
+  <Card
+    name="Automatic Migrations for Redshift"
+    description="This guide is for getting started with Redshift using Atlas"
+    url="/guides/redshift"
+    image={CardImage.Redshift}
+  />
+</div>
+
 ## Migration Directories
 
 <div className="row">
diff --git a/doc/md/guides/redshift.mdx b/doc/md/guides/redshift.mdx
new file mode 100644
index 00000000..bb266cab
--- /dev/null
+++ b/doc/md/guides/redshift.mdx
@@ -0,0 +1,745 @@
+---
+id: getting-started-redshift
+title: Automatic Migrations for Redshift with Atlas
+slug: /guides/redshift
+tags: [redshift]
+---
+
+import InstallationInstructions from '../components/_installation_instructions.mdx';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+[Amazon Redshift](https://aws.amazon.com/redshift/) is a powerful, managed data warehouse solution from AWS.
+
+One of the top challenges developers face when working with large data sets on Redshift is managing the database schema.
+As your organization grows, data from more and more applications end up being represented in the data warehouse. This makes
+schemas complex and highly interconnected, and managing them becomes a daunting task.
+
+#### Enter: Atlas
+
+Atlas helps developers manage their database schema as code - abstracting away the intricacies of database schema
+management. With Atlas, users provide the desired state of the database schema and Atlas automatically plans the
+required migrations.
+
+In this guide, we will dive into setting up Atlas for Redshift, and introduce the different workflows available.
+
+## Prerequisites
+
+1. An AWS account with the necessary permissions to create a Redshift cluster.
+2. The [AWS CLI](https://aws.amazon.com/cli/) installed on your machine.
+3. Atlas installed on your machine:
+<InstallationInstructions />
+
+## Logging in to Atlas
+
+To use Redshift with Atlas, you'll need to [log in to Atlas](https://auth.atlasgo.cloud/signup). If it's your first time,
+you will be prompted to create both an account and a workspace (organization):
+
+<Tabs>
+<TabItem value="web" label="Via Web">
+
+```shell
+atlas login
+```
+
+</TabItem>
+<TabItem value="token" label="Via Token">
+
+```shell
+atlas login --token "ATLAS_TOKEN"
+```
+
+</TabItem>
+<TabItem value="env-var" label="Via Environment Variable">
+
+```shell
+ATLAS_TOKEN="ATLAS_TOKEN" atlas login
+```
+
+</TabItem>
+</Tabs>
+
+## Creating the Cluster
+
+:::info
+
+If you want to use an existing Redshift cluster, you can skip this step.
+
+:::
+
+Let's start off by spinning up a new Redshift cluster using AWS CLI:
+
+```bash
+aws redshift create-cluster --cluster-identifier "atlas-demo" \
+  --master-username "root" --master-user-password "Password123" \
+  --number-of-nodes 2 --node-type dc2.large
+```
+
+The `master-username` and `master-user-password` are the credentials for the master user of the Redshift cluster.
+Make sure to replace them with your own values and make a note of them for future use.
+
+For this example we will begin with a minimal database with a users table and an id as distributed key.
+
+```sql
+CREATE TABLE "users" (
+    "id" int NOT NULL
+    "name" varchar(255)
+) DISTKEY("id") SORTKEY("id");
+```
+
+To create the table, run the following command:
+
+```bash
+aws redshift-data execute-statement --cluster-identifier "atlas-demo" --database "dev" \
+  --sql "CREATE TABLE users (id int NOT NULL, name varchar(255)) DISTKEY(id) SORTKEY("id");"
+```
+
+Redshift instances can take a few minutes to spin up. Make sure to wait for the instance to be in the `available` state before proceeding.
+
+To check the status of the cluster, run the following command:
+
+```bash
+aws redshift describe-clusters --cluster-identifier "atlas-demo" \
+  --query "Clusters[0].ClusterStatus" --output text --no-cli-pager
+```
+
+## Find out the Connection URL
+
+To use Atlas with Redshift, we need to provide a connection [URL](/concepts/url) to the database. In this section, we will see the
+different ways you can use Atlas to connect to your Redshift instance.
+
+Atlas provides two ways to connect to a Redshift instance:
+
+* **Direct Network Connection** to the Redshift instance.  This method provides a faster connection to the database,
+  but requires you to have direct network access to the database. Typically, this means that either the database is
+  exposed to the internet or you are connected to the database VPC via some sort of VPN connection.
+* **Using the Redshift Data API** - This method uses the [Redshift Data API](https://docs.aws.amazon.com/redshift/latest/mgmt/data-api.html)
+  to communicate with the database. This method is slower than the direct network connection, but can be used anywhere
+  you have access to the internet and an IAM role that can access the Redshift Data API.
+
+### Direct Network Connection
+
+To connect to the Redshift instance using a direct network connection, you need to provide the connection URL in the
+following format:
+
+```
+redshift://<user>:<pass>@<address>:<port>/<database>?search_path=<schema>
+```
+
+To find out the endpoint of your Redshift Cluster, run the following command:
+
+```bash
+aws redshift describe-clusters --cluster-identifier "atlas-demo" --query "Clusters[0].Endpoint" --output json --no-cli-pager
+````
+
+Observe the output and note down the `Address` and `Port` values. The connection URL will look like this:
+
+```json
+{
+    "Address": "atlas-demo.cjxjxjxjxjxj.us-west-2.redshift.amazonaws.com",
+    "Port": 5439
+}
+```
+
+The connection URL will look something like this:
+
+```
+redshift://root:Password123@atlas-demo.cjxjxjxjxjxj.us-west-2.redshift.amazonaws.com:5439/dev
+```
+
+Be sure to replace `root`, `Password123`, `atlas-demo.cjxjxjxjxj.us-west-2.redshift.amazonaws.com`, `5439`, and `dev`
+with your own values.
+
+### Using the Redshift Data API
+
+To connect to the Redshift instance using the Redshift Data API, you need to provide the connection URL in the
+following format:
+
+```
+redshift+http://cluster([cluster-name])/[database]
+```
+
+If you used `atlas-demo` as the name of your cluster from the previous step, the connection URL will look like this:
+
+```
+redshift+http://cluster(atlas-demo)/dev
+```
+
+Be sure to replace `atlas-demo` and `dev` with your own values.
+
+### Advanced Connection Options
+
+To learn about more advanced URL options such as using SSL, setting the search path, or using a different schema, connecting
+to Serverless Redshift, and more, refer to our [URL documentation](/concepts/url).
+
+## Inspecting the Schema
+
+With your Redshift instance URL determined, we are now ready to start interacting with the database. In this sections and
+all that follow, replace the example URL with the one you determined in the previous section.
+
+The `atlas schema inspect` command supports reading the database description provided by a [URL](/concepts/url) and outputting it in
+different formats, including [Atlas DDL](/atlas-schema/hcl.mdx) (default), SQL, and JSON. In this guide, we will
+demonstrate the flow using both the Atlas DDL and SQL formats, as the JSON format is often used for processing the
+output using `jq`.
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+To inspect our Redshift instance, use the `-u` flag and write the output to a file named `schema.hcl`:
+
+```shell
+atlas schema inspect -u "redshift+http://cluster(atlas-demo)/dev?mode=database" > schema.hcl
+```
+
+Open the `schema.hcl` file to view the Atlas schema that describes our database.
+
+```hcl title="schema.hcl"
+schema "public" {
+  comment = "Standard public schema"
+}
+table "users" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "name" {
+    null   = true
+    type   = character_varying(255)
+    encode = LZO
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id]
+  }
+}
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+To inspect our locally-running SQL Server instance, use the `-u` flag and write the output to a file named `schema.sql`:
+
+```shell
+atlas schema inspect -u "redshift+http://cluster(atlas-demo)/dev?mode=database" --format '{{ sql . }}' > schema.sql
+```
+
+Open the `schema.sql` file to view the inspected SQL schema that describes our database.
+
+```sql title="schema.sql"
+-- Add new schema named "public"
+CREATE SCHEMA IF NOT EXISTS "public";
+-- Set comment to schema: "public"
+COMMENT ON SCHEMA "public" IS 'Standard public schema';
+-- Create "users" table
+CREATE TABLE "public"."users" (
+  "id" integer NOT NULL ENCODE RAW,
+  "name" character varying(255) NULL ENCODE LZO
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id");
+```
+
+</TabItem>
+</Tabs>
+
+:::info
+For in-depth details on the `atlas schema inspect` command, covering aspects like inspecting specific schemas,
+handling multiple schemas concurrently, excluding tables, and more, refer to our documentation
+[here](/declarative/inspect).
+:::
+
+To generate an Entity Relationship Diagram (ERD), or a visual representation of our schema, we can add the -w flag to the inspect command:
+
+```shell
+atlas schema inspect -u "redshift+http://cluster(atlas-demo)/dev?mode=database" -w
+```
+
+![redshift-inspect](https://atlasgo.io/uploads/redshift/images/schema-inspect-hcl.png)
+
+## Declarative Migrations
+
+The declarative approach lets users manage schemas by defining the desired state of the database as code.
+Atlas then inspects the target database and calculates an execution plan to reconcile the difference between the desired and actual states.  Let's see this in action.
+
+We will start off by making a change to our schema file, such as adding a `repos` table:
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+```hcl title=schema.hcl
+schema "public" {
+  comment = "Standard public schema"
+}
+table "users" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "name" {
+    null   = true
+    type   = character_varying(255)
+    encode = LZO
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id]
+  }
+}
+
+// highlight-start
+table "repos" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "name" {
+    null   = false
+    type   = varchar(255)
+  }
+  column "owner_id" {
+    null = false
+    type = integer
+    encode = RAW
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id, column.owner_id]
+  }
+}
+// highlight-end
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+```sql title="schema.sql"
+-- Add new schema named "public"
+CREATE SCHEMA IF NOT EXISTS "public";
+-- Set comment to schema: "public"
+COMMENT ON SCHEMA "public" IS 'Standard public schema';
+-- Create "users" table
+CREATE TABLE "public"."users" (
+  "id" integer NOT NULL ENCODE RAW,
+  "name" character varying(255) NULL ENCODE LZO
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id");
+
+// highlight-start
+-- Create "repos" table
+CREATE TABLE "public"."repos" (
+  "id" integer NOT NULL ENCODE RAW,
+  "name" character varying(255) NULL ENCODE LZ,
+  "owner_id" integer NOT NULL ENCODE RAW
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id", "owner_id");
+// highlight-end
+```
+
+</TabItem>
+</Tabs>
+
+Now that our _desired state_ has changed, to apply these changes to our database, Atlas will plan a migration for us
+by running the `atlas schema apply` command:
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+```shell
+atlas schema apply \
+  -u "redshift+http://cluster(atlas-demo)/dev?mode=database" \
+  --to file://schema.hcl
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+Redshift does not support docker images, so we need another remote database to apply the changes. 
+Let's create a new database in the same instance to use as dev database:
+
+```shell
+aws redshift-data execute-statement --cluster-identifier "atlas-demo" --database "dev" --db-user root --sql "CREATE DATABASE devdb;"
+```
+
+Then we can run the `atlas schema apply` command:
+
+```shell
+atlas schema apply \
+  -u "redshift+http://cluster(atlas-demo)/dev?mode=database" \
+  --to file://schema.sql \
+--dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+</TabItem>
+</Tabs>
+
+Apply the changes, and that's it! You have successfully run a declarative migration.
+
+:::info
+For a more detailed description of the `atlas schema apply` command refer to our documentation
+[here](/declarative/apply).
+:::
+
+To ensure that the changes have been made to the schema, let's run the `inspect` command with the `-w` flag once more
+and view the ERD:
+
+![atlas-schema](https://atlasgo.io/uploads/redshift/images/schema-inspect-repos.png)
+
+## Versioned Migrations
+
+Alternatively, the versioned migration workflow, sometimes called "change-based migrations", allows each change to the
+database schema to be checked-in to source control and reviewed during code-review. Users can still benefit from Atlas
+intelligently planning migrations for them, however they are not automatically applied.
+
+### Creating the first migration
+
+In the versioned migration workflow, our database state is managed by a _migration directory_. The migration directory
+holds all of the migration files created by Atlas, and the sum of all files in lexicographical order represents the current
+state of the database.
+
+To create our first migration file, we will run the `atlas migrate diff` command, and we will provide the necessary parameters:
+
+* `--dir` the URL to the migration directory, by default it is file://migrations.
+* `--to` the URL of the desired state. A state can be specified using a database URL, HCL or SQL schema, or another migration directory.
+* `--dev-url` a URL to a [Dev Database](/concepts/dev-database) that will be used to compute the diff.
+
+Redshift does not support docker images, so we need another remote database to apply the changes. 
+Let's create a new database in the same instance to use as dev database:
+
+```shell
+aws redshift-data execute-statement --cluster-identifier "atlas-demo" --database "dev" --db-user root --sql "CREATE DATABASE devdb;"
+```
+
+After creating the dev database, we can now run the `atlas migrate diff` command:
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+```shell
+atlas migrate diff initial \
+  --to file://schema.hcl \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+```shell
+atlas migrate diff initial \
+  --to file://schema.sql \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+</TabItem>
+</Tabs>
+
+Run `ls migrations`, and you'll notice that Atlas has automatically created a migration directory for us, as well as
+two files:
+
+<Tabs>
+<TabItem value="file" label="20240208092238_initial.sql" default>
+
+```sql
+-- Create "repos" table
+CREATE TABLE "public"."repos" (
+  "id" integer NOT NULL ENCODE RAW,
+  "name" character varying(255) NOT NULL ENCODE LZO,
+  "owner_id" integer NOT NULL ENCODE RAW
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id", "owner_id");
+
+-- Create "users" table
+CREATE TABLE "public"."users" (
+  "id" integer NOT NULL ENCODE RAW,
+  "name" character varying(255) NULL ENCODE LZO
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id");
+```
+
+</TabItem>
+<TabItem value="atlas.sum" label="atlas.sum">
+
+```shell
+h1:TDwxoEtPh0o8BdPOQIlk5e7zwIrSNEW5H5nuxoseZEI=
+20240521041234_initial.sql h1:YrlXxrZJOBS1GLtVxghGmNlBOTWEQvhi4F514urBkYk=
+```
+</TabItem>
+</Tabs>
+
+### Pushing migration directories to Atlas
+
+Now that we have our first migration, we can apply it to a database. There are multiple ways to accomplish this, with
+most methods covered in the [guides](/guides) section. In this example, we'll demonstrate how to push migrations to
+[Atlas Cloud](https://atlasgo.cloud), much like how Docker images are pushed to Docker Hub.
+
+<div style={{textAlign: 'center'}}>
+    <img src="https://atlasgo.io/uploads/redshift/images/first-dir-push.png" alt="redshift migrate push" width="100%"/>
+    <p style={{fontSize: 12}}>Migration Directory created with <code>atlas migrate push</code></p>
+</div>
+
+Let's name our new migration project `app` and run `atlas migrate push`:
+
+```shell
+atlas migrate push app \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+Once the migration directory is pushed, Atlas prints a URL to the created directory, similar to the once shown in the
+image above.
+
+### Applying migrations
+
+Once our `app` migration directory has been pushed, we can apply it to a database from any CD platform without
+necessarily having our directory there.
+
+Let's create a new database that represents our local environment:
+
+```shell
+aws redshift-data execute-statement --cluster-identifier "atlas-demo" --database "dev" --db-user root --sql "CREATE DATABASE local;"
+```
+
+Then, We'll create a simple Atlas configuration file (`atlas.hcl`) to store the settings for our local environment:
+
+```hcl title="atlas.hcl" {1}
+# The "dev" environment represents our local testings.
+env "local" {
+  url = "redshift+http://root@cluster(atlas-demo)/local?mode=database"
+  migration {
+    dir = "atlas://app"
+  }
+}
+```
+
+The final step is to apply the migrations to the database. Let's run `atlas migrate apply` with the `--env` flag
+to instruct Atlas to select the environment configuration from the `atlas.hcl` file:
+
+```shell
+atlas migrate apply --env local
+```
+
+Boom! After applying the migration, you should receive a link to the deployment and the database where the migration
+was applied. Here's an example of what it should look like:
+
+<div style={{textAlign: 'center'}}>
+    <img src="https://atlasgo.io/uploads/redshift/images/redshift-deployment.png" alt="first deployment" width="100%"/>
+    <p style={{fontSize: 12}}>Migration deployment reported created with <code>atlas migrate apply</code></p>
+</div>
+
+### Generating another migration
+
+After applying the first migration, it's time to update our schema defined in the schema file and tell Atlas to generate
+another migration. This will bring the migration directory (and the database) in line with the new state defined by the
+desired schema (schema file).
+
+Let's make two changes to our schema:
+
+* Add a new `description` column to our repos table
+* Add a new `commits` table
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+```hcl title="schema.hcl"
+schema "public" {
+  comment = "Standard public schema"
+}
+table "users" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "name" {
+    null   = true
+    type   = character_varying(255)
+    encode = LZO
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id]
+  }
+}
+table "repos" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "name" {
+    null   = false
+    type   = character_varying(255)
+  }
+  // highlight-start
+  column "description" {
+    null = true
+    type = character_varying(255)
+  }
+  // highlight-end
+  column "owner_id" {
+    null = false
+    type = integer
+    encode = RAW
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id, column.owner_id]
+  }
+}
+// highlight-start
+table "commits" {
+  schema = schema.public
+  column "id" {
+    null   = false
+    type   = integer
+    encode = RAW
+  }
+  column "message" {
+    null   = false
+    type   = character_varying(255)
+  }
+  column "repo_id" {
+    null = false
+    type = integer
+    encode = RAW
+  }
+  column "author_id" {
+    null = false
+    type = integer
+    encode = RAW
+  }
+  distribution {
+    style  = KEY
+    column = column.id
+  }
+  sort {
+    style   = COMPOUND
+    columns = [column.id, column.repo_id]
+  }
+}
+// highlight-end
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+```sql title = "schema.sql"
+-- Add new schema named "public"
+CREATE SCHEMA IF NOT EXISTS "public";
+
+-- Set comment to schema: "public"
+COMMENT ON SCHEMA "public" IS 'Standard public schema';
+
+-- Create "users" table
+CREATE TABLE "public"."users" (
+  "id" integer NOT NULL ENCODE RAW, 
+  "name" character varying(255) NULL ENCODE LZO
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id");
+
+-- Create "repos" table
+CREATE TABLE "public"."repos" (
+  "id" integer NOT NULL ENCODE RAW, 
+  "name" character varying(255) NOT NULL ENCODE LZO, 
+  // highlight-next-line
+  "description" character varying(255) NULL ENCODE LZO,
+  "owner_id" integer NOT NULL ENCODE RAW
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id", "owner_id");
+
+// highlight-start
+-- Create "commits" table
+CREATE TABLE "public"."commits" (
+  "id" integer NOT NULL ENCODE RAW, 
+  "message" character varying(255) NOT NULL ENCODE LZO, 
+  "repo_id" integer NOT NULL ENCODE RAW, 
+  "author_id" integer NOT NULL ENCODE RAW
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id", "repo_id");
+// highlight-end
+
+```
+</TabItem>
+</Tabs>
+
+Next, let's run the `atlas migrate diff` command once more:
+
+<Tabs>
+<TabItem value="hcl" label="Atlas DDL (HCL)" default>
+
+```shell
+atlas migrate diff add_commits \
+  --to file://schema.hcl \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+</TabItem>
+<TabItem value="sql" label="SQL">
+
+```shell
+atlas migrate diff add_commits \
+  --to file://schema.sql \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+</TabItem>
+</Tabs>
+
+Run `ls migrations`, and you'll notice that a new migration file has been generated.
+
+```sql title="20240130141055.sql"
+-- Modify "repos" table
+ALTER TABLE "public"."repos" ADD COLUMN "description" character varying(255) NULL ENCODE LZO;
+-- Create "commits" table
+CREATE TABLE "public"."commits" (
+  "id" integer NOT NULL ENCODE RAW,
+  "message" character varying(255) NOT NULL ENCODE LZO,
+  "repo_id" integer NOT NULL ENCODE RAW,
+  "author_id" integer NOT NULL ENCODE RAW
+) DISTSTYLE KEY DISTKEY ("id") SORTKEY ("id", "repo_id");
+```
+
+Let's run `atlas migrate push` again and
+observe the new file on the migration directory page.
+
+```shell
+atlas migrate push app \
+  --dev-url "redshift+http://root@cluster(atlas-demo)/devdb?mode=database"
+```
+
+<div style={{textAlign: 'center'}}>
+    <img src="https://atlasgo.io/uploads/redshift/images/second-push.png" alt="redshift migrate push" width="100%"/>
+    <p style={{fontSize: 12}}>Migration Directory created with <code>atlas migrate push</code></p>
+</div>
+
+## Next Steps
+
+In this guide we learned about the declarative and versioned workflows, and how to use Atlas to generate migrations,
+push them to an Atlas workspace and apply them to databases.
+
+Next steps:
+* Read the [full docs](/atlas-schema/hcl) to learn HCL schema syntax or about specific Redshift [column types](/atlas-schema/hcl-types#redshift)
+* Learn how to [set up CI](/cloud/setup-ci) for your migration directory
+* Deploy schema changes with [Terraform](/integrations/terraform-provider) or [Kubernetes](/integrations/kubernetes/operator)
+* Learn about [modern CI/CD principles](/guides/modern-database-ci-cd) for databases
+
+For more in-depth guides, check out the other pages in [this section](/guides) or visit our [Docs](/getting-started) section.
+
+Have questions? Feedback? Find our team on our [Discord server](https://discord.com/invite/zZ6sWVg6NT).
\ No newline at end of file
diff --git a/doc/md/versioned/apply.mdx b/doc/md/versioned/apply.mdx
index 40b5f2d7..23bf3dab 100644
--- a/doc/md/versioned/apply.mdx
+++ b/doc/md/versioned/apply.mdx
@@ -89,6 +89,14 @@ atlas migrate push app \
   --dev-url "docker://clickhouse/23.11"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate push app \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev"
+```
+
 </TabItem>
 </Tabs>
 
@@ -148,6 +156,15 @@ atlas migrate apply \
   --url "clickhouse://root:pass@:9000/default"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate apply \
+  --dir "atlas://app" \
+  --url "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -206,6 +223,15 @@ atlas migrate apply \
   --url "clickhouse://user:pass@:9000/default"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate apply \
+  --dir "atlas://app?tag=39e7e4e35fce7409bd26d25d8140061695d4ffd5" \
+  --url "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 :::
@@ -389,6 +415,16 @@ atlas migrate diff my_baseline \
   --to "clickhouse://user:pass@remote:9000/default"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate diff my_baseline \
+  --dir "file://migrations" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --to "postgres://postgres:pass@localhost:5432/example?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/versioned/diff.mdx b/doc/md/versioned/diff.mdx
index 94301f1b..666e31fe 100644
--- a/doc/md/versioned/diff.mdx
+++ b/doc/md/versioned/diff.mdx
@@ -236,6 +236,20 @@ table "users" {
 }
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```hcl title="schema.hcl"
+schema "test" {}
+
+table "users" {
+  schema = schema.test
+  column "id" {
+    type = int
+  }
+}
+```
+
 </TabItem>
 </Tabs>
 
@@ -307,6 +321,16 @@ atlas migrate diff create_users \
   --dev-url "docker://clickhouse/23.11/test"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.hcl" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -454,6 +478,23 @@ table "users" {
 }
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```hcl title="schema.hcl" {8-10}
+schema "test" {}
+
+table "users" {
+  schema = schema.test
+  column "id" {
+    type = int
+  }
+  column "name" {
+    type = varchar(255)
+  }
+}
+```
+
 </TabItem>
 </Tabs>
 
@@ -519,6 +560,16 @@ atlas migrate diff add_users_name \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate diff add_users_name \
+  --dir "file://migrations" \
+  --to "file://schema.hcl" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -632,6 +683,15 @@ CREATE TABLE `users` (
 ) ENGINE = Memory
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```sql title="schema.sql"
+CREATE TABLE "users" (
+  "id" int NOT NULL
+)
+```
+
 </TabItem>
 </Tabs>
 
@@ -702,6 +762,16 @@ atlas migrate diff create_users \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.sql" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
@@ -771,9 +841,9 @@ CREATE TABLE `users` (
 <TabItem value="postgres" label="PostgreSQL">
 
 ```sql title="schema.sql" {3}
-CREATE TABLE `users` (
-  `id` int NOT NULL,
-  `name` varchar(255) NOT NULL
+CREATE TABLE "users" (
+  "id" int NOT NULL,
+  "name" varchar(255) NOT NULL
 )
 ```
 
@@ -807,6 +877,16 @@ CREATE TABLE `users` (
 ) ENGINE = Memory
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```sql title="schema.sql" {3}
+CREATE TABLE "users" (
+  "id" int NOT NULL,
+  "name" varchar(255) NOT NULL
+)
+```
+
 </TabItem>
 </Tabs>
 
@@ -873,6 +953,16 @@ atlas migrate diff add_users_name \
   --dev-url "docker://clickhouse/23.11/dev"
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate diff add_users_name \
+  --dir "file://migrations" \
+  --to "file://schema.sql" \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable"
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/versioned/intro.mdx b/doc/md/versioned/intro.mdx
index 99d276fd..f749fe3a 100644
--- a/doc/md/versioned/intro.mdx
+++ b/doc/md/versioned/intro.mdx
@@ -97,6 +97,22 @@ CREATE TABLE `repos` (
 ) ENGINE = MergeTree() ORDER BY id;
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```sql title="schema.sql"
+CREATE TABLE "users" (
+  "id" bigint,
+  "name" varchar NOT NULL
+);
+
+CREATE TABLE "repos" (
+  "id" bigint,
+  "name" varchar NOT NULL,
+  "owner_id" bigint NOT NULL
+);
+```
+
 </TabItem>
 </Tabs>
 
@@ -143,6 +159,16 @@ atlas migrate diff initial \
   --format '{{ sql . "  " }}'
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate diff initial \
+  --to file://schema.sql \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --format '{{ sql . "  " }}'
+```
+
 </TabItem>
 </Tabs>
 
@@ -331,6 +357,19 @@ env "local" {
 }
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```hcl title="atlas.hcl" {1}
+# The "local" environment represents our local testings.
+env "local" {
+  url = "redshift://user:pass@redshift-cluster:5439/example?search_path=public&sslmode=disable"
+  migration {
+    dir = "atlas://app"
+  }
+}
+```
+
 </TabItem>
 </Tabs>
 
@@ -501,6 +540,34 @@ CREATE TABLE `commits` (
 // highlight-end
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```sql title="schema.sql"
+CREATE TABLE "users" (
+  "id" bigint,
+  "name" varchar NOT NULL,
+  PRIMARY KEY ("id")
+);
+
+CREATE TABLE "repos" (
+  "id" bigint,
+  "name" varchar NOT NULL,
+// highlight-next-line
+  "description" varchar NULL,
+  "owner_id" bigint NOT NULL
+);
+
+// highlight-start
+CREATE TABLE "commits" (
+  "id" bigint,
+  "message" varchar NOT NULL,
+  "repo_id" bigint NOT NULL,
+  "author_id" bigint NOT NULL
+);
+// highlight-end
+```
+
 </TabItem>
 </Tabs>
 
@@ -546,6 +613,16 @@ atlas migrate diff add_commits \
   --format '{{ sql . "  " }}'
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate diff add_commits \
+  --to file://schema.sql \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --format '{{ sql . "  " }}'
+```
+
 </TabItem>
 </Tabs>
 
@@ -585,6 +662,14 @@ atlas migrate push app \
   --dev-url "docker://clickhouse/23.11/dev" \
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```bash
+atlas migrate push app \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/md/versioned/lint.mdx b/doc/md/versioned/lint.mdx
index 6db1977b..2af55f51 100644
--- a/doc/md/versioned/lint.mdx
+++ b/doc/md/versioned/lint.mdx
@@ -90,6 +90,15 @@ atlas migrate lint \
   -w
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate lint \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  -w
+```
+
 </TabItem>
 </Tabs>
 
@@ -162,6 +171,15 @@ atlas migrate lint \
   --latest 1
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate lint \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --latest 1
+```
+
 </TabItem>
 </Tabs>
 
@@ -234,6 +252,16 @@ atlas migrate lint \
   -w
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas migrate lint \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --git-base "master" \
+  -w
+```
+
 </TabItem>
 </Tabs>
 
@@ -436,6 +464,20 @@ atlas migrate lint \
   -w
 ```
 
+</TabItem>
+<TabItem value="redshift" label="Redshift">
+
+```shell
+atlas login
+```
+
+```shell
+atlas migrate lint \
+  --dev-url "redshift://user:pass@redshift-cluster:5439/dev?search_path=public&sslmode=disable" \
+  --latest 1 \
+  -w
+```
+
 </TabItem>
 </Tabs>
 
diff --git a/doc/website/docusaurus.config.js b/doc/website/docusaurus.config.js
index 8da68f5f..f3880fd9 100644
--- a/doc/website/docusaurus.config.js
+++ b/doc/website/docusaurus.config.js
@@ -54,7 +54,7 @@ module.exports = {
           className: 'header-discord-link',
           position: 'right',
         },{
-          href: 'https://twitter.com/ariga_io',
+          href: 'https://twitter.com/atlasgo_io',
           className: 'header-twitter-link',
           position: 'right',
         },{
diff --git a/doc/website/sidebars.js b/doc/website/sidebars.js
index 4f266d25..ee8255b5 100644
--- a/doc/website/sidebars.js
+++ b/doc/website/sidebars.js
@@ -501,6 +501,11 @@ module.exports = {
             id: 'guides/getting-started-mssql',
             label: 'SQL Server'
         },
+        {
+            type: 'doc',
+            id: 'guides/getting-started-redshift',
+            label: 'Redshift'
+        },
         {
             type: 'doc',
             id: 'guides/ddl'
diff --git a/sql/internal/sqlx/plan.go b/sql/internal/sqlx/plan.go
index b7ce971b..ae35f31c 100644
--- a/sql/internal/sqlx/plan.go
+++ b/sql/internal/sqlx/plan.go
@@ -465,21 +465,21 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 	case *schema.DropSchema:
 		switch c2 := c2.(type) {
 		case *schema.DropFunc:
-			return sameSchema(c1.S, c2.F.Schema)
+			return SameSchema(c1.S, c2.F.Schema)
 		case *schema.DropProc:
-			return sameSchema(c1.S, c2.P.Schema)
+			return SameSchema(c1.S, c2.P.Schema)
 		case *schema.DropTable:
 			// Schema must be dropped after all its tables and references to them.
-			return sameSchema(c1.S, c2.T.Schema) || slices.ContainsFunc(c2.T.ForeignKeys, func(fk *schema.ForeignKey) bool {
-				return sameSchema(c1.S, fk.RefTable.Schema)
+			return SameSchema(c1.S, c2.T.Schema) || slices.ContainsFunc(c2.T.ForeignKeys, func(fk *schema.ForeignKey) bool {
+				return SameSchema(c1.S, fk.RefTable.Schema)
 			})
 		case *schema.ModifyTable:
-			return sameSchema(c1.S, c2.T.Schema) || slices.ContainsFunc(c2.Changes, func(c schema.Change) bool {
+			return SameSchema(c1.S, c2.T.Schema) || slices.ContainsFunc(c2.Changes, func(c schema.Change) bool {
 				fk, ok := c.(*schema.DropForeignKey)
-				return ok && sameSchema(c1.S, fk.F.RefTable.Schema)
+				return ok && SameSchema(c1.S, fk.F.RefTable.Schema)
 			})
 		case *schema.DropView:
-			return sameSchema(c1.S, c2.V.Schema)
+			return SameSchema(c1.S, c2.V.Schema)
 		}
 	case *schema.AddTable:
 		switch c2 := c2.(type) {
@@ -487,13 +487,13 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 			return c1.T.Schema.Name == c2.S.Name
 		case *schema.DropTable:
 			// Table recreation.
-			return c1.T.Name == c2.T.Name && sameSchema(c1.T.Schema, c2.T.Schema)
+			return c1.T.Name == c2.T.Name && SameSchema(c1.T.Schema, c2.T.Schema)
 		case *schema.AddTable:
 			if refTo(c1.T.ForeignKeys, c2.T) {
 				return true
 			}
 		case *schema.ModifyTable:
-			if (c1.T.Name != c2.T.Name || !sameSchema(c1.T.Schema, c2.T.Schema)) && refTo(c1.T.ForeignKeys, c2.T) {
+			if (c1.T.Name != c2.T.Name || !SameSchema(c1.T.Schema, c2.T.Schema)) && refTo(c1.T.ForeignKeys, c2.T) {
 				return true
 			}
 		case *schema.AddObject:
@@ -530,7 +530,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 		switch c2 := c2.(type) {
 		case *schema.AddTable:
 			// Table modification relies on its creation.
-			if c1.T.Name == c2.T.Name && sameSchema(c1.T.Schema, c2.T.Schema) {
+			if c1.T.Name == c2.T.Name && SameSchema(c1.T.Schema, c2.T.Schema) {
 				return true
 			}
 			// Tables need to be created before referencing them.
@@ -572,7 +572,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 		case *schema.AddSchema:
 			return c1.V.Schema.Name == c2.S.Name
 		case *schema.DropView:
-			return c1.V.Name == c2.V.Name && sameSchema(c1.V.Schema, c2.V.Schema) // View recreation.
+			return c1.V.Name == c2.V.Name && SameSchema(c1.V.Schema, c2.V.Schema) // View recreation.
 		case *schema.AddObject:
 			t, ok := c2.O.(schema.Type)
 			if ok && slices.ContainsFunc(c1.V.Columns, func(c *schema.Column) bool {
@@ -590,7 +590,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 	case *schema.ModifyView:
 		if c2, ok := c2.(*schema.AddView); ok {
 			// View modification relies on its creation.
-			return c1.From.Name == c2.V.Name && sameSchema(c1.From.Schema, c2.V.Schema)
+			return c1.From.Name == c2.V.Name && SameSchema(c1.From.Schema, c2.V.Schema)
 		}
 		return depOfAdd(c1.To.Deps, c2)
 	case *schema.AddFunc:
@@ -598,7 +598,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 		case *schema.AddSchema:
 			return c1.F.Schema.Name == c2.S.Name
 		case *schema.DropFunc:
-			return c1.F.Name == c2.F.Name && sameSchema(c1.F.Schema, c2.F.Schema) // Func recreation.
+			return c1.F.Name == c2.F.Name && SameSchema(c1.F.Schema, c2.F.Schema) // Func recreation.
 		case *schema.AddFunc:
 			if funcDep(c1.F, c2.F, opts) {
 				return true // Relies on other function or overload.
@@ -641,7 +641,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 	case *schema.ModifyFunc:
 		switch c2 := c2.(type) {
 		case *schema.AddFunc:
-			if c1.From.Name == c2.F.Name && sameSchema(c1.From.Schema, c2.F.Schema) {
+			if c1.From.Name == c2.F.Name && SameSchema(c1.From.Schema, c2.F.Schema) {
 				return true // Func modification relies on its creation.
 			}
 			if funcDep(c1.To, c2.F, opts) {
@@ -658,7 +658,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 		case *schema.AddSchema:
 			return c1.P.Schema.Name == c2.S.Name
 		case *schema.DropProc:
-			return c1.P.Name == c2.P.Name && sameSchema(c1.P.Schema, c2.P.Schema) // Proc recreation.
+			return c1.P.Name == c2.P.Name && SameSchema(c1.P.Schema, c2.P.Schema) // Proc recreation.
 		case *schema.AddProc:
 			if procDep(c1.P, c2.P, opts) {
 				return true // Relies on other procedure or overload.
@@ -692,7 +692,7 @@ func dependsOn(c1, c2 schema.Change, opts SortOptions) bool {
 	case *schema.ModifyProc:
 		switch c2 := c2.(type) {
 		case *schema.AddProc:
-			if c1.From.Name == c2.P.Name && sameSchema(c1.From.Schema, c2.P.Schema) {
+			if c1.From.Name == c2.P.Name && SameSchema(c1.From.Schema, c2.P.Schema) {
 				return true // Proc modification relies on its creation.
 			}
 			if procDep(c1.To, c2.P, opts) {
@@ -840,12 +840,12 @@ func depOfAdd(refs []schema.Object, c schema.Change) bool {
 	case *schema.AddFunc:
 		return slices.ContainsFunc(refs, func(o schema.Object) bool {
 			f, ok := o.(*schema.Func)
-			return ok && c.F.Name == f.Name && sameSchema(c.F.Schema, f.Schema)
+			return ok && c.F.Name == f.Name && SameSchema(c.F.Schema, f.Schema)
 		})
 	case *schema.AddProc:
 		return slices.ContainsFunc(refs, func(o schema.Object) bool {
 			f, ok := o.(*schema.Proc)
-			return ok && c.P.Name == f.Name && sameSchema(c.P.Schema, f.Schema)
+			return ok && c.P.Name == f.Name && SameSchema(c.P.Schema, f.Schema)
 		})
 	default:
 		return false
@@ -872,13 +872,13 @@ func SameTable(t1, t2 *schema.Table) bool {
 	if t1 == nil || t2 == nil {
 		return t1 == t2
 	}
-	return t1.Name == t2.Name && sameSchema(t1.Schema, t2.Schema)
+	return t1.Name == t2.Name && SameSchema(t1.Schema, t2.Schema)
 }
 
-// sameSchema reports if the given schemas are the same.
+// SameSchema reports if the given schemas are the same.
 // Objects can be different as they might reside in two
 // different states (current and desired).
-func sameSchema(s1, s2 *schema.Schema) bool {
+func SameSchema(s1, s2 *schema.Schema) bool {
 	if s1 == nil || s2 == nil {
 		return s1 == s2
 	}
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 5e312890..ac16a606 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -497,6 +497,18 @@ func (b *Builder) WrapIndent(f func(b *Builder)) *Builder {
 	})
 }
 
+// WrapIndentErr is like WrapErr but with extra level of indentation.
+func (b *Builder) WrapIndentErr(f func(b *Builder) error) error {
+	var err error
+	b.Wrap(func(b *Builder) {
+		b.IndentIn()
+		err = f(b)
+		b.IndentOut()
+		b.NL()
+	})
+	return err
+}
+
 // Clone returns a duplicate of the builder.
 func (b *Builder) Clone() *Builder {
 	return &Builder{
diff --git a/sql/mysql/convert.go b/sql/mysql/convert.go
index 957d34e7..c75a20a5 100644
--- a/sql/mysql/convert.go
+++ b/sql/mysql/convert.go
@@ -101,6 +101,8 @@ func FormatType(t schema.Type) (string, error) {
 		}
 	case *schema.UUIDType:
 		f = strings.ToLower(t.T)
+	case *NetworkType:
+		f = strings.ToLower(t.T)
 	case *schema.UnsupportedType:
 		// Do not accept unsupported types as we should cover all cases.
 		return "", fmt.Errorf("unsupported type %q", t.T)
@@ -244,6 +246,10 @@ func ParseType(raw string) (schema.Type, error) {
 		return &schema.UUIDType{
 			T: t,
 		}, nil
+	case TypeInet4, TypeInet6:
+		return &NetworkType{
+			T: t,
+		}, nil
 	default:
 		return &schema.UnsupportedType{
 			T: t,
diff --git a/sql/mysql/diff.go b/sql/mysql/diff.go
index 7c64bec8..c8978fd6 100644
--- a/sql/mysql/diff.go
+++ b/sql/mysql/diff.go
@@ -458,7 +458,7 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	var changed bool
 	switch fromT := fromT.(type) {
 	case *BitType, *schema.BinaryType, *schema.BoolType, *schema.DecimalType, *schema.FloatType,
-		*schema.JSONType, *schema.StringType, *schema.SpatialType, *schema.TimeType, *schema.UUIDType:
+		*schema.JSONType, *schema.StringType, *schema.SpatialType, *schema.TimeType, *schema.UUIDType, *NetworkType:
 		ft, err := FormatType(fromT)
 		if err != nil {
 			return false, err
diff --git a/sql/mysql/driver.go b/sql/mysql/driver.go
index 23d6070e..d4ab12e8 100644
--- a/sql/mysql/driver.go
+++ b/sql/mysql/driver.go
@@ -431,6 +431,9 @@ const (
 	TypeGeometryCollection = "geometrycollection" // Geometry_type::kGeometrycollection
 
 	TypeUUID = "uuid" // MariaDB supported uuid type from 10.7.0+
+
+	TypeInet4 = "inet4" // MariaDB type for storage of IPv4 addresses, from 10.10.0+.
+	TypeInet6 = "inet6" // MariaDB type for storage of IPv6 addresses, from 10.10.0+.
 )
 
 // Additional common constants in MySQL.
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index 282c0f15..b77106a8 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -911,6 +911,12 @@ type (
 		Values []string
 	}
 
+	// NetworkType stores an IPv4 or IPv6 address.
+	NetworkType struct {
+		schema.Type
+		T string
+	}
+
 	// putShow is an intermediate table attribute used
 	// on inspection to indicate if the 'SHOW TABLE' is
 	// required and for what.
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 5787b734..6cc8c51e 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -546,6 +546,8 @@ var TypeRegistry = schemahcl.NewRegistry(
 		schemahcl.NewTypeSpec(TypePolygon),
 		schemahcl.NewTypeSpec(TypeMultiPolygon),
 		schemahcl.NewTypeSpec(TypeGeometryCollection),
+		schemahcl.NewTypeSpec(TypeInet4),
+		schemahcl.NewTypeSpec(TypeInet6),
 	),
 )
 
diff --git a/sql/postgres/driver_oss.go b/sql/postgres/driver_oss.go
index 787687e9..75e7aafe 100644
--- a/sql/postgres/driver_oss.go
+++ b/sql/postgres/driver_oss.go
@@ -228,6 +228,13 @@ func convertDomains(_ []*sqlspec.Table, domains []*domain, _ *schema.Realm) erro
 	return nil
 }
 
+func convertAggregate(d *doc, _ *schema.Realm) error {
+	if len(d.Aggregates) > 0 {
+		return fmt.Errorf("postgres: aggregates are not supported by this version. Use: https://atlasgo.io/getting-started")
+	}
+	return nil
+}
+
 func convertSequences(_ []*sqlspec.Table, seqs []*sqlspec.Sequence, _ *schema.Realm) error {
 	if len(seqs) > 0 {
 		return fmt.Errorf("postgres: sequences are not supported by this version. Use: https://atlasgo.io/getting-started")
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 576c6d55..65a73cff 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -32,6 +32,7 @@ type (
 		Sequences     []*sqlspec.Sequence `spec:"sequence"`
 		Funcs         []*sqlspec.Func     `spec:"function"`
 		Procs         []*sqlspec.Func     `spec:"procedure"`
+		Aggregates    []*aggregate        `spec:"aggregate"`
 		Triggers      []*sqlspec.Trigger  `spec:"trigger"`
 		EventTriggers []*eventTrigger     `spec:"event_trigger"`
 		Extensions    []*extension        `spec:"extension"`
@@ -93,6 +94,17 @@ type (
 		// added to the extension definition.
 		schemahcl.DefaultExtension
 	}
+
+	// aggregate holds the specification for an aggregation function.
+	aggregate struct {
+		Name      string             `spec:",name"`
+		Qualifier string             `spec:",qualifier"`
+		Schema    *schemahcl.Ref     `spec:"schema"`
+		Args      []*sqlspec.FuncArg `spec:"arg"`
+		// state_type, state_func and rest of the attributes
+		// are appended after the function arguments.
+		schemahcl.DefaultExtension
+	}
 )
 
 // merge merges the doc d1 into d.
@@ -105,6 +117,7 @@ func (d *doc) merge(d1 *doc) {
 	d.Domains = append(d.Domains, d1.Domains...)
 	d.Composites = append(d.Composites, d1.Composites...)
 	d.Schemas = append(d.Schemas, d1.Schemas...)
+	d.Aggregates = append(d.Aggregates, d1.Aggregates...)
 	d.Sequences = append(d.Sequences, d1.Sequences...)
 	d.Extensions = append(d.Extensions, d1.Extensions...)
 	d.Triggers = append(d.Triggers, d1.Triggers...)
@@ -160,10 +173,23 @@ func (c *composite) SetQualifier(q string) { c.Qualifier = q }
 // SchemaRef returns the schema reference for the composite.
 func (c *composite) SchemaRef() *schemahcl.Ref { return c.Schema }
 
+// Label returns the defaults label used for the aggregate resource.
+func (a *aggregate) Label() string { return a.Name }
+
+// QualifierLabel returns the qualifier label used for the aggregate resource, if any.
+func (a *aggregate) QualifierLabel() string { return a.Qualifier }
+
+// SetQualifier sets the qualifier label used for the aggregate resource.
+func (a *aggregate) SetQualifier(q string) { a.Qualifier = q }
+
+// SchemaRef returns the schema reference for the aggregate.
+func (a *aggregate) SchemaRef() *schemahcl.Ref { return a.Schema }
+
 func init() {
 	schemahcl.Register("enum", &enum{})
 	schemahcl.Register("domain", &domain{})
 	schemahcl.Register("composite", &composite{})
+	schemahcl.Register("aggregate", &aggregate{})
 	schemahcl.Register("extension", &extension{})
 	schemahcl.Register("event_trigger", &eventTrigger{})
 }
@@ -182,6 +208,9 @@ func evalSpec(p *hclparse.Parser, v any, input map[string]cty.Value) error {
 		if err := convertTypes(&d, v); err != nil {
 			return err
 		}
+		if err := convertAggregate(&d, v); err != nil {
+			return err
+		}
 		if err := convertSequences(d.Tables, d.Sequences, v); err != nil {
 			return err
 		}
@@ -209,6 +238,9 @@ func evalSpec(p *hclparse.Parser, v any, input map[string]cty.Value) error {
 		if err := convertTypes(&d, r); err != nil {
 			return err
 		}
+		if err := convertAggregate(&d, r); err != nil {
+			return err
+		}
 		if err := convertSequences(d.Tables, d.Sequences, r); err != nil {
 			return err
 		}
@@ -260,6 +292,9 @@ func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 		if err := specutil.QualifyObjects(d.Materialized); err != nil {
 			return nil, err
 		}
+		if err := specutil.QualifyObjects(d.Aggregates); err != nil {
+			return nil, err
+		}
 		if err := specutil.QualifyObjects(d.Enums); err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/sql/internal/sqlx/sqlx_test.go b/sql/internal/sqlx/sqlx_test.go
index d22478a5..c7fac7fc 100644
--- a/sql/internal/sqlx/sqlx_test.go
+++ b/sql/internal/sqlx/sqlx_test.go
@@ -63,6 +63,7 @@ func TestBuilder(t *testing.T) {
 
 	// WrapErr.
 	require.EqualError(t, b.WrapErr(func(*Builder) error { return errors.New("oops") }), "oops")
+	require.EqualError(t, b.WrapIndentErr(func(*Builder) error { return errors.New("oops") }), "oops")
 }
 
 func TestBuilder_Qualifier(t *testing.T) {
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index bd776b31..957899f8 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -172,6 +172,8 @@ func TestDriver_InspectTable(t *testing.T) {
 | users      |  dt0           | datetime /* mariadb-5.3 */    |                      | NO          |            | NULL           |                | NULL               | NULL           | NULL                      |
 | users      |  dt1           | datetime(6) /* mariadb-5.3 */ |                      | NO          |            | NULL           |                | NULL               | NULL           | NULL                      |
 | users      |  dt2           | time(1) /* mariadb-5.3 */     |                      | NO          |            | NULL           |                | NULL               | NULL           | NULL                      |
+| users      |  inet4         | inet4                         |                      | NO          |            | NULL           |                | NULL               | NULL           | NULL                      |
+| users      |  inet6         | inet6                         |                      | NO          |            | NULL           |                | NULL               | NULL           | NULL                      |
 +------------+----------------+-------------------------------+----------------------+-------------+------------+----------------+----------------+--------------------+----------------+---------------------------+
 `))
 				m.ExpectQuery(queryIndexes).
@@ -199,6 +201,8 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "dt0", Type: &schema.ColumnType{Raw: "datetime /* mariadb-5.3 */", Type: &schema.TimeType{T: "datetime"}}},
 					{Name: "dt1", Type: &schema.ColumnType{Raw: "datetime(6) /* mariadb-5.3 */", Type: &schema.TimeType{T: "datetime", Precision: sqlx.P(6)}}},
 					{Name: "dt2", Type: &schema.ColumnType{Raw: "time(1) /* mariadb-5.3 */", Type: &schema.TimeType{T: "time", Precision: sqlx.P(1)}}},
+					{Name: "inet4", Type: &schema.ColumnType{Raw: "inet4", Type: &NetworkType{T: "inet4"}}},
+					{Name: "inet6", Type: &schema.ColumnType{Raw: "inet6", Type: &NetworkType{T: "inet6"}}},
 				}, t.Columns)
 				require.EqualValues([]schema.Attr{
 					&schema.Check{Name: "users_chk_1", Expr: `longtext <> '\'\'""'`},
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index a2c1766d..730595c5 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -1539,6 +1539,14 @@ func TestTypes(t *testing.T) {
 			typeExpr: "boolean",
 			expected: &schema.BoolType{T: TypeBool},
 		},
+		{
+			typeExpr: "inet4",
+			expected: &NetworkType{T: TypeInet4},
+		},
+		{
+			typeExpr: "inet6",
+			expected: &NetworkType{T: TypeInet6},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.typeExpr, func(t *testing.T) {