snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
241
doc/md/migration/composite.mdx
Normal file
241
doc/md/migration/composite.mdx
Normal file
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: Using Composite Types in Ent Schema
|
||||
id: composite
|
||||
slug: composite-types
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
In PostgreSQL, a composite type is structured like a row or record, consisting of field names and their corresponding
|
||||
data types. Setting an Ent field as a composite type enables you to store complex and structured data in a single column.
|
||||
|
||||
This guide explains how to define a schema field type as a composite type in your Ent schema and configure the schema migration
|
||||
to manage both the composite types and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Composite Types](https://atlasgo.io/atlas-schema/hcl#composite-type) is available exclusively to Pro users.
|
||||
To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Composite types,
|
||||
or any other database objects do not have representation in Ent models - A composite type can be defined once,
|
||||
and may be used multiple times in different fields and models.
|
||||
|
||||
In order to extend our PostgreSQL schema to include both custom composite types and our Ent types, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Create a `schema.sql` that defines the necessary composite type. In the same way, you can configure the composite type in
|
||||
[Atlas Schema HCL language](https://atlasgo.io/atlas-schema/hcl-types#composite-type):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value={"sql"} label={"Using SQL"}>
|
||||
|
||||
```sql title="schema.sql"
|
||||
CREATE TYPE address AS (
|
||||
street text,
|
||||
city text
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value={"hcl"} label={"Using HCL"}>
|
||||
|
||||
```hcl title="schema.hcl"
|
||||
schema "public" {}
|
||||
|
||||
composite "address" {
|
||||
schema = schema.public
|
||||
field "street" {
|
||||
type = text
|
||||
}
|
||||
field "city" {
|
||||
type = text
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
2\. In your Ent schema, define a field that uses the composite type only in PostgreSQL dialect:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value={"schema"} label={"Schema"}>
|
||||
|
||||
```go title="ent/schema/user.go" {6-8}
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("address").
|
||||
GoType(&Address{}).
|
||||
SchemaType(map[string]string{
|
||||
dialect.Postgres: "address",
|
||||
}),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
In case a schema with custom driver-specific types is used with other databases, Ent falls back to the default type
|
||||
used by the driver (e.g., "varchar").
|
||||
:::
|
||||
</TabItem>
|
||||
<TabItem value={"address"} label={"Address Type"}>
|
||||
|
||||
```go title="ent/schematype/address.go"
|
||||
type Address struct {
|
||||
Street, City string
|
||||
}
|
||||
|
||||
var _ field.ValueScanner = (*Address)(nil)
|
||||
|
||||
// Scan implements the database/sql.Scanner interface.
|
||||
func (a *Address) Scan(v interface{}) (err error) {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
case string:
|
||||
_, err = fmt.Sscanf(v, "(%q,%q)", &a.Street, &a.City)
|
||||
case []byte:
|
||||
_, err = fmt.Sscanf(string(v), "(%q,%q)", &a.Street, &a.City)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a *Address) Value() (driver.Value, error) {
|
||||
return fmt.Sprintf("(%q,%q)", a.Street, a.City), nil
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
3\. Create a simple `atlas.hcl` config file with a `composite_schema` that includes both your custom types defined in
|
||||
`schema.sql` and your Ent schema:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load first custom types first.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
# Second, load the Ent schema.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our schema, we can get its representation using the `atlas schema inspect` command, generate migrations for
|
||||
it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL. Note, the `address` composite type is defined in the schema before
|
||||
its usage in the `address` field:
|
||||
|
||||
```sql
|
||||
-- Create composite type "address"
|
||||
CREATE TYPE "address" AS ("street" text, "city" text);
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "address" "address" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create composite type "address"
|
||||
CREATE TYPE "address" AS ("street" text, "city" text);
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "address" "address" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/compositetypes).
|
||||
208
doc/md/migration/domain.mdx
Normal file
208
doc/md/migration/domain.mdx
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: Using Domain Types in Ent Schema
|
||||
id: domain
|
||||
slug: domain-types
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
PostgreSQL domain types are user-defined data types that extend existing ones, allowing you to add constraints that
|
||||
restrict the values they can hold. Setting a field type as a domain type enables you to enforce data integrity and
|
||||
validation rules at the database level.
|
||||
|
||||
This guide explains how to define a schema field type as a domain type in your Ent schema and configure the schema migration
|
||||
to manage both the domains and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Domain Types](https://atlasgo.io/atlas-schema/hcl#domain) is available exclusively to Pro users.
|
||||
To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Domain types,
|
||||
or any other database objects do not have representation in Ent models - A domain type can be defined once,
|
||||
and may be used multiple times in different fields and models.
|
||||
|
||||
In order to extend our PostgreSQL schema to include both custom domain types and our Ent types, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Create a `schema.sql` that defines the necessary domain type. In the same way, you can configure the domain type in
|
||||
[Atlas Schema HCL language](https://atlasgo.io/atlas-schema/hcl-types#domain):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value={"sql"} label={"Using SQL"}>
|
||||
|
||||
```sql title="schema.sql"
|
||||
CREATE DOMAIN us_postal_code AS TEXT
|
||||
CHECK(
|
||||
VALUE ~ '^\d{5}$'
|
||||
OR VALUE ~ '^\d{5}-\d{4}$'
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value={"hcl"} label={"Using HCL"}>
|
||||
|
||||
```hcl title="schema.hcl"
|
||||
schema "public" {}
|
||||
|
||||
domain "us_postal_code" {
|
||||
schema = schema.public
|
||||
type = text
|
||||
null = true
|
||||
check "us_postal_code_check" {
|
||||
expr = "((VALUE ~ '^\\d{5}$'::text) OR (VALUE ~ '^\\d{5}-\\d{4}$'::text))"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
2\. In your Ent schema, define a field that uses the domain type only in PostgreSQL dialect:
|
||||
|
||||
```go title="ent/schema/user.go" {5-7}
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("postal_code").
|
||||
SchemaType(map[string]string{
|
||||
dialect.Postgres: "us_postal_code",
|
||||
}),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
In case a schema with custom driver-specific types is used with other databases, Ent falls back to the default type
|
||||
used by the driver (e.g., "varchar").
|
||||
:::
|
||||
|
||||
3\. Create a simple `atlas.hcl` config file with a `composite_schema` that includes both your custom types defined in
|
||||
`schema.sql` and your Ent schema:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load first custom types first.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
# Second, load the Ent schema.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our schema, we can get its representation using the `atlas schema inspect` command, generate migrations for
|
||||
it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL. Note, the `us_postal_code` domain type is defined in the schema before
|
||||
its usage in the `postal_code` field:
|
||||
|
||||
```sql
|
||||
-- Create domain type "us_postal_code"
|
||||
CREATE DOMAIN "us_postal_code" AS text CONSTRAINT "us_postal_code_check" CHECK ((VALUE ~ '^\d{5}$'::text) OR (VALUE ~ '^\d{5}-\d{4}$'::text));
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "postal_code" "us_postal_code" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create domain type "us_postal_code"
|
||||
CREATE DOMAIN "us_postal_code" AS text CONSTRAINT "us_postal_code_check" CHECK ((VALUE ~ '^\d{5}$'::text) OR (VALUE ~ '^\d{5}-\d{4}$'::text));
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "postal_code" "us_postal_code" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/domaintypes).
|
||||
202
doc/md/migration/enum.mdx
Normal file
202
doc/md/migration/enum.mdx
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Using Postgres Enum Types in Ent Schema
|
||||
id: enum
|
||||
slug: enum-types
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
|
||||
Enum types are data structures that consist of a predefined, ordered set of values. By default, when using `field.Enum`
|
||||
in your Ent schema, Ent uses simple string types to represent the enum values in **PostgreSQL and SQLite**. However, in some
|
||||
cases, you may want to use the native enum types provided by the database.
|
||||
|
||||
This guide explains how to define a schema field that uses a native PostgreSQL enum type and configure the schema migration
|
||||
to manage both Postgres enums and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema) used in this
|
||||
guide is available exclusively to Pro users. To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. External enum types,
|
||||
or any other database objects do not have representation in Ent models - A Postgres enum type can be defined once in your Postgres
|
||||
schema, and may be used multiple times in different fields and models.
|
||||
|
||||
In order to extend our PostgreSQL schema to include both custom enum types and our Ent types, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Create a `schema.sql` that defines the necessary enum type there. In the same way, you can define the enum type in
|
||||
[Atlas Schema HCL language](https://atlasgo.io/atlas-schema/hcl-types#enum):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value={"sql"} label={"Using SQL"}>
|
||||
|
||||
```sql title="schema.sql"
|
||||
CREATE TYPE status AS ENUM ('active', 'inactive', 'pending');
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value={"hcl"} label={"Using HCL"}>
|
||||
|
||||
```hcl title="schema.hcl"
|
||||
schema "public" {}
|
||||
|
||||
enum "status" {
|
||||
schema = schema.public
|
||||
values = ["active", "inactive", "pending"]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
2\. In your Ent schema, define an enum field that uses the underlying Postgres `ENUM` type:
|
||||
|
||||
```go title="ent/schema/user.go" {6-8}
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Enum("status").
|
||||
Values("active", "inactive", "pending").
|
||||
SchemaType(map[string]string{
|
||||
dialect.Postgres: "status",
|
||||
}),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
In case a schema with custom driver-specific types is used with other databases, Ent falls back to the default type
|
||||
used by the driver (e.g., `TEXT` in SQLite and `ENUM (...)` in MariaDB or MySQL)s.
|
||||
:::
|
||||
|
||||
3\. Create a simple `atlas.hcl` config file with a `composite_schema` that includes both your custom enum types defined in
|
||||
`schema.sql` and your Ent schema:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load first custom types first.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
# Second, load the Ent schema.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our composite schema, we can get its representation using the `atlas schema inspect` command, generate
|
||||
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL. Note, the `status` enum type is defined in the schema before
|
||||
its usage in the `users.status` column:
|
||||
|
||||
```sql
|
||||
-- Create enum type "status"
|
||||
CREATE TYPE "status" AS ENUM ('active', 'inactive', 'pending');
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "status" "status" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create enum type "status"
|
||||
CREATE TYPE "status" AS ENUM ('active', 'inactive', 'pending');
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "status" "status" NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/enumtypes).
|
||||
223
doc/md/migration/extension.mdx
Normal file
223
doc/md/migration/extension.mdx
Normal file
@@ -0,0 +1,223 @@
|
||||
---
|
||||
title: Using Postgres Extensions in Ent Schema
|
||||
id: extension
|
||||
slug: extensions
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
|
||||
[Postgres extensions](https://www.postgresql.org/docs/current/sql-createextension.html) are add-on modules that extend
|
||||
the functionality of the database by providing new data types, operators, functions, procedural languages, and more.
|
||||
|
||||
This guide explains how to define a schema field that uses a data type provided by the PostGIS extension, and configure
|
||||
the schema migration to manage both Postgres extension installations and the Ent schema as a single migration unit using
|
||||
Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Extensions](https://atlasgo.io/atlas-schema/hcl#extension) is available exclusively to Pro users.
|
||||
To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Extensions like
|
||||
`postgis` or `hstore` do not have representation in Ent schema. A Postgres extension can be installed once in your
|
||||
Postgres database, and may be used multiple times in different schemas.
|
||||
|
||||
In order to extend our PostgreSQL schema migration to include both extensions and our Ent types, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Create a `schema.sql` that defines the necessary extensions used by your database. In the same way, you can define
|
||||
the extensions in [Atlas Schema HCL language](https://atlasgo.io/atlas-schema/hcl-types#extension):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value={"sql"} label={"Using SQL"}>
|
||||
|
||||
```sql title="schema.sql"
|
||||
-- Install PostGIS extension.
|
||||
CREATE EXTENSION postgis;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value={"hcl"} label={"Using HCL"}>
|
||||
|
||||
```hcl title="schema.hcl"
|
||||
schema "public" {}
|
||||
|
||||
extension "postgis" {
|
||||
schema = schema.public
|
||||
version = "3.4.2"
|
||||
comment = "PostGIS geometry and geography spatial types and functions"
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
2\. In your Ent schema, define a field that uses the data type provided by the extension. In this example, we use the
|
||||
`GEOMETRY(Point, 4326)` data type provided by the `postgis` extension:
|
||||
|
||||
```go title="ent/schema/user.go" {7-9}
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Bytes("location").
|
||||
// Ideally, we would use a custom GoType
|
||||
// to represent the "geometry" type.
|
||||
SchemaType(map[string]string{
|
||||
dialect.Postgres: "GEOMETRY(Point, 4326)",
|
||||
}),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3\. Create a simple `atlas.hcl` config file with a `composite_schema` that includes both the extensions defined in
|
||||
`schema.sql` and your Ent schema:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Install extensions first (PostGIS).
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
# Then, load the Ent schema.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgis/latest/dev"
|
||||
format {
|
||||
migrate {
|
||||
diff = "{{ sql . \" \" }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our composite schema, we can get its representation using the `atlas schema inspect` command, generate
|
||||
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL.
|
||||
|
||||
```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 extension "postgis"
|
||||
CREATE EXTENSION "postgis" WITH SCHEMA "public" VERSION "3.4.2";
|
||||
-- Create "users" table
|
||||
CREATE TABLE "public"."users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "location" public.geometry(point,4326) NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
:::info Extensions Are Database-Level Objects
|
||||
Although the `SCHEMA` argument is supported by the `CREATE EXTENSION` command, it only indicates where the extension's
|
||||
objects will be installed. The extension itself is installed at the database level and cannot be loaded multiple times
|
||||
into different schemas.
|
||||
|
||||
Therefore, to avoid conflicts with other schemas, when working with extensions, the scope of the migration should be set
|
||||
to the database, where objects are qualified with the schema name. Hence, the `search_path` is dropped from the dev-database
|
||||
URL in the `atlas.hcl` file.
|
||||
:::
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create extension "postgis"
|
||||
CREATE EXTENSION "postgis" WITH SCHEMA "public" VERSION "3.4.2";
|
||||
-- Create "users" table
|
||||
CREATE TABLE "public"."users" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"location" public.geometry(point,4326) NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/enumtypes).
|
||||
200
doc/md/migration/functional-indexes.mdx
Normal file
200
doc/md/migration/functional-indexes.mdx
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
title: Using Functional Indexes in Ent Schema
|
||||
id: functional-indexes
|
||||
slug: functional-indexes
|
||||
---
|
||||
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
A functional index is an index whose key parts are based on expression values, rather than column values. This index
|
||||
type is helpful for indexing the results of functions or expressions that are not stored in the table. Supported by
|
||||
[MySQL, MariaDB](https://atlasgo.io/guides/mysql/functional-indexes), [PostgreSQL](https://atlasgo.io/guides/postgres/functional-indexes)
|
||||
and [SQLite](https://atlasgo.io/guides/sqlite/functional-indexes).
|
||||
|
||||
This guide explains how to extend your Ent schema with functional indexes, and configure the schema migration to manage
|
||||
both functional indexes and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema) used in this
|
||||
guide is available exclusively to Pro users. To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Functional indexes,
|
||||
do not have representation in Ent schema, as Ent supports defining indexes on fields, edges (foreign-keys), and the combination
|
||||
of them.
|
||||
|
||||
In order to extend our PostgreSQL schema migration with functional indexes to our Ent types (tables), we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Let's define a simple schema with one type (table): `User` (table `users`):
|
||||
|
||||
```go title="ent/schema/user.go"
|
||||
// User holds the schema definition for the User entity.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name").
|
||||
Comment("A unique index is defined on lower(name) in schema.sql"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2\. Next step, we define a functional index on the `name` field in the `schema.sql` file:
|
||||
|
||||
```sql title="schema.sql" {2}
|
||||
-- Create a functional (unique) index on the lowercased name column.
|
||||
CREATE UNIQUE INDEX unique_name ON "users" ((lower("name")));
|
||||
```
|
||||
|
||||
3\. Create a simple `atlas.hcl` config file with a `composite_schema` that includes both the functional indexes defined in
|
||||
`schema.sql` and your Ent schema:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load the ent schema first with all tables.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
# Then, load the functional indexes.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our composite schema, we can get its representation using the `atlas schema inspect` command, generate
|
||||
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL.
|
||||
|
||||
```sql
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Create index "unique_name" to table: "users"
|
||||
CREATE UNIQUE INDEX "unique_name" ON "users" ((lower((name)::text)));
|
||||
```
|
||||
|
||||
Note, our functional index is defined on the `name` field in the `users` table.
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Create index "unique_name" to table: "users"
|
||||
CREATE UNIQUE INDEX "unique_name" ON "users" ((lower((name)::text)));
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Code Example
|
||||
|
||||
After setting up our Ent schema with functional indexes, we expect the database to enforce the uniqueness of the `name`
|
||||
field in the `users` table:
|
||||
|
||||
```go
|
||||
// Test that the unique index is enforced.
|
||||
client.User.Create().SetName("Ariel").SaveX(ctx)
|
||||
err = client.User.Create().SetName("ariel").Exec(ctx)
|
||||
require.EqualError(t, err, `ent: constraint failed: pq: duplicate key value violates unique constraint "unique_name"`)
|
||||
|
||||
// Type-assert returned error.
|
||||
var pqerr *pq.Error
|
||||
require.True(t, errors.As(err, &pqerr))
|
||||
require.Equal(t, `duplicate key value violates unique constraint "unique_name"`, pqerr.Message)
|
||||
require.Equal(t, user.Table, pqerr.Table)
|
||||
require.Equal(t, "unique_name", pqerr.Constraint)
|
||||
require.Equal(t, pq.ErrorCode("23505"), pqerr.Code, "unique violation")
|
||||
```
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/functionalidx).
|
||||
227
doc/md/migration/rls.mdx
Normal file
227
doc/md/migration/rls.mdx
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: Using Row-Level Security in Ent Schema
|
||||
id: rls
|
||||
slug: row-level-security
|
||||
---
|
||||
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
Row-level security (RLS) in PostgreSQL enables tables to implement policies that limit access or modification of rows
|
||||
according to the user's role, enhancing the basic SQL-standard privileges provided by `GRANT`.
|
||||
|
||||
Once activated, every standard access to the table has to adhere to these policies. If no policies are defined on the table,
|
||||
it defaults to a deny-all rule, meaning no rows can be seen or mutated. These policies can be tailored to specific commands,
|
||||
roles, or both, allowing for detailed management of who can access or change data.
|
||||
|
||||
This guide explains how to attach Row-Level Security (RLS) Policies to your Ent types (objects) and configure the schema
|
||||
migration to manage both the RLS and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
|
||||
Atlas support for [Row-Level Security Policies](https://atlasgo.io/atlas-schema/hcl#row-level-security-policy) used in
|
||||
this guide is available exclusively to Pro users. To use this feature, run:
|
||||
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Table policies
|
||||
or any other database native objects do not have representation in Ent models.
|
||||
|
||||
In order to extend our PostgreSQL schema to include both our Ent types and their policies, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Let's define a simple schema with two types (tables): `users` and `tenants`:
|
||||
|
||||
```go title="ent/schema/tenant.go"
|
||||
// Tenant holds the schema definition for the Tenant entity.
|
||||
type Tenant struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the Tenant.
|
||||
func (Tenant) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// User holds the schema definition for the User entity.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name"),
|
||||
field.Int("tenant_id"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2\. Now, suppose we want to limit access to the `users` table based on the `tenant_id` field. We can achieve this by defining
|
||||
a Row-Level Security (RLS) policy on the `users` table. Below is the SQL code that defines the RLS policy:
|
||||
|
||||
```sql title="schema.sql"
|
||||
--- Enable row-level security on the users table.
|
||||
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Create a policy that restricts access to rows in the users table based on the current tenant.
|
||||
CREATE POLICY tenant_isolation ON "users"
|
||||
USING ("tenant_id" = current_setting('app.current_tenant')::integer);
|
||||
```
|
||||
|
||||
|
||||
3\. Lastly, we create a simple `atlas.hcl` config file with a `composite_schema` that includes both our Ent schema and
|
||||
the custom security policies defined in `schema.sql`:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load the ent schema first with all tables.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
# Then, load the RLS schema.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our composite schema, we can get its representation using the `atlas schema inspect` command, generate
|
||||
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL. Note, the `tenant_isolation` policy is defined in the schema after the `users`
|
||||
table:
|
||||
|
||||
```sql
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, "tenant_id" bigint NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Enable row-level security for "users" table
|
||||
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
|
||||
-- Create policy "tenant_isolation"
|
||||
CREATE POLICY "tenant_isolation" ON "users" AS PERMISSIVE FOR ALL TO PUBLIC USING (tenant_id = (current_setting('app.current_tenant'::text))::integer);
|
||||
-- Create "tenants" table
|
||||
CREATE TABLE "tenants" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, "tenant_id" bigint NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Enable row-level security for "users" table
|
||||
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
|
||||
-- Create policy "tenant_isolation"
|
||||
CREATE POLICY "tenant_isolation" ON "users" AS PERMISSIVE FOR ALL TO PUBLIC USING (tenant_id = (current_setting('app.current_tenant'::text))::integer);
|
||||
-- Create "tenants" table
|
||||
CREATE TABLE "tenants" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Code Example
|
||||
|
||||
After setting up our Ent schema and the RLS policies, we can open an Ent client and pass the different mutations and
|
||||
queries the relevant tenant ID we work on. This ensures that the database upholds our RLS policy:
|
||||
|
||||
```go
|
||||
ctx1, ctx2 := sql.WithIntVar(ctx, "app.current_tenant", a8m.ID), sql.WithIntVar(ctx, "app.current_tenant", r3m.ID)
|
||||
users1 := client.User.Query().AllX(ctx1)
|
||||
// Users1 can only see users from tenant a8m.
|
||||
users2 := client.User.Query().AllX(ctx2)
|
||||
// Users2 can only see users from tenant r3m.
|
||||
```
|
||||
|
||||
:::info Real World Example
|
||||
In real applications, users can utilize [hooks](/docs/hooks) and [interceptors](/docs/interceptors) to set the `app.current_tenant`
|
||||
variable based on the user's context.
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/rls).
|
||||
277
doc/md/migration/trigger.mdx
Normal file
277
doc/md/migration/trigger.mdx
Normal file
@@ -0,0 +1,277 @@
|
||||
---
|
||||
title: Using Database Triggers in Ent Schema
|
||||
id: trigger
|
||||
slug: triggers
|
||||
---
|
||||
|
||||
import InstallationInstructions from '../components/_installation_instructions.mdx';
|
||||
|
||||
Triggers are useful tools in relational databases that allow you to execute custom code when specific events occur on a
|
||||
table. For instance, triggers can automatically populate the audit log table whenever a new mutation is applied to a different table.
|
||||
This way we ensure that all changes (including those made by other applications) are meticulously recorded, enabling the enforcement
|
||||
on the database-level and reducing the need for additional code in the applications.
|
||||
|
||||
This guide explains how to attach triggers to your Ent types (objects) and configure the schema migration to manage
|
||||
both the triggers and the Ent schema as a single migration unit using Atlas.
|
||||
|
||||
:::info [Atlas Pro Feature](https://atlasgo.io/features#pro-plan)
|
||||
Atlas support for [Triggers](https://atlasgo.io/atlas-schema/hcl#trigger) used in this guide is available exclusively
|
||||
to Pro users. To use this feature, run:
|
||||
```
|
||||
atlas login
|
||||
```
|
||||
:::
|
||||
|
||||
## Install Atlas
|
||||
|
||||
<InstallationInstructions />
|
||||
|
||||
## Login to Atlas
|
||||
|
||||
```shell
|
||||
$ atlas login a8m
|
||||
//highlight-next-line-info
|
||||
You are now connected to "a8m" on Atlas Cloud.
|
||||
```
|
||||
|
||||
## Composite Schema
|
||||
|
||||
An `ent/schema` package is mostly used for defining Ent types (objects), their fields, edges and logic. Table triggers
|
||||
or any other database native objects do not have representation in Ent models. A trigger function can be defined once,
|
||||
and used in multiple triggers in different tables.
|
||||
|
||||
In order to extend our PostgreSQL schema to include both our Ent types and their triggers, we configure Atlas to
|
||||
read the state of the schema from a [Composite Schema](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema)
|
||||
data source. Follow the steps below to configure this for your project:
|
||||
|
||||
1\. Let's define a simple schema with two types (tables): `users` and `user_audit_logs`:
|
||||
|
||||
```go title="ent/schema/user.go"
|
||||
// User holds the schema definition for the User entity.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the User.
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// UserAuditLog holds the schema definition for the UserAuditLog entity.
|
||||
type UserAuditLog struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Fields of the UserAuditLog.
|
||||
func (UserAuditLog) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("operation_type"),
|
||||
field.String("operation_time"),
|
||||
field.String("old_value").
|
||||
Optional(),
|
||||
field.String("new_value").
|
||||
Optional(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, suppose we want to log every change to the `users` table and save it in the `user_audit_logs` table.
|
||||
To achieve this, we need to create a trigger function on `INSERT`, `UPDATE` and `DELETE` operations and attach it to
|
||||
the `users` table.
|
||||
|
||||
2\. Next step, we define a trigger function ( `audit_users_changes`) and attach it to the `users` table using the `CREATE TRIGGER` commands:
|
||||
|
||||
```sql title="schema.sql" {23,26,29}
|
||||
-- Function to audit changes in the users table.
|
||||
CREATE OR REPLACE FUNCTION audit_users_changes()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD), row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD));
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger for INSERT operations.
|
||||
CREATE TRIGGER users_insert_audit AFTER INSERT ON users FOR EACH ROW EXECUTE FUNCTION audit_users_changes();
|
||||
|
||||
-- Trigger for UPDATE operations.
|
||||
CREATE TRIGGER users_update_audit AFTER UPDATE ON users FOR EACH ROW EXECUTE FUNCTION audit_users_changes();
|
||||
|
||||
-- Trigger for DELETE operations.
|
||||
CREATE TRIGGER users_delete_audit AFTER DELETE ON users FOR EACH ROW EXECUTE FUNCTION audit_users_changes();
|
||||
```
|
||||
|
||||
|
||||
3\. Lastly, we create a simple `atlas.hcl` config file with a `composite_schema` that includes both our Ent schema and
|
||||
the custom triggers defined in `schema.sql`:
|
||||
|
||||
```hcl title="atlas.hcl"
|
||||
data "composite_schema" "app" {
|
||||
# Load the ent schema first with all tables.
|
||||
schema "public" {
|
||||
url = "ent://ent/schema"
|
||||
}
|
||||
# Then, load the triggers schema.
|
||||
schema "public" {
|
||||
url = "file://schema.sql"
|
||||
}
|
||||
}
|
||||
|
||||
env "local" {
|
||||
src = data.composite_schema.app.url
|
||||
dev = "docker://postgres/15/dev?search_path=public"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After setting up our composite schema, we can get its representation using the `atlas schema inspect` command, generate
|
||||
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
|
||||
|
||||
#### Inspect the Schema
|
||||
|
||||
The `atlas schema inspect` command is commonly used to inspect databases. However, we can also use it to inspect our
|
||||
`composite_schema` and print the SQL representation of it:
|
||||
|
||||
```shell
|
||||
atlas schema inspect \
|
||||
--env local \
|
||||
--url env://src \
|
||||
--format '{{ sql . }}'
|
||||
```
|
||||
|
||||
The command above prints the following SQL. Note, the `audit_users_changes` function and the triggers are defined after
|
||||
the `users` and `user_audit_logs` tables:
|
||||
|
||||
```sql
|
||||
-- Create "user_audit_logs" table
|
||||
CREATE TABLE "user_audit_logs" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "operation_type" character varying NOT NULL, "operation_time" character varying NOT NULL, "old_value" character varying NULL, "new_value" character varying NULL, PRIMARY KEY ("id"));
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Create "audit_users_changes" function
|
||||
CREATE FUNCTION "audit_users_changes" () RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD), row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD));
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
-- Create trigger "users_delete_audit"
|
||||
CREATE TRIGGER "users_delete_audit" AFTER DELETE ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
-- Create trigger "users_insert_audit"
|
||||
CREATE TRIGGER "users_insert_audit" AFTER INSERT ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
-- Create trigger "users_update_audit"
|
||||
CREATE TRIGGER "users_update_audit" AFTER UPDATE ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
```
|
||||
|
||||
#### Generate Migrations For the Schema
|
||||
|
||||
To generate a migration for the schema, run the following command:
|
||||
|
||||
```shell
|
||||
atlas migrate diff \
|
||||
--env local
|
||||
```
|
||||
|
||||
Note that a new migration file is created with the following content:
|
||||
|
||||
```sql title="migrations/20240712090543.sql"
|
||||
-- Create "user_audit_logs" table
|
||||
CREATE TABLE "user_audit_logs" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "operation_type" character varying NOT NULL, "operation_time" character varying NOT NULL, "old_value" character varying NULL, "new_value" character varying NULL, PRIMARY KEY ("id"));
|
||||
-- Create "users" table
|
||||
CREATE TABLE "users" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" character varying NOT NULL, PRIMARY KEY ("id"));
|
||||
-- Create "audit_users_changes" function
|
||||
CREATE FUNCTION "audit_users_changes" () RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value, new_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD), row_to_json(NEW));
|
||||
RETURN NEW;
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
INSERT INTO user_audit_logs(operation_type, operation_time, old_value)
|
||||
VALUES (TG_OP, CURRENT_TIMESTAMP, row_to_json(OLD));
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
-- Create trigger "users_delete_audit"
|
||||
CREATE TRIGGER "users_delete_audit" AFTER DELETE ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
-- Create trigger "users_insert_audit"
|
||||
CREATE TRIGGER "users_insert_audit" AFTER INSERT ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
-- Create trigger "users_update_audit"
|
||||
CREATE TRIGGER "users_update_audit" AFTER UPDATE ON "users" FOR EACH ROW EXECUTE FUNCTION "audit_users_changes"();
|
||||
```
|
||||
|
||||
#### Apply the Migrations
|
||||
|
||||
To apply the migration generated above to a database, run the following command:
|
||||
|
||||
```
|
||||
atlas migrate apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
:::info Apply the Schema Directly on the Database
|
||||
|
||||
Sometimes, there is a need to apply the schema directly to the database without generating a migration file. For example,
|
||||
when experimenting with schema changes, spinning up a database for testing, etc. In such cases, you can use the command
|
||||
below to apply the schema directly to the database:
|
||||
|
||||
```shell
|
||||
atlas schema apply \
|
||||
--env local \
|
||||
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
|
||||
```
|
||||
|
||||
Or, using the [Atlas Go SDK](https://github.com/ariga/atlas-go-sdk):
|
||||
|
||||
```go
|
||||
ac, err := atlasexec.NewClient(".", "atlas")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize client: %w", err)
|
||||
}
|
||||
// Automatically update the database with the desired schema.
|
||||
// Another option, is to use 'migrate apply' or 'schema apply' manually.
|
||||
if _, err := ac.SchemaApply(ctx, &atlasexec.SchemaApplyParams{
|
||||
Env: "local",
|
||||
URL: "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable",
|
||||
AutoApprove: true,
|
||||
}); err != nil {
|
||||
log.Fatalf("failed to apply schema changes: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The code for this guide can be found in [GitHub](https://github.com/ent/ent/tree/master/examples/triggers).
|
||||
Reference in New Issue
Block a user