diff --git a/pom.xml b/pom.xml index bb567dc..73fcb3f 100644 --- a/pom.xml +++ b/pom.xml @@ -103,14 +103,6 @@ postgresql 42.7.11 - - org.springframework.boot - spring-boot-starter-flyway - - - org.flywaydb - flyway-database-postgresql - org.springframework.boot spring-boot-starter-web diff --git a/run_service.sh b/run_service.sh index 837536e..bfe9085 100755 --- a/run_service.sh +++ b/run_service.sh @@ -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" diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactJobEntity.java b/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactJobEntity.java index feffa55..27ea4b3 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactJobEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactJobEntity.java @@ -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) diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactReportEntity.java b/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactReportEntity.java index 8c4f64e..3d14ab1 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactReportEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/bias/persistence/BiasImpactReportEntity.java @@ -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; } diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/repo/ExecutionEntity.java b/src/main/java/it/cnr/isti/workflow/manager/executions/repo/ExecutionEntity.java index e2b51f2..7995880 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/repo/ExecutionEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/repo/ExecutionEntity.java @@ -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; diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java index 5ae4597..6e4ae89 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java @@ -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 diff --git a/src/main/java/it/cnr/isti/workflow/manager/projects/repo/ProjectEntity.java b/src/main/java/it/cnr/isti/workflow/manager/projects/repo/ProjectEntity.java index afbd0c8..b54c12c 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/projects/repo/ProjectEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/projects/repo/ProjectEntity.java @@ -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 diff --git a/src/main/java/it/cnr/isti/workflow/manager/vault/repo/UserSecretEntity.java b/src/main/java/it/cnr/isti/workflow/manager/vault/repo/UserSecretEntity.java index 23cf8f6..b2634ab 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/vault/repo/UserSecretEntity.java +++ b/src/main/java/it/cnr/isti/workflow/manager/vault/repo/UserSecretEntity.java @@ -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; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 13879d3..6c8bb30 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 diff --git a/src/main/resources/db/migration/V10__add_bias_report_version.sql b/src/main/resources/db/migration/V10__add_bias_report_version.sql deleted file mode 100644 index 5d627a7..0000000 --- a/src/main/resources/db/migration/V10__add_bias_report_version.sql +++ /dev/null @@ -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; diff --git a/src/main/resources/db/migration/V2__create_bias_impact_reports.sql b/src/main/resources/db/migration/V2__create_bias_impact_reports.sql deleted file mode 100644 index 01e6a51..0000000 --- a/src/main/resources/db/migration/V2__create_bias_impact_reports.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V3__harden_bias_experiments.sql b/src/main/resources/db/migration/V3__harden_bias_experiments.sql deleted file mode 100644 index 0ef98fd..0000000 --- a/src/main/resources/db/migration/V3__harden_bias_experiments.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V4__add_subflow_execution_relationship.sql b/src/main/resources/db/migration/V4__add_subflow_execution_relationship.sql deleted file mode 100644 index f5c103a..0000000 --- a/src/main/resources/db/migration/V4__add_subflow_execution_relationship.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V5__create_user_secret_vault.sql b/src/main/resources/db/migration/V5__create_user_secret_vault.sql deleted file mode 100644 index 7404415..0000000 --- a/src/main/resources/db/migration/V5__create_user_secret_vault.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V6__create_project.sql b/src/main/resources/db/migration/V6__create_project.sql deleted file mode 100644 index 8d1e2a8..0000000 --- a/src/main/resources/db/migration/V6__create_project.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V7__add_project_execution_columns.sql b/src/main/resources/db/migration/V7__add_project_execution_columns.sql deleted file mode 100644 index cad3dcc..0000000 --- a/src/main/resources/db/migration/V7__add_project_execution_columns.sql +++ /dev/null @@ -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); diff --git a/src/main/resources/db/migration/V8__add_project_run_order.sql b/src/main/resources/db/migration/V8__add_project_run_order.sql deleted file mode 100644 index 8bdf2ac..0000000 --- a/src/main/resources/db/migration/V8__add_project_run_order.sql +++ /dev/null @@ -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; diff --git a/src/main/resources/db/migration/V9__add_bias_judge_job.sql b/src/main/resources/db/migration/V9__add_bias_judge_job.sql deleted file mode 100644 index 4280748..0000000 --- a/src/main/resources/db/migration/V9__add_bias_judge_job.sql +++ /dev/null @@ -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; diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index 9d90c94..bc8adb9 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -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