mirror of
https://github.com/ent/ent.git
synced 2026-05-05 00:50:54 +03:00
199 lines
6.6 KiB
Plaintext
199 lines
6.6 KiB
Plaintext
---
|
|
title: Using Functional Indexes in Ent Schema
|
|
id: function-indexes
|
|
slug: function-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",
|
|
}); 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). |