Replace Flyway schema setup with JPA mappings

This commit is contained in:
Lucio Lelii 2026-09-09 12:20:36 +02:00
parent e71ba9c290
commit 8d6ddfecfb
19 changed files with 54 additions and 188 deletions

View File

@ -103,14 +103,6 @@
<artifactId>postgresql</artifactId>
<version>42.7.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -39,7 +39,6 @@ until docker exec "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POST
sleep 1
done
# Il database è usa-e-getta: crea lo schema dalle entity e non applica le
# migration Flyway, che richiedono uno schema base già esistente.
# Il database è usa-e-getta: crea lo schema direttamente dalle entity.
./mvnw spring-boot:run \
-Dspring-boot.run.jvmArguments="-Dddl-auto=create-drop -Dspring.flyway.enabled=false"
-Dspring-boot.run.jvmArguments="-Dddl-auto=create-drop"

View File

@ -18,6 +18,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
@Entity
@Table(name = "bias_impact_job_entity", indexes = {
@ -44,11 +45,13 @@ public class BiasImpactJobEntity {
private String stepId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private BiasImpactJobKind kind;
@Column(nullable = false, length = 32)
@ColumnDefault("'ISOLATED_STEP'")
@Builder.Default
private BiasImpactJobKind kind = BiasImpactJobKind.ISOLATED_STEP;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Column(nullable = false, length = 32)
private BiasImpactJobStatus status;
@Column(name = "created_at", nullable = false)

View File

@ -17,6 +17,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
@Entity
@Table(name = "bias_impact_report_entity", indexes = {
@ -35,6 +36,7 @@ public class BiasImpactReportEntity {
private String id;
@NotBlank
@Column(nullable = false)
private String owner;
@NotBlank
@ -48,8 +50,10 @@ public class BiasImpactReportEntity {
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Builder.Default
@Column(name = "raw_outputs_included", nullable = false)
private boolean rawOutputsIncluded;
@ColumnDefault("true")
private boolean rawOutputsIncluded = true;
@Column(name = "report_data", columnDefinition = "TEXT")
@Convert(converter = BiasImpactReportConverter.class)
@ -66,5 +70,6 @@ public class BiasImpactReportEntity {
*/
@Version
@Column(name = "version", nullable = false)
@ColumnDefault("0")
private long version;
}

View File

@ -11,14 +11,29 @@ import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
@Table(indexes = {
@Index(name = "idx_execution_owner_kind", columnList = "owner, execution_kind"),
@Index(name = "idx_execution_parent", columnList = "parent_execution_id, parent_step_id, parent_iteration_index"),
@Index(name = "idx_execution_project_run", columnList = "owner, project_run_id"),
@Index(name = "idx_execution_owner_project", columnList = "owner, project_id")
})
@Data
@Builder
@NoArgsConstructor
@ -60,10 +75,22 @@ public class ExecutionEntity {
@Builder.Default
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 32)
@ColumnDefault("'TOP_LEVEL'")
private ExecutionKind executionKind = ExecutionKind.TOP_LEVEL;
@Column(name = "parent_execution_id")
private String parentExecutionId;
/**
* Schema-only association for the self-referencing foreign key. The service continues to use
* {@link #parentExecutionId}, so this association is deliberately read-only.
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_execution_id", insertable = false, updatable = false,
foreignKey = @ForeignKey(name = "fk_execution_parent"))
@OnDelete(action = OnDeleteAction.CASCADE)
private ExecutionEntity parentExecution;
private String parentStepId;
private Integer parentIterationIndex;

View File

@ -10,6 +10,8 @@ import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
@ -18,6 +20,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(indexes = @Index(name = "idx_flow_owner_project", columnList = "owner, project_id"))
@NoArgsConstructor
@AllArgsConstructor
@Data

View File

@ -10,6 +10,7 @@ import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
@ -19,7 +20,7 @@ import lombok.NoArgsConstructor;
@Entity
@Table(name = "project", uniqueConstraints = @UniqueConstraint(name = "uk_project_owner_name",
columnNames = { "owner", "name" }))
columnNames = { "owner", "name" }), indexes = @Index(name = "idx_project_owner", columnList = "owner"))
@Data
@Builder
@NoArgsConstructor

View File

@ -7,16 +7,19 @@ import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
@Entity
@Table(name = "user_secret", uniqueConstraints = @UniqueConstraint(name = "uk_user_secret_owner_label",
columnNames = { "owner", "label" }))
columnNames = { "owner", "label" }), indexes = @Index(name = "idx_user_secret_owner_provider_active",
columnList = "owner, provider, active"))
@Data
@Builder
@NoArgsConstructor
@ -30,10 +33,10 @@ public class UserSecretEntity {
@Column(nullable = false)
private String owner;
@Column(nullable = false)
@Column(nullable = false, length = 120)
private String label;
@Column(nullable = false)
@Column(nullable = false, length = 120)
private String provider;
@Column(length = 1000)
@ -45,7 +48,7 @@ public class UserSecretEntity {
@Column(nullable = false)
private String iv;
@Column(nullable = false)
@Column(nullable = false, length = 64)
private String algorithm;
@Column(nullable = false)
@ -58,5 +61,6 @@ public class UserSecretEntity {
@Builder.Default
@Column(nullable = false)
@ColumnDefault("true")
private boolean active = true;
}

View File

@ -7,8 +7,6 @@ spring.datasource.password=${DB_PASSWORD:password}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=${ddl-auto:validate}
spring.flyway.baseline-on-migrate=${FLYWAY_BASELINE_ON_MIGRATE:true}
spring.flyway.baseline-version=${FLYWAY_BASELINE_VERSION:1}
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

View File

@ -1,6 +0,0 @@
-- Appending an LLM assessment to a report is a read-modify-write over its JSON, and the model calls
-- in the middle take minutes. Two overlapping assessments both read a report with no history, and
-- whichever saved last kept only its own: the other was silently gone. A version column is what lets
-- the second writer be refused and retried against the row as it actually is.
ALTER TABLE bias_impact_report_entity
ADD COLUMN version BIGINT NOT NULL DEFAULT 0;

View File

@ -1,14 +0,0 @@
CREATE TABLE bias_impact_report_entity (
id VARCHAR(255) PRIMARY KEY,
owner VARCHAR(255) NOT NULL,
baseline_execution_id VARCHAR(255) NOT NULL,
biased_execution_id VARCHAR(255),
created_at TIMESTAMP(6) NOT NULL,
report_data TEXT
);
CREATE INDEX idx_bias_report_baseline_owner_created
ON bias_impact_report_entity (baseline_execution_id, owner, created_at);
CREATE INDEX idx_bias_report_biased_execution
ON bias_impact_report_entity (biased_execution_id);

View File

@ -1,43 +0,0 @@
ALTER TABLE bias_impact_report_entity
ADD COLUMN raw_outputs_included BOOLEAN NOT NULL DEFAULT TRUE;
DELETE FROM bias_impact_report_entity
WHERE biased_execution_id IS NOT NULL
AND id IN (
SELECT id
FROM (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY baseline_execution_id, biased_execution_id, owner, raw_outputs_included
ORDER BY created_at DESC, id DESC
) AS duplicate_number
FROM bias_impact_report_entity
WHERE biased_execution_id IS NOT NULL
) ranked_reports
WHERE duplicate_number > 1
);
ALTER TABLE bias_impact_report_entity
ADD CONSTRAINT uk_bias_full_comparison
UNIQUE (baseline_execution_id, biased_execution_id, owner, raw_outputs_included);
CREATE TABLE bias_impact_job_entity (
id VARCHAR(255) PRIMARY KEY,
owner VARCHAR(255) NOT NULL,
execution_id VARCHAR(255) NOT NULL,
step_id VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
started_at TIMESTAMP(6),
completed_at TIMESTAMP(6),
report_id VARCHAR(255),
error_code VARCHAR(255),
error_message TEXT,
request_data TEXT NOT NULL
);
CREATE INDEX idx_bias_job_owner_created
ON bias_impact_job_entity (owner, created_at);
CREATE INDEX idx_bias_job_status
ON bias_impact_job_entity (status);

View File

@ -1,26 +0,0 @@
ALTER TABLE execution_entity
ADD COLUMN execution_kind VARCHAR(32) NOT NULL DEFAULT 'TOP_LEVEL';
ALTER TABLE execution_entity
ADD COLUMN parent_execution_id VARCHAR(255);
ALTER TABLE execution_entity
ADD COLUMN parent_step_id VARCHAR(255);
ALTER TABLE execution_entity
ADD COLUMN parent_iteration_index INTEGER;
ALTER TABLE execution_entity
ADD COLUMN subflow_role VARCHAR(32);
ALTER TABLE execution_entity
ADD CONSTRAINT fk_execution_parent
FOREIGN KEY (parent_execution_id)
REFERENCES execution_entity (id)
ON DELETE CASCADE;
CREATE INDEX idx_execution_owner_kind
ON execution_entity (owner, execution_kind);
CREATE INDEX idx_execution_parent
ON execution_entity (parent_execution_id, parent_step_id, parent_iteration_index);

View File

@ -1,18 +0,0 @@
CREATE TABLE user_secret (
id VARCHAR(255) PRIMARY KEY,
owner VARCHAR(255) NOT NULL,
label VARCHAR(120) NOT NULL,
provider VARCHAR(120) NOT NULL,
description VARCHAR(1000),
ciphertext TEXT NOT NULL,
iv VARCHAR(255) NOT NULL,
algorithm VARCHAR(64) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
updated_at TIMESTAMP(6) NOT NULL,
last_used_at TIMESTAMP(6),
active BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT uk_user_secret_owner_label UNIQUE (owner, label)
);
CREATE INDEX idx_user_secret_owner_provider_active
ON user_secret (owner, provider, active);

View File

@ -1,25 +0,0 @@
CREATE TABLE project (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(1000),
owner VARCHAR(255) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
last_update_at TIMESTAMP(6) NOT NULL,
shared_context TEXT,
CONSTRAINT uk_project_owner_name UNIQUE (owner, name)
);
CREATE INDEX idx_project_owner ON project (owner);
-- No foreign key on project_id, on purpose. The tests run on H2 with ddl-auto=create-drop and
-- spring.flyway.enabled=false, and Hibernate's validate does not check foreign keys, so a
-- constraint added here would exist only in production and never be exercised anywhere. The
-- cascade is enforced in ProjectService, which deletes a project's flows before the project, and a
-- dangling project_id degrades safely: the project name simply resolves to null.
-- project_order is unused for now: it is the stable display order a project run will need, and a
-- nullable column costs nothing today while sparing an ALTER on flow_entity later.
ALTER TABLE flow_entity ADD COLUMN project_id VARCHAR(255);
ALTER TABLE flow_entity ADD COLUMN project_order INTEGER;
CREATE INDEX idx_flow_owner_project ON flow_entity (owner, project_id);

View File

@ -1,11 +0,0 @@
-- Executions carry the project their source flow belonged to, plus the run that started them.
--
-- project_run_id is a new column rather than a reuse of run_group_id: that one means "the rerun
-- history of one flow" (resolveHistoryGroupId keys off source_flow_id first, and run numbering
-- depends on it), so overloading it would both fail to group a project run and corrupt per-flow
-- rerun numbering.
ALTER TABLE execution_entity ADD COLUMN project_id VARCHAR(255);
ALTER TABLE execution_entity ADD COLUMN project_run_id VARCHAR(255);
CREATE INDEX idx_execution_project_run ON execution_entity (owner, project_run_id);
CREATE INDEX idx_execution_owner_project ON execution_entity (owner, project_id);

View File

@ -1,4 +0,0 @@
-- Position of an execution inside its project run. A project run starts one flow at a time in this
-- order, so the sequence has to survive a restart: it is the run's only state, deliberately, rather
-- than a separate run table with a status machine to keep in sync with the executions themselves.
ALTER TABLE execution_entity ADD COLUMN project_run_order INTEGER;

View File

@ -1,18 +0,0 @@
-- A bias job used to be one thing: re-run a step with the intervention active. Asking a model to
-- assess an existing comparison is the second, and it reuses the same queue, recovery and polling
-- rather than growing a parallel one - so the columns that only the first kind fills stop being
-- mandatory.
ALTER TABLE bias_impact_job_entity
ADD COLUMN kind VARCHAR(32) NOT NULL DEFAULT 'ISOLATED_STEP';
ALTER TABLE bias_impact_job_entity
ALTER COLUMN step_id DROP NOT NULL;
ALTER TABLE bias_impact_job_entity
ALTER COLUMN request_data DROP NOT NULL;
ALTER TABLE bias_impact_job_entity
ADD COLUMN report_target_id VARCHAR(255);
ALTER TABLE bias_impact_job_entity
ADD COLUMN judge_data TEXT;

View File

@ -5,7 +5,6 @@ spring.datasource.password=
# Use the native H2 dialect for schema generation while keeping MySQL compatibility mode.
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.flyway.enabled=false
spring.jpa.properties.jakarta.persistence.validation.mode=none
app.db.init.enabled=true
app.assistant.default-model=assistant-test-model