Uso de disparadores de base de datos en esquemas Ent
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Los disparadores (triggers) son herramientas útiles en bases de datos relacionales que permiten ejecutar código personalizado cuando ocurren eventos específicos en una tabla. Por ejemplo, los disparadores pueden poblar automáticamente una tabla de registro de auditoría cada vez que se aplica una nueva mutación a otra tabla. Así garantizamos que todos los cambios (incluidos los realizados por otras aplicaciones) queden meticulosamente registrados, permitiendo la aplicación a nivel de base de datos y reduciendo la necesidad de código adicional en las aplicaciones.
Esta guía explica cómo adjuntar disparadores a tus tipos Ent (objetos) y configurar la migración de esquemas para gestionar tanto los disparadores como el esquema Ent como una única unidad de migración usando Atlas.
El soporte de Atlas para Disparadores usado en esta guía está disponible exclusivamente para usuarios Pro. Para usar esta función, ejecuta:
atlas login
Instalar Atlas
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Para instalar la última versión de Atlas, simplemente ejecuta uno de los siguientes comandos en tu terminal, o visita el sitio web de Atlas:
- macOS + Linux
- Homebrew
- Docker
- Windows
curl -sSf https://atlasgo.sh | sh
brew install ariga/tap/atlas
docker pull arigaio/atlas
docker run --rm arigaio/atlas --help
If the container needs access to the host network or a local directory, use the --net=host flag and mount the desired
directory:
docker run --rm --net=host \
-v $(pwd)/migrations:/migrations \
arigaio/atlas migrate apply
--url "mysql://root:pass@:3306/test"
Download the latest release and move the atlas binary to a file location on your system PATH.
Iniciar sesión en Atlas
$ atlas login a8m
You are now connected to "a8m" on Atlas Cloud.
Esquema compuesto
El paquete ent/schema se usa principalmente para definir tipos Ent (objetos), sus campos, relaciones y lógica. Los disparadores de tabla u otros objetos nativos de la base de datos no tienen representación en los modelos Ent. Una función de disparador puede definirse una vez y usarse en múltiples disparadores en diferentes tablas.
Para extender nuestro esquema PostgreSQL e incluir tanto nuestros tipos Ent como sus disparadores, configuramos Atlas para leer el estado del esquema desde un origen de datos de Esquema Compuesto. Sigue estos pasos para configurarlo en tu proyecto:
1. Definamos un esquema simple con dos tipos (tablas): users y user_audit_logs:
// 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(),
}
}
Ahora, supongamos que queremos registrar cada cambio en la tabla users y guardarlo en la tabla user_audit_logs. Para lograrlo, necesitamos crear una función de disparador para operaciones INSERT, UPDATE y DELETE, y adjuntarla a la tabla users.
2. A continuación, definimos una función de disparador (audit_users_changes) y la adjuntamos a la tabla users usando los comandos CREATE TRIGGER:
-- 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. Por último, creamos un archivo de configuración atlas.hcl simple con un composite_schema que incluya tanto nuestro esquema Ent como los disparadores personalizados definidos en schema.sql:
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"
}
Uso
Tras configurar nuestro esquema compuesto, podemos obtener su representación usando el comando atlas schema inspect, generar migraciones de esquema, aplicarlas a una base de datos y más. Aquí tienes algunos comandos para comenzar con Atlas:
Inspeccionar el esquema
El comando atlas schema inspect se usa comúnmente para inspeccionar bases de datos. Sin embargo, también podemos usarlo para inspeccionar nuestro composite_schema e imprimir su representación SQL:
atlas schema inspect \
--env local \
--url env://src \
--format '{{ sql . }}'
El comando anterior imprime el siguiente SQL. Observa que la función audit_users_changes y los disparadores se definen después de las tablas users y user_audit_logs:
-- 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"();
Generar migraciones para el esquema
Para generar una migración para el esquema, ejecuta el siguiente comando:
atlas migrate diff \
--env local
Ten en cuenta que se crea un nuevo archivo de migración con el siguiente contenido:
-- 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"();
Aplicar las migraciones
Para aplicar la migración generada a una base de datos, ejecuta el siguiente comando:
atlas migrate apply \
--env local \
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
En ocasiones, puede ser necesario aplicar el esquema directamente a la base de datos sin generar un archivo de migración. Por ejemplo, al experimentar con cambios en el esquema, al preparar una base de datos para pruebas, etc. En estos casos, puedes usar el siguiente comando para aplicar el esquema directamente a la base de datos:
atlas schema apply \
--env local \
--url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
O bien, usando el SDK de Go de Atlas:
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)
}
El código de esta guía se puede encontrar en GitHub.