Repository: ariga/atlas
Pull request #2363 (2023-12-11 13:13:45)

doc/website/blog: hibernate-orm support


Patch:
diff --git a/doc/website/blog/2023-12-06-gitops-for-databases-part-1.mdx b/doc/website/blog/2023-12-06-gitops-for-databases-part-1.mdx
index bee9085c..4e5265ec 100644
--- a/doc/website/blog/2023-12-06-gitops-for-databases-part-1.mdx
+++ b/doc/website/blog/2023-12-06-gitops-for-databases-part-1.mdx
@@ -3,6 +3,14 @@ title: "GitOps for Databases, Part 1: CI/CD"
 authors: rotemtam
 tags: [kubernetes, gitops, migrations, argocd, github-actions, postgres]
 ---
+:::info
+
+This is the first post in a two-part tutorial, which demonstrates how to use the [Atlas Operator](https://github.com/ariga/atlas-operator) in tandem with
+[Atlas Cloud](https://atlasgo.cloud) and [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) to create a slick,
+modern GitOps workflow for managing your database migrations natively in Kubernetes.
+
+:::
+
 [GitOps](https://opengitops.dev/) is a software development and deployment methodology that uses Git as the central repository
 for both code and infrastructure configuration, enabling automated and auditable deployments.
 
diff --git a/doc/website/blog/2023-12-11-gitops-for-databases-part-2.mdx b/doc/website/blog/2023-12-11-gitops-for-databases-part-2.mdx
new file mode 100644
index 00000000..868c067f
--- /dev/null
+++ b/doc/website/blog/2023-12-11-gitops-for-databases-part-2.mdx
@@ -0,0 +1,306 @@
+---
+title: "GitOps for Databases, Part 2: Atlas Operator and ArgoCD"
+authors: rotemtam
+tags: [kubernetes, gitops, migrations, argocd, github-actions, postgres]
+---
+
+:::info
+
+This is the second post in a two-part tutorial, which demonstrates how to use the [Atlas Operator](https://github.com/ariga/atlas-operator) in tandem with
+[Atlas Cloud](https://atlasgo.cloud) and [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) to create a slick,
+modern GitOps workflow for managing your database migrations natively in Kubernetes.
+
+:::
+
+In [part one](2023-12-06-gitops-for-databases-part-1.mdx), we demonstrated how to initialize an Atlas project,
+and create a CI/CD pipeline that automatically plans, verifies and stores your database migrations in Atlas Cloud
+using GitHub Actions.
+
+In this part, we will show how to deploy these migrations using the Atlas Operator and ArgoCD to demonstrate
+a complete GitOps workflow for database migrations.
+
+## How to GitOps your Database Migrations on Kubernetes
+
+> *"We can wrap existing schema management solutions into containers, and run them in Kubernetes as Jobs.
+> But that is SILLY. That is not how we work in Kubernetes."*
+>
+> *-Viktor Farcic, DevOps ToolKit*
+
+As applications evolve, so do their database schemas. The practice of automating the deployment of database schema
+changes has evolved hand in hand with modern devops principles into what is known as
+[database migrations](https://en.wikipedia.org/wiki/Schema_migration). As part of this evolution, hundreds of
+"migration tools" have been created to help developers manage their database migrations. These tools range from
+ORM and language specific tools like [Alembic](https://alembic.sqlalchemy.org/en/latest/) for Python, to language
+agnostic tools like [Flyway](https://flywaydb.org/) and [Liquibase](https://www.liquibase.org/).
+
+When Kubernetes came along and teams started to containerize their applications, the knee-jerk reaction was to
+wrap these legacy tools in a container and run them as part of the application deployment process. We
+discussed some of the shortcomings of this approach in a recent [KubeCon talk](https://www.youtube.com/watch?v=U-o4HUNGHsE)
+and earlier [Webinar](https://www.youtube.com/watch?v=K_Js7hzEyrA).
+
+Atlas was created from the ground up to be a modern database migration tool that embodies modern DevOps principles
+and is designed to run natively in Kubernetes. The [Atlas Operator](https://github.com/ariga/atlas) enables teams
+to extend the native Kubernetes API with new resource types that represent database schemas and migrations. By using
+these capabilities it is possible to natively integrate database migrations into your GitOps workflow.
+
+## Prerequisites
+
+* A running Kubernetes cluster - for learning purposes, you can use
+[Minikube](https://minikube.sigs.k8s.io/docs/start/), which is a tool that runs a single-node
+Kubernetes cluster on your laptop.
+* [kubectl](https://kubernetes.io/docs/tasks/tools/) - a command-line tool for interacting with Kubernetes clusters.
+* [Helm](https://helm.sh/docs/intro/install/) - a package manager for Kubernetes.
+
+## Setting up the Atlas Operator and ArgoCD
+
+### 1. Install ArgoCD
+
+To install ArgoCD run the following commands:
+
+```bash
+kubectl create namespace argocd
+kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
+```
+
+Wait until all the pods in the `argocd` namespace are running:
+
+```bash
+kubectl wait --for=condition=ready pod --all -n argocd
+```
+
+`kubectl` will print something like this:
+
+```bash
+pod/argocd-application-controller-0 condition met
+pod/argocd-applicationset-controller-69dbc8585c-6qbwr condition met
+pod/argocd-dex-server-59f89468dc-xl7rg condition met
+pod/argocd-notifications-controller-55565589db-gnjdh condition met
+pod/argocd-redis-74cb89f466-gzk4f condition met
+pod/argocd-repo-server-68444f6479-mn5gl condition met
+pod/argocd-server-579f659dd5-5djb5 condition met
+```
+
+For more information or if you run into some errors refer to the
+[Argo CD Documentation](https://argo-cd.readthedocs.io/en/stable/getting_started/).
+
+### 2. Install the Atlas Operator
+
+```bash
+helm install atlas-operator oci://ghcr.io/ariga/charts/atlas-operator
+```
+
+Helm will print something like this:
+
+```bash
+Pulled: ghcr.io/ariga/charts/atlas-operator:0.3.6
+Digest: sha256:7e29c15e846fa9c25164f4ad5a7cb7f25e9ead2882082f0352985e58c1976f99
+NAME: atlas-operator
+LAST DEPLOYED: Mon Dec 11 10:25:11 2023
+NAMESPACE: default
+STATUS: deployed
+REVISION: 1
+TEST SUITE: None
+```
+
+Wait until the atlas-operator pod is running:
+
+```bash
+kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=atlas-operator -n default
+```
+
+`kubectl` will print something like this:
+
+```bash
+pod/atlas-operator-866dfbc56d-qkkkn condition met
+```
+
+For more information on the installation process, refer to the [Atlas Operator Documentation](/integrations/kubernetes/operator#getting-started)
+
+### Step 2: Set up the Target Database
+
+Start by deploying a simple PostgreSQL database using the following command:
+
+```bash
+kubectl apply -f https://raw.githubusercontent.com/ariga/atlas-operator/master/config/integration/databases/postgres.yaml
+```
+
+This will create a `Deployment` which runs a single (non-persistent) PostgreSQL instance and a `Service` that
+exposes it on port `5432`. In addition, it will create a `Secret` that contains the database credentials.
+
+Wait until the database pod is running:
+
+```bash
+kubectl wait --for=condition=ready pod -l app=postgres -n default
+```
+
+### Step 3: Create the `AtlasMigration` resource
+
+In order for the Atlas Operator to know which migrations to apply, we need to create an `AtlasMigration` resource
+that points to the Atlas Cloud project we created in [part one](2023-12-06-gitops-for-databases-part-1.mdx).
+Create a new directory called `manifests` in your GitHub repository. In it,
+create a file called `atlas-migration.yaml` with the following contents:
+
+```yaml title="manifests/atlas-migration.yaml"
+apiVersion: db.atlasgo.io/v1alpha1
+kind: AtlasMigration
+metadata:
+  name: migration
+spec:
+  urlFrom:
+    secretKeyRef:
+      key: url
+      name: postgres-credentials
+  cloud:
+    project: "atlasdemo" # Atlas Cloud project name
+    tokenFrom:
+      secretKeyRef:
+        name: atlas-credentials
+        key: token
+  dir:
+    remote:
+      name: "atlasdemo" # Migration directory name in your atlas cloud project
+      tag: "1d579be616db48803bb21713fd836a9165030f18" # See below on how to obtain this value for your project.
+```
+
+This resource tells the Atlas Operator to apply the migrations in the `atlasdemo` project in Atlas Cloud to the
+database specified in the `postgres-credentials` secret. Notice that the `tokenFrom` field references a secret
+called `atlas-credentials`. This secret will contain the Atlas Cloud API token that we created in
+[part one](2023-12-06-gitops-for-databases-part-1.mdx).
+
+To create it run:
+
+```
+kubectl create secret generic atlas-credentials --from-literal=token=aci_<replace with your token>
+```
+
+:::info Obtaining the `tag` field
+
+Notice the `tag` field in the `dir` section. This field tells the Atlas Operator which version of the migrations
+to apply. In this case, we are telling it to apply the migrations tagged with the commit hash
+`1d579be616db48803bb21713fd836a9165030f18` which is the commit hash of the merge commit that merged the pull
+request we created in [part one](2023-12-06-gitops-for-databases-part-1.mdx).
+
+To review which tags are available for your migrations, head over to you Atlas Cloud project and click on the
+`Tags` tab. You should see something like this:
+
+![](https://atlasgo.io/uploads/blog/gitops/dir-tags.png)
+
+:::
+
+Commit and push the changes to your GitHub repository.
+
+### Step 4: Create the ArgoCD Application
+
+Now that we have created the `AtlasMigration` resource, we can create an ArgoCD application that will deploy it.
+Create a file called `Application.yaml` in the root of your GitHub repository with the following contents:
+
+```yaml title="Application.yaml"
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: atlas-argocd-demo
+  namespace: argocd
+  finalizers:
+    - resources-finalizer.argocd.argoproj.io
+spec:
+  source:
+    path: manifests
+    repoURL: 'https://github.com/<your gh user>/<your repo name>'
+    targetRevision: master
+  destination:
+    namespace: default
+    server: 'https://kubernetes.default.svc'
+  project: default
+  syncPolicy:
+    automated:
+      prune: true
+      selfHeal: true
+    retry:
+      limit: 5
+      backoff:
+        duration: 5s
+        maxDuration: 3m0s
+        factor: 2
+    syncOptions:
+      - CreateNamespace=true
+```
+
+Be sure to replace the `repoURL` field with the URL of your GitHub repository.
+
+
+:::info
+
+If your repository is private, you will need to create a [GitHub Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic)
+and tell ArgoCD about it by running the following command:
+
+```bash
+export CURRENT_NS=$(kubectl config view --minify --output 'jsonpath={..namespace}')
+kubectl config set-context --current --namespace=argocd
+argocd repo add https://github.com/<user>/<repo> --username <user> --password ghp_<your token>
+kubectl config set-context --current --namespace=$CURRENT_NS
+```
+
+:::
+
+### 5. Step 5: Deploy!
+
+Next, apply the application manifest:
+
+```bash
+kubectl apply -f Application.yaml
+```
+
+Wait until the application is deployed:
+
+```bash
+kubectl wait --for=condition=ready atlasmigration/migration
+```
+
+Observe the status of the migration object:
+
+```bash
+ kubectl get atlasmigration/migration -o jsonpath='{.status}' | jq
+```
+
+The output will look similar to:
+
+```json
+{
+  "conditions": [
+    {
+      "lastTransitionTime": "2023-12-11T08:38:35Z",
+      "message": "",
+      "reason": "Applied",
+      "status": "True",
+      "type": "Ready"
+    }
+  ],
+  "lastApplied": 1702283914,
+  "lastAppliedVersion": "20231206075118",
+  "observed_hash": "6e4feac15a35d20c38e705428de507835c7c58d487eacc84ed012a17b002981d"
+}
+```
+
+You can also observe the status of the migration using the Atlas Cloud UI:
+
+![](https://atlasgo.io/uploads/blog/gitops/deploy-logs.png)
+
+## Wrapping Up
+
+Let's review the flow that we have created, from end to end:
+* Developers modify the desired state of their schema and use `atlas migrate diff` locally to generate a migration
+plan.
+* Developers commit the migration plan to their GitHub repository and create a pull request.
+* GitHub Actions runs the Atlas Continuous Integration workflow, which verifies the migration plan is correct and safe.
+* Once the pull request is merged, a GitHub Actions workflow pushes the new migration to Atlas Cloud. It is tagged
+with the commit hash of the merge commit.
+* When we are ready to deploy our changes to production, we change the value of the `tag` field in the
+`AtlasMigration` resource to the most recent tag. We push this change to our GitHub repository.
+* ArgoCD detects the change and updates our `AtlasMigration` resource.
+* The Atlas Operator detects the change and applies the migrations to the database.
+* The database is now up to date with the desired state of our schema!
+
+To summarize, in this tutorial we demonstrated how to use the Atlas Operator and ArgoCD to create a slick, modern GitOps workflow
+for managing your database migrations natively in Kubernetes.
+
+As always, we would love to hear your feedback and suggestions on our [Discord server](https://discord.gg/zZ6sWVg6NT).
diff --git a/doc/website/blog/2023-12-12-hibernate-schema-loader.mdx b/doc/website/blog/2023-12-12-hibernate-schema-loader.mdx
new file mode 100644
index 00000000..e5ac542c
--- /dev/null
+++ b/doc/website/blog/2023-12-12-hibernate-schema-loader.mdx
@@ -0,0 +1,418 @@
+---
+title: "Announcing Automatic Migrations for Hibernate Users"
+authors: dorav
+tags: [hibernate, schema, migration]
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import InstallationInstructions from '../../md/components/_installation_instructions.mdx'
+
+:::info TL;DR
+
+You can now import the desired database schema from your Hibernate project into Atlas,
+and use it to automatically plan migrations for you.
+
+[See an example](#demo-time)
+
+:::
+
+## Introduction
+
+[Hibernate-ORM](https://hibernate.org/orm/documentation/) is one of the most popular ORMs for Java, so much so that parts of it have evolved into
+[the JPA standard](https://docs.jboss.org/hibernate/orm/6.4/introduction/html_single/Hibernate_Introduction.html#hibernate-and-jpa) and the Jakarta APIs.
+
+Today, we are excited to announce that Atlas now supports loading and managing Hibernate schemas.
+
+[Atlas](https://atlasgo.io) is a modern tool for managing your database schema. It allows you to inspect, plan, lint and apply schema changes
+to your database. It is designed to be used by developers, DBAs and DevOps engineers alike.
+
+By using Atlas, Hibernate users can now enjoy these benefits:
+* A declarative migration flow - Atlas can operate like a "Terraform for databases", where
+by running `atlas schema apply` the application schema is applied on a target database.
+* Automatic schema migration planning - Alternatively, Atlas can operate using a more traditional
+versioned migration flow. However, contrary to most tools, Atlas will automatically analyze the diff between
+the migration directory and the current application data model, and will produce correct and
+safe SQL migration files.
+* CI for schema changes - Atlas can be used during CI to make sure you never merge a pull request
+that will break your database schema.
+* Modern CD integrations - Atlas integrates seamlessly with modern deployment tools such as Kubernetes,
+Terraform, Helm, Flux, and ArgoCD. This allows you to deploy changes to your database schema
+as part of your existing deployment pipelines.
+* Visualization - Atlas users can create beautiful, shareable ERDs of their application data model
+with a single command.
+* .. and much more (read more about [Atlas features](/)).
+
+:::note
+Hibernate support is currently in Beta and we would love to hear your feedback :heart:.
+Please reach out to us on [Discord](https://discord.gg/zZ6sWVg6NT) or by opening an [issue](https://github.com/ariga/atlas-provider-hibernate/issues/new).
+:::
+
+## Integrating Atlas into your Hibernate project
+
+Hibernate ships with an automatic schema management tool called [`hbm2ddl`](https://docs.jboss.org/hibernate/orm/6.4/userguide/html_single/Hibernate_User_Guide.html#schema-generation).
+Similarly to Atlas, this tool can inspect a target database and automatically migrate the schema to the desired one. However, the Hibernate team has been advising for years **not** to use this tool in production:
+
+> Although the automatic schema generation is very useful for testing and prototyping purposes, in a production environment, it’s much more flexible to manage the schema using incremental migration scripts.
+
+This is where Atlas comes in. Atlas can read Hibernate schema and plan database schema migrations.
+
+### How does it work?
+
+Atlas compares two database schema states and plans a migration to get from ones state to the other. The database schema can be read directly
+from Hibernate, a migration directory, a database connection, or another ORM.
+
+To read the Hibernate schema, Atlas utilizes the concept of an `external_schema` [datasource](/atlas-schema/projects#data-sources).
+
+## Demo Time
+
+For this demo, we are going to use Gradle, PostgreSQL and this [example project](https://github.com/ariga/atlas-provider-hibernate/tree/master/examples/e2e_java_example).
+
+### Installation
+
+If you haven't already, install the latest version of Atlas:
+
+<InstallationInstructions />
+
+Add the `hibernate-provider` to your project via Gradle or Maven:
+
+<Tabs>
+<TabItem value="Gradle Kotlin" label="Gradle Kotlin" default>
+
+```gradle
+plugins {
+    id("io.atlasgo.hibernate-provider-gradle-plugin") version "0.1"
+}
+```
+
+> To check the installation, run: `./gradlew help --task schema`
+
+</TabItem>
+<TabItem value="Gradle Groovy" label="Gradle Groovy" default>
+
+```gradle
+plugins {
+    id "io.atlasgo.hibernate-provider-gradle-plugin" version "0.1"
+}
+```
+
+> To check the installation, run: `./gradlew help --task schema`
+
+</TabItem>
+<TabItem value="Maven" label="Maven">
+
+```xml
+<build>
+    <pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>io.atlasgo</groupId>
+                <artifactId>hibernate-provider-maven-plugin</artifactId>
+                <version>0.1</version>
+            </plugin>
+        </plugins>
+    </pluginManagement>
+</build>
+```
+
+> To check the installation, run `mvn help:describe -Dplugin=hibernate-provider -Dgoal=schema`
+
+</TabItem>
+</Tabs>
+
+### Configuration
+
+The plugin adds a configurable Gradle task (or a Maven goal) that prints the Hibernate schema without requiring a database connection.
+However, the task needs to be configured with the database dialect. We can do this by creating a `schema-export.properties` file in the
+resource directory. For example, for MySQL / PostgreSQL:
+
+<Tabs>
+<TabItem value="MySQL" label="MySQL" default>
+
+```properties
+jakarta.persistence.database-product-name=MySQL
+jakarta.persistence.database-major-version=8
+```
+</TabItem>
+<TabItem value="PostgreSQL" label="PostgreSQL">
+
+```properties
+jakarta.persistence.database-product-name=PostgreSQL
+jakarta.persistence.database-major-version=15
+```
+</TabItem>
+</Tabs>
+
+Lastly, we need to configure Atlas to use this configuration by creating an `atlas.hcl` file
+and adding the definition of the Hibernate schema:
+
+<Tabs>
+<TabItem value="Gradle" label="Gradle" default>
+
+```hcl title="atlas.hcl"
+data "external_schema" "hibernate" {
+  program = [
+    "./gradlew",
+    "-q",
+    "schema",
+    "--properties", "schema-export.properties"
+  ]
+}
+```
+</TabItem>
+<TabItem value="Maven" label="Maven">
+
+```hcl title="atlas.hcl"
+data "external_schema" "hibernate" {
+  program = [
+    "./mvn",
+    "-q",
+    "hibernate-provider:schema",
+    "-Dproperties", "schema-export.properties"
+  ]
+}
+```
+
+</TabItem>
+</Tabs>
+
+And the Atlas configuration:
+
+<Tabs>
+<TabItem value="MySQL" label="MySQL" default>
+
+```hcl title="atlas.hcl"
+env "hibernate" {
+  src = data.external_schema.hibernate.url
+  dev = "docker://mysql/8/dev"
+  migration {
+    dir = "file://migrations"
+  }
+  format {
+    migrate {
+      diff = "{{ sql . \"  \" }}"
+    }
+  }
+}
+```
+
+</TabItem>
+<TabItem value="PostgreSQL" label="PostgreSQL">
+
+```hcl title="atlas.hcl"
+env "hibernate" {
+  src = data.external_schema.hibernate.url
+  dev = "docker://postgres/15/dev?search_path=public"
+  migration {
+    dir = "file://migrations"
+  }
+  format {
+    migrate {
+      diff = "{{ sql . \"  \" }}"
+    }
+  }
+}
+```
+
+</TabItem>
+</Tabs>
+
+## Running Atlas
+
+We should now be able to view our schema using Atlas:
+
+```bash
+atlas schema inspect -w --env hibernate --url env://src
+```
+
+The `-w` flag allows us to inspect the schema in [atlas cloud](https://atlasgo.cloud/):
+
+![](https://atlasgo.io/uploads/images/hibernate-schema-inspect.png)
+
+Atlas has many more features we can explore, let's create a migration directory from our schema:
+
+```bash
+atlas migrate diff --env hibernate
+```
+
+By running `atlas migrate diff`, Atlas compares the state of our Hibernate schema and the state of the schema in the `migration` directory.
+Atlas sees that the migration directory does not exist and initializes it with the current Hibernate schema.
+Observe the migration directory, it should contain similar files:
+
+<Tabs>
+<TabItem value="migrations/20231211114848.sql" default>
+
+```sql
+-- Create "movies" table
+-- Create "movies" table
+CREATE TABLE "movies" (
+  "id" bigserial NOT NULL,
+  "numberinseries" integer NULL,
+  "title" character varying(255) NULL,
+  PRIMARY KEY ("id")
+);
+-- Create "actors" table
+CREATE TABLE "actors" (
+  "name" character varying(255) NOT NULL,
+  PRIMARY KEY ("name")
+);
+-- Create "movieparticipation" table
+CREATE TABLE "movieparticipation" (
+  "actorname" character varying(255) NOT NULL,
+  "movieid" bigint NOT NULL,
+  PRIMARY KEY ("actorname", "movieid"),
+  CONSTRAINT "fkaq2kkwvh9870847sm35vtjtiy" FOREIGN KEY ("movieid") REFERENCES "movies" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION,
+  CONSTRAINT "fktm8fbwa577lnbvwdjegwxvget" FOREIGN KEY ("actorname") REFERENCES "actors" ("name") ON UPDATE NO ACTION ON DELETE NO ACTION
+);
+```
+
+</TabItem>
+<TabItem value="migrations/atlas.sum">
+
+```
+h1:Ezh6r7A0pU4XS7PztE87h+aq5PoGGVTd6kaprtpXxas=
+20231211114848.sql h1:9ECs2QsjHE8S4PxXxFcoPYaOVUWRjpxHuF8MGFRd3dE=
+```
+
+</TabItem>
+</Tabs>
+
+Atlas uses the `atlas.sum` file to protect against conflicting schema changes, you can read about it [here](/concepts/migration-directory-integrity).
+
+:::note
+Currently, Atlas does not support using generated fields that require data initialization such as `GenerationType.SEQUENCE`, `GenerationType.TABLE`, and `Generation.AUTO`.
+
+If needed, you can still export the schema using the flag --enable-table-generators (or -Denable-table-generators using Maven). When applying the schema to your database, you will need
+to make sure to apply the ignored statements (using `atlas migrate --env hibernate diff --edit`). See more information on manual migrations [here](/versioned/diff)
+
+For example, if you are adding GenerationType.SEQUENCE to the Event entity, you will need to add insert statements to your generated migration file:
+
+```diff
+diff --git a/migrations/20231210140844.sql b/examples/with_local_plugin_repository/migrations/20231210140844.sql
+index ad80a64..5955834 100644
+--- a/migrations/20231210140844.sql
++++ b/migrations/20231210140844.sql
+@@ -4,3 +4,6 @@ CREATE TABLE `Event` (`id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(255)
+ -- Create "Event_SEQ" table
+ CREATE TABLE `Event_SEQ` (`next_val` bigint NULL) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
++ -- Initialize "Event_SEQ" table
++ insert into Event_SEQ values ( 1 );
+```
+
+Testing these changes can be done by running the application with a local database and creating the entity. To apply the migration directory
+to the local database, use `atlas migrate apply`.
+:::
+
+### Testing the migrations
+
+Now that our migration directory is ready, let's see how to apply it to a target database.
+Let's start a local PostgreSQL instance:
+
+```bash
+docker run -it --rm --name mypostgres -p 5432:5432 -e 'POSTGRES_PASSWORD=password' postgres
+```
+
+Next, let's apply our migrations to the database:
+
+```bash
+atlas migrate apply --env hibernate --url 'postgres://postgres:password@0.0.0.0:5432/?search_path=public&sslmode=disable'
+```
+
+Atlas provides details on the applied migrations:
+
+```
+Migrating to version 20231211121102 (1 migrations in total):
+
+  -- migrating version 20231211121102
+    -> CREATE TABLE "movies" (
+         "id" bigserial NOT NULL,
+         "numberinseries" integer NULL,
+         "title" character varying(255) NULL,
+         PRIMARY KEY ("id")
+       );
+    -> CREATE TABLE "actors" (
+         "name" character varying(255) NOT NULL,
+         PRIMARY KEY ("name")
+       );
+    -> CREATE TABLE "movieparticipation" (
+         "actorname" character varying(255) NOT NULL,
+         "movieid" bigint NOT NULL,
+         PRIMARY KEY ("actorname", "movieid"),
+         CONSTRAINT "fkaq2kkwvh9870847sm35vtjtiy" FOREIGN KEY ("movieid") REFERENCES "movies" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION,
+         CONSTRAINT "fktm8fbwa577lnbvwdjegwxvget" FOREIGN KEY ("actorname") REFERENCES "actors" ("name") ON UPDATE NO ACTION ON DELETE NO ACTION
+       );
+  -- ok (9.282079ms)
+
+  -------------------------
+  -- 54.100203ms
+  -- 1 migrations
+  -- 3 sql statements
+```
+
+To confirm the migrations were applied, we can use Atlas to inspect the database. Run the following command:
+
+```bash
+atlas schema inspect -w --env hibernate --url 'postgres://postgres:password@0.0.0.0:5432/?search_path=public&sslmode=disable'
+````
+
+![](https://atlasgo.io/uploads/images/hibernate-schema-inspect.png)
+
+### Making changes with confidence
+
+Atlas ships with a static code analysis engine that can detect risky schema changes during development or Continuous Integration.
+This functionality is exposed to users via the `migrate lint` command.
+Let's demonstrate this capability with an example.
+
+Suppose we make the following change:
+
+```diff
+--- a/src/main/java/org/example/Movie.java
++++ b/src/main/java/org/example/Movie.java
+@@ -10,13 +10,10 @@ public class Movie {
+
+     Movie(String title, Integer numberInSeries) {
+         this.title = title;
+-        this.numberInSeries = numberInSeries;
+     }
+     @Id
+     @GeneratedValue(strategy = GenerationType.IDENTITY)
+     public Long id;
+
+     public String title;
+-
+-    public Integer numberInSeries;
+ }
+```
+
+By removing an attribute from the `Movie` model, we are removing a column from the database schema.
+Let's see how Atlas handles this change. Run `atlas migrate diff --env hibernate`, and observe the new file in the migration directory:
+```sql title="cat migrations/20231211124321.sql"
+-- Modify "movies" table
+ALTER TABLE "movies" DROP COLUMN "numberinseries";
+```
+
+While this change may be desired, it is an irreversible operation that should be done with caution.
+Atlas can help us avoid dangerous schema changes by linting the migration directory and not allowing such a change to get merged.
+
+Running the following command, we can see that Atlas will warn us about a destructive change to the database:
+
+```bash
+atlas migrate lint --env hibernate --latest 1
+20231211124321.sql: destructive changes detected:
+        L2: Dropping non-virtual column "numberinseries"
+```
+
+Running `migrate lint` locally during development can be very useful, but linting becomes much more powerful when you
+integrate into your Continuous Integration pipeline.
+Atlas offers a set of [Github Actions](/integrations/github-actions) designed to make setting this up a breeze.
+
+## Conclusion
+
+In this post, we have presented how Hibernate projects can use Atlas to automatically
+plan, lint and apply schema migrations based only on their data model.
+
+If you want to explore more configuration options or dive deeper into how this works, please take a look at this [repository](https://github.com/ariga/atlas-provider-hibernate).
+
+#### How can we make Atlas better?
+
+We would love to hear from you [on our Discord server](https://discord.gg/zZ6sWVg6NT) :heart:.
diff --git a/doc/website/blog/authors.yml b/doc/website/blog/authors.yml
index 152ea615..d3eb5c71 100644
--- a/doc/website/blog/authors.yml
+++ b/doc/website/blog/authors.yml
@@ -43,3 +43,6 @@ ronenlu:
   title: Software Engineer
   url: "https://github.com/ronenlu"
   image_url: "https://avatars.githubusercontent.com/u/63970571?v=4"
+dorav:
+  name: Dor Avraham
+  image_url: "https://avatars.githubusercontent.com/u/6278240?v=4"
\ No newline at end of file
diff --git a/sql/internal/sqlx/diff.go b/sql/internal/sqlx/diff.go
index 5afb647e..ebe429dc 100644
--- a/sql/internal/sqlx/diff.go
+++ b/sql/internal/sqlx/diff.go
@@ -65,6 +65,12 @@ type (
 		ReferenceChanged(from, to schema.ReferenceOption) bool
 	}
 
+	// DropSchemaChanger is an optional interface allows DiffDriver to drop
+	// schema objects before dropping the schema itself.
+	DropSchemaChanger interface {
+		DropSchemaChange(*schema.Schema) []schema.Change
+	}
+
 	// A Normalizer wraps the Normalize method for normalizing the from and to tables before
 	// running diffing. The "from" usually represents the inspected database state (current),
 	// and the second represents the desired state.
@@ -107,7 +113,12 @@ func (d *Diff) RealmDiff(from, to *schema.Realm, options ...schema.DiffOption) (
 	for _, s1 := range from.Schemas {
 		s2, ok := to.Schema(s1.Name)
 		if !ok {
-			changes = opts.AddOrSkip(changes, &schema.DropSchema{S: s1})
+			if ds, ok := d.DiffDriver.(DropSchemaChanger); ok {
+				// The driver can drop other objects before dropping the schema.
+				changes = opts.AddOrSkip(changes, ds.DropSchemaChange(s1)...)
+			} else {
+				changes = opts.AddOrSkip(changes, &schema.DropSchema{S: s1})
+			}
 			continue
 		}
 		change, err := d.schemaDiff(s1, s2, opts)
diff --git a/sql/internal/sqlx/plan.go b/sql/internal/sqlx/plan.go
index 844c332c..22ed8e98 100644
--- a/sql/internal/sqlx/plan.go
+++ b/sql/internal/sqlx/plan.go
@@ -389,7 +389,7 @@ func SortChanges(changes []schema.Change) []schema.Change {
 		switch c.(type) {
 		case *schema.AddView, *schema.DropView, *schema.ModifyView:
 			views = append(views, c)
-		case *schema.DropTable, *schema.DropFunc, *schema.DropProc, *schema.DropObject:
+		case *schema.DropSchema, *schema.DropTable, *schema.DropFunc, *schema.DropProc, *schema.DropObject:
 			drop = append(drop, c)
 		default:
 			other = append(other, c)
@@ -449,8 +449,21 @@ func dependsOn(c1, c2 schema.Change) bool {
 		return true
 	}
 	switch c1 := c1.(type) {
+	case *schema.DropSchema:
+		switch c2 := c2.(type) {
+		case *schema.DropFunc:
+			return c1.S.Name == c2.F.Schema.Name
+		case *schema.DropProc:
+			return c1.S.Name == c2.P.Schema.Name
+		case *schema.DropTable:
+			return c1.S.Name == c2.T.Schema.Name
+		case *schema.DropView:
+			return c1.S.Name == c2.V.Schema.Name
+		}
 	case *schema.AddTable:
 		switch c2 := c2.(type) {
+		case *schema.AddSchema:
+			return c1.T.Schema.Name == c2.S.Name
 		case *schema.DropTable:
 			return c1.T.Name == c2.T.Name && c1.T.Schema == c2.T.Schema // Table recreation.
 		case *schema.AddTable:
@@ -527,10 +540,14 @@ func dependsOn(c1, c2 schema.Change) bool {
 		}
 		return depOfAdd(c1.T.Deps, c2)
 	case *schema.AddView:
-		if c2, ok := c2.(*schema.DropView); ok && c1.V.Name == c2.V.Name && c1.V.Schema == c2.V.Schema {
-			return true // View recreation.
+		switch c2 := c2.(type) {
+		case *schema.AddSchema:
+			return c1.V.Schema.Name == c2.S.Name
+		case *schema.DropView:
+			return c1.V.Name == c2.V.Name && c1.V.Schema == c2.V.Schema // View recreation.
+		default:
+			return depOfAdd(c1.V.Deps, c2)
 		}
-		return depOfAdd(c1.V.Deps, c2)
 	case *schema.DropView:
 		return depOfDrop(c1.V, c2)
 	case *schema.ModifyView:
@@ -540,10 +557,14 @@ func dependsOn(c1, c2 schema.Change) bool {
 		}
 		return depOfAdd(c1.To.Deps, c2)
 	case *schema.AddFunc:
-		if c2, ok := c2.(*schema.DropFunc); ok && c1.F.Name == c2.F.Name && c1.F.Schema == c2.F.Schema {
-			return true // Func recreation.
+		switch c2 := c2.(type) {
+		case *schema.AddSchema:
+			return c1.F.Schema.Name == c2.S.Name
+		case *schema.DropFunc:
+			return c1.F.Name == c2.F.Name && c1.F.Schema == c2.F.Schema // Func recreation.
+		default:
+			return depOfAdd(c1.F.Deps, c2)
 		}
-		return depOfAdd(c1.F.Deps, c2)
 	case *schema.DropFunc:
 		return depOfDrop(c1.F, c2)
 	case *schema.ModifyFunc:
@@ -553,10 +574,14 @@ func dependsOn(c1, c2 schema.Change) bool {
 		}
 		return depOfAdd(c1.To.Deps, c2)
 	case *schema.AddProc:
-		if c2, ok := c2.(*schema.DropProc); ok && c1.P.Name == c2.P.Name && c1.P.Schema == c2.P.Schema {
-			return true // Proc recreation.
+		switch c2 := c2.(type) {
+		case *schema.AddSchema:
+			return c1.P.Schema.Name == c2.S.Name
+		case *schema.DropProc:
+			return c1.P.Name == c2.P.Name && c1.P.Schema == c2.P.Schema // Proc recreation.
+		default:
+			return depOfAdd(c1.P.Deps, c2)
 		}
-		return depOfAdd(c1.P.Deps, c2)
 	case *schema.DropProc:
 		return depOfDrop(c1.P, c2)
 	case *schema.ModifyProc:
diff --git a/sql/migrate/lex.go b/sql/migrate/lex.go
index 89d1fbf4..a1fb5a83 100644
--- a/sql/migrate/lex.go
+++ b/sql/migrate/lex.go
@@ -217,13 +217,13 @@ Scan:
 			s.comment("--", "\n")
 		case r == '/' && s.next() == '*':
 			s.comment("/*", "*/")
-		case s.MatchBeginAtomic && reBeginAtomic.MatchString(s.input[s.pos-1:]):
+		case s.delim == delimiter && s.MatchBeginAtomic && reBeginAtomic.MatchString(s.input[s.pos-1:]):
 			if err := s.skipBeginAtomic(); err != nil {
 				return nil, err
 			}
 			text = s.input[:s.pos]
 			break Scan
-		case s.MatchBegin && reBegin.MatchString(s.input[s.pos-1:]):
+		case s.delim == delimiter && s.MatchBegin && reBegin.MatchString(s.input[s.pos-1:]):
 			if err := s.skipBegin(); err != nil {
 				return nil, err
 			}
diff --git a/sql/mysql/mysqlcheck/mysqlcheck.go b/sql/mysql/mysqlcheck/mysqlcheck.go
index 5b794279..4b9b97bb 100644
--- a/sql/mysql/mysqlcheck/mysqlcheck.go
+++ b/sql/mysql/mysqlcheck/mysqlcheck.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 
 	"ariga.io/atlas/schemahcl"
+	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/schema"
@@ -59,11 +60,13 @@ func addNotNull(p *datadepend.ColumnPass) (diags []sqlcheck.Diagnostic, err erro
 	}
 	switch ct := p.Column.Type.Type.(type) {
 	case *mysql.BitType, *schema.BoolType, *schema.IntegerType, *schema.DecimalType, *schema.FloatType, *schema.BinaryType:
-		tt, err := mysql.FormatType(p.Column.Type.Type)
-		if err != nil {
-			return nil, err
+		if !sqlx.Has(p.Column.Attrs, &mysql.AutoIncrement{}) {
+			tt, err := mysql.FormatType(p.Column.Type.Type)
+			if err != nil {
+				return nil, err
+			}
+			implicitUpdate(tt, "0")
 		}
-		implicitUpdate(tt, "0")
 	case *schema.StringType:
 		switch ct.T {
 		case mysql.TypeVarchar, mysql.TypeChar:


Test patch:
diff --git a/internal/integration/testdata/mysql/cli-migrate-lint-add-notnull.txt b/internal/integration/testdata/mysql/cli-migrate-lint-add-notnull.txt
index 8000cc08..c6b69233 100644
--- a/internal/integration/testdata/mysql/cli-migrate-lint-add-notnull.txt
+++ b/internal/integration/testdata/mysql/cli-migrate-lint-add-notnull.txt
@@ -12,6 +12,9 @@ cmp got.txt empty.txt
 atlas migrate lint --dir file://migrations4 --dev-url URL --latest=1 > got.txt
 cmp got.txt expected4.txt
 
+atlas migrate lint --dir file://migrations5 --dev-url URL --latest=1 > got.txt
+cmp got.txt empty.txt
+
 -- empty.txt --
 -- migrations1/1.sql --
 CREATE TABLE users (id int);
@@ -51,3 +54,9 @@ UPDATE `users` SET `name` = CONCAT("name", id) WHERE `name` IS NULL;
 
 	L1: Adding a non-nullable "varchar" column "name" on table "users" without a default value implicitly sets existing rows with ""
 
+-- migrations5/1.sql --
+CREATE TABLE users (name varchar(36));
+
+-- migrations5/2.sql --
+-- Auto-increment fills existing rows.
+ALTER TABLE users ADD COLUMN `id` bigint PRIMARY KEY AUTO_INCREMENT;
diff --git a/sql/migrate/testdata/lex/15_dollar_quote.sql b/sql/migrate/testdata/lex/15_dollar_quote.sql
index 201af985..da728683 100644
--- a/sql/migrate/testdata/lex/15_dollar_quote.sql
+++ b/sql/migrate/testdata/lex/15_dollar_quote.sql
@@ -124,4 +124,14 @@ CREATE FUNCTION my_function() RETURNS void AS $$
 BEGIN
    -- function body here
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION f1(target regclass)
+  RETURNS VOID AS $BEGIN$
+DECLARE
+table_name TEXT := quote_ident(target :: text);
+BEGIN
+EXECUTE 'alter table ' || table_name || ' add column id serial';
+END;
+$BEGIN$
+LANGUAGE plpgsql;
\ No newline at end of file
diff --git a/sql/migrate/testdata/lex/15_dollar_quote.sql.golden b/sql/migrate/testdata/lex/15_dollar_quote.sql.golden
index dbe356f7..6acebcea 100644
--- a/sql/migrate/testdata/lex/15_dollar_quote.sql.golden
+++ b/sql/migrate/testdata/lex/15_dollar_quote.sql.golden
@@ -131,4 +131,14 @@ CREATE FUNCTION my_function() RETURNS void AS $$
 BEGIN
    -- function body here
 END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+-- end --
+CREATE OR REPLACE FUNCTION f1(target regclass)
+  RETURNS VOID AS $BEGIN$
+DECLARE
+table_name TEXT := quote_ident(target :: text);
+BEGIN
+EXECUTE 'alter table ' || table_name || ' add column id serial';
+END;
+$BEGIN$
+LANGUAGE plpgsql;
\ No newline at end of file
diff --git a/sql/migrate/testdata/lexgroup/3_delimiter.sql b/sql/migrate/testdata/lexgroup/3_delimiter.sql
new file mode 100644
index 00000000..d0afb6b2
--- /dev/null
+++ b/sql/migrate/testdata/lexgroup/3_delimiter.sql
@@ -0,0 +1,18 @@
+-- delimiter should not conflict with compound statements scanning.
+
+create function `add2` (a int, b int) returns int deterministic no sql return a + b;
+create function `add3` (a int, b int, c int) returns int deterministic no sql return a + b + c;
+delimiter |
+-- error 1418
+create function fn1(x int) returns int deterministic
+begin
+       insert into t1 values (x);
+       return x+2;
+end|
+create function fn2(x int) returns int deterministic
+begin
+       insert into t1 values (x);
+       return x+2;
+end|
+delimiter ;
+create function `add4` (a int, b int, c int, d int) returns int deterministic no sql return a + b + c + d;
\ No newline at end of file
diff --git a/sql/migrate/testdata/lexgroup/3_delimiter.sql.golden b/sql/migrate/testdata/lexgroup/3_delimiter.sql.golden
new file mode 100644
index 00000000..f16478a6
--- /dev/null
+++ b/sql/migrate/testdata/lexgroup/3_delimiter.sql.golden
@@ -0,0 +1,17 @@
+create function `add2` (a int, b int) returns int deterministic no sql return a + b;
+-- end --
+create function `add3` (a int, b int, c int) returns int deterministic no sql return a + b + c;
+-- end --
+create function fn1(x int) returns int deterministic
+begin
+       insert into t1 values (x);
+       return x+2;
+end
+-- end --
+create function fn2(x int) returns int deterministic
+begin
+       insert into t1 values (x);
+       return x+2;
+end
+-- end --
+create function `add4` (a int, b int, c int, d int) returns int deterministic no sql return a + b + c + d;
\ No newline at end of file
diff --git a/sql/mysql/mysqlcheck/mysqlcheck_test.go b/sql/mysql/mysqlcheck/mysqlcheck_test.go
index df87de13..75cf6a66 100644
--- a/sql/mysql/mysqlcheck/mysqlcheck_test.go
+++ b/sql/mysql/mysqlcheck/mysqlcheck_test.go
@@ -99,6 +99,7 @@ func TestDataDepend_MySQL8_ImplicitUpdate(t *testing.T) {
 									),
 								Changes: []schema.Change{
 									&schema.AddColumn{C: schema.NewTimeColumn("b", mysql.TypeTimestamp)},
+									&schema.AddColumn{C: schema.NewIntColumn("id", mysql.TypeBigInt).AddAttrs(&mysql.AutoIncrement{})},
 								},
 							},
 						},