From 4b165f801844ce7aebe3f3fa00d70118e9b852cc Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Mon, 29 Jan 2024 12:54:07 -0800 Subject: [PATCH 01/17] Split Scheduling Conditions into Metadata and Definition - Update Specification to include revision - Add a Model Specification for Scheduling Conditions - Add tags tables --- .../down.sql | 145 ++++++++++ .../up.sql | 262 ++++++++++++++++++ .../scheduling_condition_definition_tags.sql | 12 + .../metadata/scheduling_condition_tags.sql | 9 + .../scheduler/tables/scheduling_condition.sql | 54 ---- .../scheduling_condition_definition.sql | 51 ++++ .../tables/scheduling_condition_metadata.sql | 51 ++++ ...eduling_model_specification_conditions.sql | 24 ++ .../scheduling_specification_conditions.sql | 58 +++- 9 files changed, 605 insertions(+), 61 deletions(-) create mode 100644 deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql create mode 100644 deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql create mode 100644 scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_definition_tags.sql create mode 100644 scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_tags.sql delete mode 100644 scheduler-server/sql/scheduler/tables/scheduling_condition.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_condition_definition.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_condition_metadata.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_model_specification_conditions.sql diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql new file mode 100644 index 0000000000..868df91a8a --- /dev/null +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql @@ -0,0 +1,145 @@ +/********** +SCHEDULING CONDITION +***********/ +/* +RESTORE ORIGINAL +*/ +create table scheduling_condition ( + id integer generated by default as identity, + revision integer not null default 0, + name text not null, + definition text not null, + + model_id integer not null, + description text not null default '', + author text null, + last_modified_by text null, + created_date timestamptz not null default now(), + modified_date timestamptz not null default now(), + + constraint scheduling_condition_synthetic_key + primary key (id) +); + +comment on table scheduling_condition is e'' + 'A condition restricting scheduling of a plan.'; +comment on column scheduling_condition.id is e'' + 'The synthetic identifier for this scheduling condition.'; +comment on column scheduling_condition.revision is e'' + 'A monotonic clock that ticks for every change to this scheduling condition.'; +comment on column scheduling_condition.definition is e'' + 'The source code for a Typescript module defining this scheduling condition'; +comment on column scheduling_condition.model_id is e'' + 'The mission model used to which this scheduling condition is associated.'; +comment on column scheduling_condition.name is e'' + 'A short human readable name for this condition'; +comment on column scheduling_condition.description is e'' + 'A longer text description of this scheduling condition.'; +comment on column scheduling_condition.author is e'' + 'The original user who authored this scheduling condition.'; +comment on column scheduling_condition.last_modified_by is e'' + 'The last user who modified this scheduling condition.'; +comment on column scheduling_condition.created_date is e'' + 'The date this scheduling condition was created.'; +comment on column scheduling_condition.modified_date is e'' + 'The date this scheduling condition was last modified.'; + +create function update_logging_on_update_scheduling_condition() + returns trigger + security definer +language plpgsql as $$begin + new.revision = old.revision + 1; + new.modified_date = now(); +return new; +end$$; + +create trigger update_logging_on_update_scheduling_condition_trigger + before update on scheduling_condition + for each row + when (pg_trigger_depth() < 1) + execute function update_logging_on_update_scheduling_condition(); + +/* +DATA MIGRATION +*/ +-- Conditions not on a model spec will not be kept, as the scheduler DB can't get the model id from the plan id +-- Because there is no uniqueness constraint on Scheduling Conditions when it comes to specifications, the ids can be preserved +with specified_definition(condition_id, condition_revision, model_id, definition, definition_creation) as ( + select cd.condition_id, cd.revision, s.model_id, cd.definition, cd.created_at + from scheduling_model_specification_conditions s + left join scheduling_condition_definition cd using (condition_id) + where ((s.condition_revision is not null and s.condition_revision = cd.revision) + or (s.condition_revision is null and cd.revision = (select def.revision + from scheduling_condition_definition def + where def.condition_id = s.condition_id + order by def.revision desc limit 1))) +) +insert into scheduling_condition(id, revision, name, definition, model_id, description, + author, last_modified_by, created_date, modified_date) +select m.id, sd.condition_revision, m.name, sd.definition, sd.model_id, m.description, + m.owner, m.updated_by, m.updated_at, greatest(m.updated_at::timestamptz, sd.definition_creation::timestamptz) + from scheduling_condition_metadata m + inner join specified_definition sd on m.id = sd.condition_id; + +/* +POST DATA MIGRATION TABLE CHANGES +*/ +drop trigger set_timestamp on scheduling_condition_metadata; +drop function scheduling_condition_metadata_set_updated_at(); + +alter table scheduling_condition + alter column id set generated always; +/* +SPECIFICATIONS +*/ +drop trigger increment_revision_on_condition_delete on scheduling_specification_conditions; +drop function increment_spec_revision_on_conditions_spec_delete(); +drop trigger increment_revision_on_condition_update on scheduling_specification_conditions; +drop function increment_spec_revision_on_conditions_spec_update(); + +alter table scheduling_specification_conditions + drop constraint scheduling_specification_condition_definition_exists, + drop constraint scheduling_specification_condition_exists, + add constraint scheduling_specification_conditions_references_scheduling_conditions + foreign key (condition_id) + references scheduling_condition + on update cascade + on delete cascade, + drop constraint scheduling_specification_conditions_specification_exists, + add constraint scheduling_specification_conditions_references_scheduling_specification + foreign key (specification_id) + references scheduling_specification + on update cascade + on delete cascade, + drop column condition_revision; + +comment on table scheduling_specification_conditions is e'' + 'A join table associating scheduling specifications with scheduling conditions.'; +comment on column scheduling_specification_conditions.specification_id is e'' + 'The ID of the scheduling specification a scheduling goal is associated with.'; +comment on column scheduling_specification_conditions.condition_id is e'' + 'The ID of the condition a scheduling specification is associated with.'; +comment on column scheduling_specification_conditions.enabled is null; + +drop table scheduling_model_specification_conditions; + +/* +TAGS +*/ +drop table metadata.scheduling_condition_definition_tags; +drop table metadata.scheduling_condition_tags; + +/* +DEFINITION +*/ +drop trigger scheduling_goal_definition_set_revision on scheduling_condition_definition; +drop function scheduling_condition_definition_set_revision(); +drop table scheduling_condition_definition; + +/* +METADATA +*/ +drop index condition_name_unique_if_published; +drop table scheduling_condition_metadata; + +call migrations.mark_migration_rolled_back('13'); diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql new file mode 100644 index 0000000000..c2ce2059d4 --- /dev/null +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql @@ -0,0 +1,262 @@ +/********** +SCHEDULING CONDITION +***********/ +/* +METADATA +*/ +create table scheduling_condition_metadata ( + id integer generated by default as identity, + + name text not null, + description text not null default '', + public boolean not null default false, + + owner text, + updated_by text, + + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint scheduling_condition_metadata_pkey + primary key (id) +); + +-- A partial index is used to enforce name uniqueness only on conditions visible to other users +create unique index condition_name_unique_if_published on scheduling_condition_metadata (name) where public; + +comment on table scheduling_condition_metadata is e'' + 'A condition restricting scheduling of a plan.'; +comment on column scheduling_condition_metadata.id is e'' + 'The unique identifier for this scheduling condition.'; +comment on column scheduling_condition_metadata.name is e'' + 'A short human readable name for this condition'; +comment on column scheduling_condition_metadata.description is e'' + 'A longer text description of this scheduling condition.'; +comment on column scheduling_condition_metadata.public is e'' + 'Whether this goal is visible to all users.'; +comment on column scheduling_condition_metadata.owner is e'' + 'The user responsible for this condition.'; +comment on column scheduling_condition_metadata.updated_by is e'' + 'The user who last modified this condition''s metadata.'; +comment on column scheduling_condition_metadata.created_at is e'' + 'The time at which this condition was created.'; +comment on column scheduling_condition_metadata.updated_at is e'' + 'The time at which this condition''s metadata was last modified.'; + +/* +DEFINITION +*/ +create table scheduling_condition_definition( + condition_id integer not null, + revision integer not null default 0, + definition text not null, + author text, + created_at timestamptz not null default now(), + + constraint scheduling_condition_definition_pkey + primary key (condition_id, revision), + constraint scheduling_condition_definition_condition_exists + foreign key (condition_id) + references scheduling_condition_metadata + on update cascade + on delete cascade +); + +comment on table scheduling_condition_definition is e'' + 'The specific revisions of a scheduling condition''s definition'; +comment on column scheduling_condition_definition.revision is e'' + 'An identifier of this definition.'; +comment on column scheduling_condition_definition.definition is e'' + 'An executable expression in the Merlin scheduling language.'; +comment on column scheduling_condition_definition.author is e'' + 'The user who authored this revision.'; +comment on column scheduling_condition_definition.created_at is e'' + 'When this revision was created.'; + +create function scheduling_condition_definition_set_revision() +returns trigger +volatile +language plpgsql as $$ +declare + max_revision integer; +begin + -- Grab the current max value of revision, or -1, if this is the first revision + select coalesce((select revision + from scheduling_condition_definition + where condition_id = new.condition_id + order by revision desc + limit 1), -1) + into max_revision; + + new.revision = max_revision + 1; + return new; +end +$$; + +create trigger scheduling_goal_definition_set_revision + before insert on scheduling_condition_definition + for each row + execute function scheduling_condition_definition_set_revision(); + +/* +TAGS +*/ +create table metadata.scheduling_condition_tags ( + condition_id integer references public.scheduling_condition_metadata + on update cascade + on delete cascade, + tag_id integer not null, + primary key (condition_id, tag_id) +); +comment on table metadata.scheduling_condition_tags is e'' + 'The tags associated with a scheduling condition.'; + +create table metadata.scheduling_condition_definition_tags ( + condition_id integer not null, + condition_revision integer not null, + tag_id integer not null, + primary key (condition_id, condition_revision, tag_id), + foreign key (condition_id, condition_revision) references scheduling_condition_definition + on update cascade + on delete cascade +); + +comment on table metadata.scheduling_condition_definition_tags is e'' + 'The tags associated with a specific scheduling condition definition.'; + +/* +SPECIFICATIONS +*/ +create table scheduling_model_specification_conditions( + model_id integer not null, + condition_id integer not null, + condition_revision integer, -- latest is NULL + + primary key (model_id, condition_id), + foreign key (condition_id) + references scheduling_condition_metadata + on update cascade + on delete restrict, + foreign key (condition_id, condition_revision) + references scheduling_condition_definition + on update cascade + on delete restrict +); + +comment on table scheduling_model_specification_conditions is e'' +'The set of scheduling conditions that all plans using the model should include in their scheduling specification.'; +comment on column scheduling_model_specification_conditions.model_id is e'' +'The model which this specification is for. Half of the primary key.'; +comment on column scheduling_model_specification_conditions.condition_id is e'' +'The id of a specific scheduling condition in the specification. Half of the primary key.'; +comment on column scheduling_model_specification_conditions.condition_revision is e'' +'The version of the scheduling condition definition to use. Leave NULL to use the latest version.'; + +alter table scheduling_specification_conditions + add column condition_revision integer, + -- This constraint's name is too long + drop constraint scheduling_specification_conditions_references_scheduling_specification, + add constraint scheduling_specification_conditions_specification_exists + foreign key (specification_id) + references scheduling_specification + on update cascade + on delete cascade, + drop constraint scheduling_specification_conditions_references_scheduling_conditions, + add constraint scheduling_specification_condition_exists + foreign key (condition_id) + references scheduling_condition_metadata + on update cascade + on delete restrict, + add constraint scheduling_specification_condition_definition_exists + foreign key (condition_id, condition_revision) + references scheduling_condition_definition + on update cascade + on delete restrict; + +comment on table scheduling_specification_conditions is e'' + 'The set of scheduling conditions to be used on a given plan.'; +comment on column scheduling_specification_conditions.specification_id is e'' + 'The plan scheduling specification which this condition is on. Half of the primary key.'; +comment on column scheduling_specification_conditions.condition_id is e'' + 'The ID of a specific condition in the specification. Half of the primary key.'; +comment on column scheduling_specification_conditions.condition_revision is e'' + 'The version of the condition definition to use. Leave NULL to use the latest version.'; +comment on column scheduling_specification_conditions.enabled is e'' + 'Whether to use a given condition. Defaults to TRUE.'; + +create function increment_spec_revision_on_conditions_spec_update() + returns trigger + security definer +language plpgsql as $$ +begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end; +$$; + +create trigger increment_revision_on_condition_update + before insert or update on scheduling_specification_conditions + for each row + execute function increment_spec_revision_on_conditions_spec_update(); + +create function increment_spec_revision_on_conditions_spec_delete() + returns trigger + security definer +language plpgsql as $$ +begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end; +$$; + +create trigger increment_revision_on_condition_delete + before delete on scheduling_specification_conditions + for each row + execute function increment_spec_revision_on_conditions_spec_delete(); + +/* +DATA MIGRATION +*/ +insert into scheduling_condition_metadata(id, name, description, public, owner, updated_by, created_at, updated_at) +select id, name, description, false, author, last_modified_by, created_date, modified_date +from scheduling_condition; + +insert into scheduling_condition_definition(condition_id, definition, author, created_at) +select id, definition, author, modified_date +from scheduling_condition; + +insert into scheduling_model_specification_conditions(model_id, condition_id) +select model_id, id +from scheduling_condition; + +/* +POST DATA MIGRATION TABLE CHANGES +*/ +alter table scheduling_condition_metadata + alter column id set generated always; + +create function scheduling_condition_metadata_set_updated_at() +returns trigger +security definer +language plpgsql as $$begin + new.updated_at = now(); + return new; +end$$; + +create trigger set_timestamp +before update on scheduling_condition_metadata +for each row +execute function scheduling_condition_metadata_set_updated_at(); + +/* +DROP ORIGINAL +*/ +drop trigger update_logging_on_update_scheduling_condition_trigger on scheduling_condition; +drop function update_logging_on_update_scheduling_condition(); +drop table scheduling_condition; + +call migrations.mark_migration_applied('13'); diff --git a/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_definition_tags.sql b/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_definition_tags.sql new file mode 100644 index 0000000000..f29d45981f --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_definition_tags.sql @@ -0,0 +1,12 @@ +create table metadata.scheduling_condition_definition_tags ( + condition_id integer not null, + condition_revision integer not null, + tag_id integer not null, + primary key (condition_id, condition_revision, tag_id), + foreign key (condition_id, condition_revision) references scheduling_condition_definition + on update cascade + on delete cascade +); + +comment on table metadata.scheduling_condition_definition_tags is e'' + 'The tags associated with a specific scheduling condition definition.'; diff --git a/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_tags.sql b/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_tags.sql new file mode 100644 index 0000000000..f209b9854f --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/metadata/scheduling_condition_tags.sql @@ -0,0 +1,9 @@ +create table metadata.scheduling_condition_tags ( + condition_id integer references public.scheduling_condition_metadata + on update cascade + on delete cascade, + tag_id integer not null, + primary key (condition_id, tag_id) +); +comment on table metadata.scheduling_condition_tags is e'' + 'The tags associated with a scheduling condition.'; diff --git a/scheduler-server/sql/scheduler/tables/scheduling_condition.sql b/scheduler-server/sql/scheduler/tables/scheduling_condition.sql deleted file mode 100644 index d4aabbb776..0000000000 --- a/scheduler-server/sql/scheduler/tables/scheduling_condition.sql +++ /dev/null @@ -1,54 +0,0 @@ -create table scheduling_condition ( - id integer generated always as identity, - revision integer not null default 0, - name text not null, - definition text not null, - - model_id integer not null, - description text not null default '', - author text null, - last_modified_by text null, - created_date timestamptz not null default now(), - modified_date timestamptz not null default now(), - - constraint scheduling_condition_synthetic_key - primary key (id) -); - -comment on table scheduling_condition is e'' - 'A condition restricting scheduling of a plan.'; -comment on column scheduling_condition.id is e'' - 'The synthetic identifier for this scheduling condition.'; -comment on column scheduling_condition.revision is e'' - 'A monotonic clock that ticks for every change to this scheduling condition.'; -comment on column scheduling_condition.definition is e'' - 'The source code for a Typescript module defining this scheduling condition'; -comment on column scheduling_condition.model_id is e'' - 'The mission model used to which this scheduling condition is associated.'; -comment on column scheduling_condition.name is e'' - 'A short human readable name for this condition'; -comment on column scheduling_condition.description is e'' - 'A longer text description of this scheduling condition.'; -comment on column scheduling_condition.author is e'' - 'The original user who authored this scheduling condition.'; -comment on column scheduling_condition.last_modified_by is e'' - 'The last user who modified this scheduling condition.'; -comment on column scheduling_condition.created_date is e'' - 'The date this scheduling condition was created.'; -comment on column scheduling_condition.modified_date is e'' - 'The date this scheduling condition was last modified.'; - -create function update_logging_on_update_scheduling_condition() - returns trigger - security definer -language plpgsql as $$begin - new.revision = old.revision + 1; - new.modified_date = now(); -return new; -end$$; - -create trigger update_logging_on_update_scheduling_condition_trigger - before update on scheduling_condition - for each row - when (pg_trigger_depth() < 1) - execute function update_logging_on_update_scheduling_condition(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_condition_definition.sql b/scheduler-server/sql/scheduler/tables/scheduling_condition_definition.sql new file mode 100644 index 0000000000..2501ac11a4 --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_condition_definition.sql @@ -0,0 +1,51 @@ +create table scheduling_condition_definition( + condition_id integer not null, + revision integer not null default 0, + definition text not null, + author text, + created_at timestamptz not null default now(), + + constraint scheduling_condition_definition_pkey + primary key (condition_id, revision), + constraint scheduling_condition_definition_condition_exists + foreign key (condition_id) + references scheduling_condition_metadata + on update cascade + on delete cascade +); + +comment on table scheduling_condition_definition is e'' + 'The specific revisions of a scheduling condition''s definition'; +comment on column scheduling_condition_definition.revision is e'' + 'An identifier of this definition.'; +comment on column scheduling_condition_definition.definition is e'' + 'An executable expression in the Merlin scheduling language.'; +comment on column scheduling_condition_definition.author is e'' + 'The user who authored this revision.'; +comment on column scheduling_condition_definition.created_at is e'' + 'When this revision was created.'; + +create function scheduling_condition_definition_set_revision() +returns trigger +volatile +language plpgsql as $$ +declare + max_revision integer; +begin + -- Grab the current max value of revision, or -1, if this is the first revision + select coalesce((select revision + from scheduling_condition_definition + where condition_id = new.condition_id + order by revision desc + limit 1), -1) + into max_revision; + + new.revision = max_revision + 1; + return new; +end +$$; + +create trigger scheduling_goal_definition_set_revision + before insert on scheduling_condition_definition + for each row + execute function scheduling_condition_definition_set_revision(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_condition_metadata.sql b/scheduler-server/sql/scheduler/tables/scheduling_condition_metadata.sql new file mode 100644 index 0000000000..60a23d880b --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_condition_metadata.sql @@ -0,0 +1,51 @@ +create table scheduling_condition_metadata ( + id integer generated always as identity, + + name text not null, + description text not null default '', + public boolean not null default false, + + owner text, + updated_by text, + + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint scheduling_condition_metadata_pkey + primary key (id) +); + +-- A partial index is used to enforce name uniqueness only on conditions visible to other users +create unique index condition_name_unique_if_published on scheduling_condition_metadata (name) where public; + +comment on table scheduling_condition_metadata is e'' + 'A condition restricting scheduling of a plan.'; +comment on column scheduling_condition_metadata.id is e'' + 'The unique identifier for this scheduling condition.'; +comment on column scheduling_condition_metadata.name is e'' + 'A short human readable name for this condition'; +comment on column scheduling_condition_metadata.description is e'' + 'A longer text description of this scheduling condition.'; +comment on column scheduling_condition_metadata.public is e'' + 'Whether this goal is visible to all users.'; +comment on column scheduling_condition_metadata.owner is e'' + 'The user responsible for this condition.'; +comment on column scheduling_condition_metadata.updated_by is e'' + 'The user who last modified this condition''s metadata.'; +comment on column scheduling_condition_metadata.created_at is e'' + 'The time at which this condition was created.'; +comment on column scheduling_condition_metadata.updated_at is e'' + 'The time at which this condition''s metadata was last modified.'; + +create function scheduling_condition_metadata_set_updated_at() +returns trigger +security definer +language plpgsql as $$begin + new.updated_at = now(); + return new; +end$$; + +create trigger set_timestamp +before update on scheduling_condition_metadata +for each row +execute function scheduling_condition_metadata_set_updated_at(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_model_specification_conditions.sql b/scheduler-server/sql/scheduler/tables/scheduling_model_specification_conditions.sql new file mode 100644 index 0000000000..1230483f20 --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_model_specification_conditions.sql @@ -0,0 +1,24 @@ +create table scheduling_model_specification_conditions( + model_id integer not null, + condition_id integer not null, + condition_revision integer, -- latest is NULL + + primary key (model_id, condition_id), + foreign key (condition_id) + references scheduling_condition_metadata + on update cascade + on delete restrict, + foreign key (condition_id, condition_revision) + references scheduling_condition_definition + on update cascade + on delete restrict +); + +comment on table scheduling_model_specification_conditions is e'' +'The set of scheduling conditions that all plans using the model should include in their scheduling specification.'; +comment on column scheduling_model_specification_conditions.model_id is e'' +'The model which this specification is for. Half of the primary key.'; +comment on column scheduling_model_specification_conditions.condition_id is e'' +'The id of a specific scheduling condition in the specification. Half of the primary key.'; +comment on column scheduling_model_specification_conditions.condition_revision is e'' +'The version of the scheduling condition definition to use. Leave NULL to use the latest version.'; diff --git a/scheduler-server/sql/scheduler/tables/scheduling_specification_conditions.sql b/scheduler-server/sql/scheduler/tables/scheduling_specification_conditions.sql index badd50bd29..4a9657ef08 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_specification_conditions.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_specification_conditions.sql @@ -1,25 +1,69 @@ create table scheduling_specification_conditions ( specification_id integer not null, condition_id integer not null, + condition_revision integer, -- latest is NULL enabled boolean default true, constraint scheduling_specification_conditions_primary_key primary key (specification_id, condition_id), - constraint scheduling_specification_conditions_references_scheduling_specification + constraint scheduling_specification_conditions_specification_exists foreign key (specification_id) references scheduling_specification on update cascade on delete cascade, - constraint scheduling_specification_conditions_references_scheduling_conditions + constraint scheduling_specification_condition_exists foreign key (condition_id) - references scheduling_condition + references scheduling_condition_metadata on update cascade - on delete cascade + on delete restrict, + constraint scheduling_specification_condition_definition_exists + foreign key (condition_id, condition_revision) + references scheduling_condition_definition + on update cascade + on delete restrict ); comment on table scheduling_specification_conditions is e'' - 'A join table associating scheduling specifications with scheduling conditions.'; + 'The set of scheduling conditions to be used on a given plan.'; comment on column scheduling_specification_conditions.specification_id is e'' - 'The ID of the scheduling specification a scheduling goal is associated with.'; + 'The plan scheduling specification which this condition is on. Half of the primary key.'; comment on column scheduling_specification_conditions.condition_id is e'' - 'The ID of the condition a scheduling specification is associated with.'; + 'The ID of a specific condition in the specification. Half of the primary key.'; +comment on column scheduling_specification_conditions.condition_revision is e'' + 'The version of the condition definition to use. Leave NULL to use the latest version.'; +comment on column scheduling_specification_conditions.enabled is e'' + 'Whether to use a given condition. Defaults to TRUE.'; + +create function increment_spec_revision_on_conditions_spec_update() + returns trigger + security definer +language plpgsql as $$ +begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end; +$$; + +create trigger increment_revision_on_condition_update + before insert or update on scheduling_specification_conditions + for each row + execute function increment_spec_revision_on_conditions_spec_update(); + +create function increment_spec_revision_on_conditions_spec_delete() + returns trigger + security definer +language plpgsql as $$ +begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end; +$$; + +create trigger increment_revision_on_condition_delete + before delete on scheduling_specification_conditions + for each row + execute function increment_spec_revision_on_conditions_spec_delete(); From 5e495b71035f6c151371680065a0c565afe5453d Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Mon, 29 Jan 2024 16:44:18 -0800 Subject: [PATCH 02/17] Split Scheduling Goals into Metadata and Definition - Update Specification to include revision - Add a Model Specification for Scheduling Goals - Add and update tags tables --- .../down.sql | 237 +++++++++ .../up.sql | 466 ++++++++++++++++++ .../scheduling_goal_definition_tags.sql | 12 + .../tables/metadata/scheduling_goal_tags.sql | 2 +- .../sql/scheduler/tables/scheduling_goal.sql | 54 -- .../tables/scheduling_goal_definition.sql | 52 ++ .../tables/scheduling_goal_metadata.sql | 51 ++ .../scheduling_model_specification_goals.sql | 140 ++++++ .../tables/scheduling_specification.sql | 18 - .../tables/scheduling_specification_goals.sql | 214 +++++--- 10 files changed, 1095 insertions(+), 151 deletions(-) create mode 100644 scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_definition_tags.sql delete mode 100644 scheduler-server/sql/scheduler/tables/scheduling_goal.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_goal_definition.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_goal_metadata.sql create mode 100644 scheduler-server/sql/scheduler/tables/scheduling_model_specification_goals.sql diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql index 868df91a8a..a6b39092fa 100644 --- a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql @@ -1,3 +1,240 @@ +/********** +SCHEDULING GOALS +***********/ +/* +RESTORE ORIGINAL +*/ +create table scheduling_goal ( + id integer generated always as identity, + revision integer not null default 0, + name text not null, + definition text not null, + + model_id integer not null, + description text not null default '', + author text null, + last_modified_by text null, + created_date timestamptz not null default now(), + modified_date timestamptz not null default now(), + + constraint scheduling_goal_synthetic_key + primary key (id) +); + +comment on table scheduling_goal is e'' + 'A goal for scheduling of a plan.'; +comment on column scheduling_goal.id is e'' + 'The synthetic identifier for this scheduling goal.'; +comment on column scheduling_goal.revision is e'' + 'A monotonic clock that ticks for every change to this scheduling goal.'; +comment on column scheduling_goal.definition is e'' + 'The source code for a Typescript module defining this scheduling goal'; +comment on column scheduling_goal.model_id is e'' + 'The mission model used to which this scheduling goal is associated.'; +comment on column scheduling_goal.name is e'' + 'A short human readable name for this goal'; +comment on column scheduling_goal.description is e'' + 'A longer text description of this scheduling goal.'; +comment on column scheduling_goal.author is e'' + 'The original user who authored this scheduling goal.'; +comment on column scheduling_goal.last_modified_by is e'' + 'The last user who modified this scheduling goal.'; +comment on column scheduling_goal.created_date is e'' + 'The date this scheduling goal was created.'; +comment on column scheduling_goal.modified_date is e'' + 'The date this scheduling goal was last modified.'; + +create function update_logging_on_update_scheduling_goal() + returns trigger + security definer +language plpgsql as $$begin + new.revision = old.revision + 1; + new.modified_date = now(); +return new; +end$$; + +create trigger update_logging_on_update_scheduling_goal_trigger + before update on scheduling_goal + for each row + when (pg_trigger_depth() < 1) + execute function update_logging_on_update_scheduling_goal(); + +/* +DATA MIGRATION +*/ +-- Goals not on a model spec will not be kept, as the scheduler DB can't get the model id from the plan id +-- Because multiple spec may be using the same goal/goal definition, we have to regenerate the id +with specified_definition(goal_id, goal_revision, model_id, definition, definition_creation) as ( + select gd.goal_id, gd.revision, s.model_id, gd.definition, gd.created_at + from scheduling_model_specification_goals s + left join scheduling_goal_definition gd using (goal_id) + where ((s.goal_revision is not null and s.goal_revision = gd.revision) + or (s.goal_revision is null and gd.revision = (select def.revision + from scheduling_goal_definition def + where def.goal_id = s.goal_id + order by def.revision desc limit 1))) +) +insert into scheduling_goal(revision, name, definition, model_id, description, + author, last_modified_by, created_date, modified_date) +select sd.goal_revision, m.name, sd.definition, sd.model_id, m.description, + m.owner, m.updated_by, m.created_at, greatest(m.updated_at::timestamptz, sd.definition_creation::timestamptz) + from scheduling_goal_metadata m + inner join specified_definition sd on m.id = sd.goal_id; +/* +POST DATA MIGRATION TABLE CHANGES +*/ +drop trigger set_timestamp on scheduling_goal_metadata; +drop function scheduling_goal_metadata_set_updated_at(); + +/* +SCHEDULING SPECIFICATION +*/ +create function increment_revision_on_goal_update() + returns trigger + security definer +language plpgsql as $$begin + with goals as ( + select g.specification_id from scheduling_specification_goals as g + where g.goal_id = new.id + ) + update scheduling_specification set revision = revision + 1 + where exists(select 1 from goals where specification_id = id); + return new; +end$$; +create trigger increment_revision_on_goal_update + before update on scheduling_goal + for each row + execute function increment_revision_on_goal_update(); + + +/* +SPECIFICATIONS +*/ +drop trigger increment_revision_on_goal_delete on scheduling_specification_goals; +drop function increment_spec_revision_on_goal_spec_delete(); +drop trigger increment_revision_on_goal_update on scheduling_specification_goals; +drop function increment_spec_revision_on_goal_spec_update(); + +create or replace function delete_scheduling_specification_goal_func() + returns trigger as $$begin + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = OLD.specification_id + and priority > OLD.priority; + return null; + end;$$ +language plpgsql; + +create or replace function update_scheduling_specification_goal_func() + returns trigger as $$begin + if (pg_trigger_depth() = 1) then + if NEW.priority > OLD.priority then + if NEW.priority > ( + select coalesce(max(priority), -1) from scheduling_specification_goals + where specification_id = new.specification_id + ) + 1 then + raise exception 'Updated priority % for specification_id % is not consecutive', NEW.priority, new.specification_id; + end if; + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = NEW.specification_id + and priority between OLD.priority + 1 and NEW.priority + and goal_id <> NEW.goal_id; + else + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = NEW.specification_id + and priority between NEW.priority and OLD.priority - 1 + and goal_id <> NEW.goal_id; + end if; + end if; + return NEW; + end;$$ +language plpgsql; + +create or replace function insert_scheduling_specification_goal_func() + returns trigger as $$begin + if NEW.priority IS NULL then + NEW.priority = ( + select coalesce(max(priority), -1) from scheduling_specification_goals + where specification_id = NEW.specification_id + ) + 1; + elseif NEW.priority > ( + select coalesce(max(priority), -1) from scheduling_specification_goals + where specification_id = new.specification_id + ) + 1 then + raise exception 'Inserted priority % for specification_id % is not consecutive', NEW.priority, NEW.specification_id; + end if; + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = NEW.specification_id + and priority >= NEW.priority; + return NEW; + end;$$ +language plpgsql; + +alter table scheduling_specification_goals + add constraint scheduling_specification_unique_goal_id + unique (goal_id), + drop constraint scheduling_spec_goal_definition_exists, + drop constraint scheduling_spec_goal_exists, + add constraint scheduling_specification_goals_references_scheduling_goals + foreign key (goal_id) + references scheduling_goal + on update cascade + on delete cascade, + drop constraint scheduling_specification_goals_specification_exists, + add constraint scheduling_specification_goals_references_scheduling_specification + foreign key (specification_id) + references scheduling_specification + on update cascade + on delete cascade, + alter column enabled drop not null, + alter column priority set default null, + drop column goal_revision; + +comment on table scheduling_specification_goals is e'' + 'A join table associating scheduling specifications with scheduling goals.'; +comment on column scheduling_specification_goals.specification_id is e'' + 'The ID of the scheduling specification a scheduling goal is associated with.'; +comment on column scheduling_specification_goals.goal_id is e'' + 'The ID of the scheduling goal a scheduling specification is associated with.'; +comment on column scheduling_specification_goals.priority is e'' + 'The relative priority of a scheduling goal in relation to other ' + 'scheduling goals within the same specification.'; +comment on column scheduling_specification_goals.enabled is null; +comment on column scheduling_specification_goals.simulate_after is e'' + 'Whether to re-simulate after evaluating this goal and before the next goal.'; + +drop trigger delete_scheduling_model_specification_goal on scheduling_model_specification_goals; +drop function delete_scheduling_model_specification_goal_func(); +drop trigger update_scheduling_model_specification_goal on scheduling_model_specification_goals; +drop function update_scheduling_model_specification_goal_func(); +drop trigger insert_scheduling_model_specification_goal on scheduling_model_specification_goals; +drop function insert_scheduling_model_specification_goal_func(); + +drop table scheduling_model_specification_goals; +/* +TAGS +*/ +drop table metadata.scheduling_goal_definition_tags; +alter table metadata.scheduling_goal_tags +drop constraint scheduling_goal_tags_goal_id_fkey, +add foreign key (goal_id) references public.scheduling_goal + on update cascade + on delete cascade; +/* +DEFINITION +*/ +drop trigger scheduling_goal_definition_set_revision on scheduling_goal_definition; +drop function scheduling_goal_definition_set_revision(); +drop table scheduling_goal_definition; +/* +METADATA +*/ +drop index goal_name_unique_if_published; +drop table scheduling_goal_metadata; + /********** SCHEDULING CONDITION ***********/ diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql index c2ce2059d4..f9ff0a991f 100644 --- a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql @@ -259,4 +259,470 @@ drop trigger update_logging_on_update_scheduling_condition_trigger on scheduling drop function update_logging_on_update_scheduling_condition(); drop table scheduling_condition; +/********** +SCHEDULING GOALS +***********/ +/* +METADATA +*/ +create table scheduling_goal_metadata ( + id integer generated by default as identity, + + name text not null, + description text not null default '', + public boolean not null default false, + + owner text, + updated_by text, + + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint scheduling_goal_metadata_pkey + primary key (id) +); + +-- A partial index is used to enforce name uniqueness only on goals visible to other users +create unique index goal_name_unique_if_published on scheduling_goal_metadata (name) where public; + +comment on table scheduling_goal_metadata is e'' + 'A goal for scheduling a plan.'; +comment on column scheduling_goal_metadata.id is e'' + 'The unique identifier of the goal'; +comment on column scheduling_goal_metadata.name is e'' + 'A human-meaningful name.'; +comment on column scheduling_goal_metadata.description is e'' + 'A detailed description suitable for long-form documentation.'; +comment on column scheduling_goal_metadata.public is e'' + 'Whether this goal is visible to all users.'; +comment on column scheduling_goal_metadata.owner is e'' + 'The user responsible for this goal.'; +comment on column scheduling_goal_metadata.updated_by is e'' + 'The user who last modified this goal''s metadata.'; +comment on column scheduling_goal_metadata.created_at is e'' + 'The time at which this goal was created.'; +comment on column scheduling_goal_metadata.updated_at is e'' + 'The time at which this goal''s metadata was last modified.'; + +/* +DEFINITION +*/ +create table scheduling_goal_definition( + goal_id integer not null, + revision integer not null default 0, + + definition text not null, + author text, + created_at timestamptz not null default now(), + + constraint scheduling_goal_definition_pkey + primary key (goal_id, revision), + constraint scheduling_goal_definition_goal_exists + foreign key (goal_id) + references scheduling_goal_metadata + on update cascade + on delete cascade +); + +comment on table scheduling_goal_definition is e'' + 'The specific revisions of a scheduling goal''s definition'; +comment on column scheduling_goal_definition.revision is e'' + 'An identifier of this definition.'; +comment on column scheduling_goal_definition.definition is e'' + 'An executable expression in the Merlin scheduling language.'; +comment on column scheduling_goal_definition.author is e'' + 'The user who authored this revision.'; +comment on column scheduling_goal_definition.created_at is e'' + 'When this revision was created.'; + +create function scheduling_goal_definition_set_revision() +returns trigger +volatile +language plpgsql as $$ +declare + max_revision integer; +begin + -- Grab the current max value of revision, or -1, if this is the first revision + select coalesce((select revision + from scheduling_goal_definition + where goal_id = new.goal_id + order by revision desc + limit 1), -1) + into max_revision; + + new.revision = max_revision + 1; + return new; +end +$$; + +create trigger scheduling_goal_definition_set_revision + before insert on scheduling_goal_definition + for each row + execute function scheduling_goal_definition_set_revision(); + + +/* +TAGS +*/ +alter table metadata.scheduling_goal_tags +drop constraint scheduling_goal_tags_goal_id_fkey, +add foreign key (goal_id) references public.scheduling_goal_metadata + on update cascade + on delete cascade; + +create table metadata.scheduling_goal_definition_tags ( + goal_id integer not null, + goal_revision integer not null, + tag_id integer not null, + primary key (goal_id, goal_revision, tag_id), + foreign key (goal_id, goal_revision) references scheduling_goal_definition + on update cascade + on delete cascade +); + +comment on table metadata.scheduling_goal_definition_tags is e'' + 'The tags associated with a specific scheduling condition definition.'; + +/* +SPECIFICATIONS +*/ +create table scheduling_model_specification_goals( + model_id integer not null, + goal_id integer not null, + goal_revision integer, -- latest is NULL + priority integer not null, + + primary key (model_id, goal_id), + foreign key (goal_id) + references scheduling_goal_metadata + on update cascade + on delete restrict, + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete restrict, + constraint model_spec_unique_goal_priorities + unique (model_id, priority) deferrable initially deferred, + constraint model_spec_nonnegative_priority + check (priority >= 0) +); + +comment on table scheduling_model_specification_goals is e'' +'The set of scheduling goals that all plans using the model should include in their scheduling specification.'; +comment on column scheduling_model_specification_goals.model_id is e'' +'The model which this specification is for. Half of the primary key.'; +comment on column scheduling_model_specification_goals.goal_id is e'' +'The id of a specific scheduling goal in the specification. Half of the primary key.'; +comment on column scheduling_model_specification_goals.goal_revision is e'' +'The version of the scheduling goal definition to use. Leave NULL to use the latest version.'; +comment on column scheduling_model_specification_goals.priority is e'' + 'The relative priority of the scheduling goal in relation to other goals on the same specification.'; + +create function insert_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_model_specification_goals smg + where smg.model_id = new.model_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for model_id % is not consecutive', new.priority, new.model_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority is null then + new.priority = next_priority; + end if; + + update scheduling_model_specification_goals + set priority = priority + 1 + where model_id = new.model_id + and priority >= new.priority; + return new; +end; +$$; + +comment on function insert_scheduling_model_specification_goal_func() is e'' + 'Checks that the inserted priority is consecutive, and reorders (increments) higher or equal priorities to make room.'; + +create trigger insert_scheduling_model_specification_goal + before insert + on scheduling_model_specification_goals + for each row +execute function insert_scheduling_model_specification_goal_func(); + +create function update_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_model_specification_goals smg + where smg.model_id = new.model_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for model_id % is not consecutive', new.priority, new.model_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority > old.priority then + update scheduling_model_specification_goals + set priority = priority - 1 + where model_id = new.model_id + and priority between old.priority + 1 and new.priority + and goal_id != new.goal_id; + else + update scheduling_model_specification_goals + set priority = priority + 1 + where model_id = new.model_id + and priority between new.priority and old.priority - 1 + and goal_id != new.goal_id; + end if; + return new; +end; +$$; + +comment on function update_scheduling_model_specification_goal_func() is e'' + 'Checks that the updated priority is consecutive, and reorders priorities to make room.'; + +create trigger update_scheduling_model_specification_goal + before update + on scheduling_model_specification_goals + for each row + when (OLD.priority is distinct from NEW.priority and pg_trigger_depth() < 1) +execute function update_scheduling_model_specification_goal_func(); + +create function delete_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ +begin + update scheduling_model_specification_goals + set priority = priority - 1 + where model_id = old.model_id + and priority > old.priority; + return null; +end; +$$; + +comment on function delete_scheduling_model_specification_goal_func() is e'' + 'Reorders (decrements) priorities to fill the gap from deleted priority.'; + +create trigger delete_scheduling_model_specification_goal + after delete + on scheduling_model_specification_goals + for each row +execute function delete_scheduling_model_specification_goal_func(); + +alter table scheduling_specification_goals + add column goal_revision integer, + alter column priority drop default, + alter column enabled set not null, + -- This constraint's name is too long + drop constraint scheduling_specification_goals_references_scheduling_specification, + add constraint scheduling_specification_goals_specification_exists + foreign key (specification_id) + references scheduling_specification + on update cascade + on delete cascade, + drop constraint scheduling_specification_goals_references_scheduling_goals, + add constraint scheduling_spec_goal_exists + foreign key (goal_id) + references scheduling_goal_metadata + on update cascade + on delete restrict, + add constraint scheduling_spec_goal_definition_exists + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete restrict, + drop constraint scheduling_specification_unique_goal_id; + +comment on table scheduling_specification_goals is e'' + 'The scheduling goals to be executed against a given plan.'; +comment on column scheduling_specification_goals.specification_id is e'' + 'The plan scheduling specification this goal is on. Half of the primary key.'; +comment on column scheduling_specification_goals.goal_id is e'' + 'The id of a specific goal in the specification. Half of the primary key.'; +comment on column scheduling_specification_goals.goal_revision is e'' + 'The version of the goal definition to use. Leave NULL to use the latest version.'; +comment on column scheduling_specification_goals.priority is e'' + 'The relative priority of a scheduling goal in relation to other ' + 'scheduling goals within the same specification.'; +comment on column scheduling_specification_goals.enabled is e'' + 'Whether to run a given goal. Defaults to TRUE.'; +comment on column scheduling_specification_goals.simulate_after is e'' + 'Whether to re-simulate after evaluating this goal and before the next goal.'; + +create or replace function insert_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_specification_goals ssg + where ssg.specification_id = new.specification_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for specification_id % is not consecutive', new.priority, new.specification_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority is null then + new.priority = next_priority; + end if; + + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = new.specification_id + and priority >= new.priority; + return new; +end; +$$; + +create or replace function update_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_specification_goals ssg + where ssg.specification_id = new.specification_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for specification_id % is not consecutive', new.priority, new.specification_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority > old.priority then + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = new.specification_id + and priority between old.priority + 1 and new.priority + and goal_id != new.goal_id; + else + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = new.specification_id + and priority between new.priority and old.priority - 1 + and goal_id != new.goal_id; + end if; + return new; +end; +$$; + +create or replace function delete_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ +begin + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = old.specification_id + and priority > old.priority; + return null; +end; +$$; + +create function increment_spec_revision_on_goal_spec_update() + returns trigger + security definer +language plpgsql as $$begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end$$; + +create trigger increment_revision_on_goal_update + before insert or update on scheduling_specification_goals + for each row + execute function increment_spec_revision_on_goal_spec_update(); + +create function increment_spec_revision_on_goal_spec_delete() + returns trigger + security definer +language plpgsql as $$begin + update scheduling_specification + set revision = revision + 1 + where id = old.specification_id; + return old; +end$$; + +create trigger increment_revision_on_goal_delete + before delete on scheduling_specification_goals + for each row + execute function increment_spec_revision_on_goal_spec_delete(); + +/* +SCHEDULING SPECIFICATION +*/ +drop trigger increment_revision_on_goal_update on scheduling_goal; +drop function increment_revision_on_goal_update(); + +/* +DATA MIGRATION +*/ +insert into scheduling_goal_metadata(id, name, description, public, owner, updated_by, created_at, updated_at) +select id, name, description, false, author, last_modified_by, created_date, modified_date +from scheduling_goal; + +insert into scheduling_goal_definition(goal_id, definition, author, created_at) +select id, definition, author, modified_date +from scheduling_goal; + +insert into scheduling_model_specification_goals(model_id, goal_id) +select model_id, id +from scheduling_goal; + +/* +POST DATA MIGRATION TABLE CHANGES +*/ +alter table scheduling_goal_metadata + alter column id set generated always; + +create function scheduling_goal_metadata_set_updated_at() +returns trigger +security definer +language plpgsql as $$begin + new.updated_at = now(); + return new; +end$$; + +create trigger set_timestamp +before update on scheduling_goal_metadata +for each row +execute function scheduling_goal_metadata_set_updated_at(); + +/* +DROP ORIGINAL +*/ +drop trigger update_logging_on_update_scheduling_goal_trigger on scheduling_goal; +drop function update_logging_on_update_scheduling_goal(); +drop table scheduling_goal; + call migrations.mark_migration_applied('13'); diff --git a/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_definition_tags.sql b/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_definition_tags.sql new file mode 100644 index 0000000000..6c43be051e --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_definition_tags.sql @@ -0,0 +1,12 @@ +create table metadata.scheduling_goal_definition_tags ( + goal_id integer not null, + goal_revision integer not null, + tag_id integer not null, + primary key (goal_id, goal_revision, tag_id), + foreign key (goal_id, goal_revision) references scheduling_goal_definition + on update cascade + on delete cascade +); + +comment on table metadata.scheduling_goal_definition_tags is e'' + 'The tags associated with a specific scheduling condition definition.'; diff --git a/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_tags.sql b/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_tags.sql index fb2d243e29..1effedabf2 100644 --- a/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_tags.sql +++ b/scheduler-server/sql/scheduler/tables/metadata/scheduling_goal_tags.sql @@ -1,5 +1,5 @@ create table metadata.scheduling_goal_tags ( - goal_id integer references public.scheduling_goal + goal_id integer references public.scheduling_goal_metadata on update cascade on delete cascade, tag_id integer not null, diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal.sql deleted file mode 100644 index 5a5ddfdc06..0000000000 --- a/scheduler-server/sql/scheduler/tables/scheduling_goal.sql +++ /dev/null @@ -1,54 +0,0 @@ -create table scheduling_goal ( - id integer generated always as identity, - revision integer not null default 0, - name text not null, - definition text not null, - - model_id integer not null, - description text not null default '', - author text null, - last_modified_by text null, - created_date timestamptz not null default now(), - modified_date timestamptz not null default now(), - - constraint scheduling_goal_synthetic_key - primary key (id) -); - -comment on table scheduling_goal is e'' - 'A goal for scheduling of a plan.'; -comment on column scheduling_goal.id is e'' - 'The synthetic identifier for this scheduling goal.'; -comment on column scheduling_goal.revision is e'' - 'A monotonic clock that ticks for every change to this scheduling goal.'; -comment on column scheduling_goal.definition is e'' - 'The source code for a Typescript module defining this scheduling goal'; -comment on column scheduling_goal.model_id is e'' - 'The mission model used to which this scheduling goal is associated.'; -comment on column scheduling_goal.name is e'' - 'A short human readable name for this goal'; -comment on column scheduling_goal.description is e'' - 'A longer text description of this scheduling goal.'; -comment on column scheduling_goal.author is e'' - 'The original user who authored this scheduling goal.'; -comment on column scheduling_goal.last_modified_by is e'' - 'The last user who modified this scheduling goal.'; -comment on column scheduling_goal.created_date is e'' - 'The date this scheduling goal was created.'; -comment on column scheduling_goal.modified_date is e'' - 'The date this scheduling goal was last modified.'; - -create function update_logging_on_update_scheduling_goal() - returns trigger - security definer -language plpgsql as $$begin - new.revision = old.revision + 1; - new.modified_date = now(); -return new; -end$$; - -create trigger update_logging_on_update_scheduling_goal_trigger - before update on scheduling_goal - for each row - when (pg_trigger_depth() < 1) - execute function update_logging_on_update_scheduling_goal(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal_definition.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal_definition.sql new file mode 100644 index 0000000000..62588d6c29 --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_goal_definition.sql @@ -0,0 +1,52 @@ +create table scheduling_goal_definition( + goal_id integer not null, + revision integer not null default 0, + + definition text not null, + author text, + created_at timestamptz not null default now(), + + constraint scheduling_goal_definition_pkey + primary key (goal_id, revision), + constraint scheduling_goal_definition_goal_exists + foreign key (goal_id) + references scheduling_goal_metadata + on update cascade + on delete cascade +); + +comment on table scheduling_goal_definition is e'' + 'The specific revisions of a scheduling goal''s definition'; +comment on column scheduling_goal_definition.revision is e'' + 'An identifier of this definition.'; +comment on column scheduling_goal_definition.definition is e'' + 'An executable expression in the Merlin scheduling language.'; +comment on column scheduling_goal_definition.author is e'' + 'The user who authored this revision.'; +comment on column scheduling_goal_definition.created_at is e'' + 'When this revision was created.'; + +create function scheduling_goal_definition_set_revision() +returns trigger +volatile +language plpgsql as $$ +declare + max_revision integer; +begin + -- Grab the current max value of revision, or -1, if this is the first revision + select coalesce((select revision + from scheduling_goal_definition + where goal_id = new.goal_id + order by revision desc + limit 1), -1) + into max_revision; + + new.revision = max_revision + 1; + return new; +end +$$; + +create trigger scheduling_goal_definition_set_revision + before insert on scheduling_goal_definition + for each row + execute function scheduling_goal_definition_set_revision(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal_metadata.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal_metadata.sql new file mode 100644 index 0000000000..e968d901c3 --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_goal_metadata.sql @@ -0,0 +1,51 @@ +create table scheduling_goal_metadata ( + id integer generated always as identity, + + name text not null, + description text not null default '', + public boolean not null default false, + + owner text, + updated_by text, + + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint scheduling_goal_metadata_pkey + primary key (id) +); + +-- A partial index is used to enforce name uniqueness only on goals visible to other users +create unique index goal_name_unique_if_published on scheduling_goal_metadata (name) where public; + +comment on table scheduling_goal_metadata is e'' + 'A goal for scheduling a plan.'; +comment on column scheduling_goal_metadata.id is e'' + 'The unique identifier of the goal'; +comment on column scheduling_goal_metadata.name is e'' + 'A human-meaningful name.'; +comment on column scheduling_goal_metadata.description is e'' + 'A detailed description suitable for long-form documentation.'; +comment on column scheduling_goal_metadata.public is e'' + 'Whether this goal is visible to all users.'; +comment on column scheduling_goal_metadata.owner is e'' + 'The user responsible for this goal.'; +comment on column scheduling_goal_metadata.updated_by is e'' + 'The user who last modified this goal''s metadata.'; +comment on column scheduling_goal_metadata.created_at is e'' + 'The time at which this goal was created.'; +comment on column scheduling_goal_metadata.updated_at is e'' + 'The time at which this goal''s metadata was last modified.'; + +create function scheduling_goal_metadata_set_updated_at() +returns trigger +security definer +language plpgsql as $$begin + new.updated_at = now(); + return new; +end$$; + +create trigger set_timestamp +before update on scheduling_goal_metadata +for each row +execute function scheduling_goal_metadata_set_updated_at(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_model_specification_goals.sql b/scheduler-server/sql/scheduler/tables/scheduling_model_specification_goals.sql new file mode 100644 index 0000000000..65738a481d --- /dev/null +++ b/scheduler-server/sql/scheduler/tables/scheduling_model_specification_goals.sql @@ -0,0 +1,140 @@ +create table scheduling_model_specification_goals( + model_id integer not null, + goal_id integer not null, + goal_revision integer, -- latest is NULL + priority integer not null, + + primary key (model_id, goal_id), + foreign key (goal_id) + references scheduling_goal_metadata + on update cascade + on delete restrict, + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete restrict, + constraint model_spec_unique_goal_priorities + unique (model_id, priority) deferrable initially deferred, + constraint model_spec_nonnegative_priority + check (priority >= 0) +); + +comment on table scheduling_model_specification_goals is e'' +'The set of scheduling goals that all plans using the model should include in their scheduling specification.'; +comment on column scheduling_model_specification_goals.model_id is e'' +'The model which this specification is for. Half of the primary key.'; +comment on column scheduling_model_specification_goals.goal_id is e'' +'The id of a specific scheduling goal in the specification. Half of the primary key.'; +comment on column scheduling_model_specification_goals.goal_revision is e'' +'The version of the scheduling goal definition to use. Leave NULL to use the latest version.'; +comment on column scheduling_model_specification_goals.priority is e'' + 'The relative priority of the scheduling goal in relation to other goals on the same specification.'; + +create function insert_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_model_specification_goals smg + where smg.model_id = new.model_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for model_id % is not consecutive', new.priority, new.model_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority is null then + new.priority = next_priority; + end if; + + update scheduling_model_specification_goals + set priority = priority + 1 + where model_id = new.model_id + and priority >= new.priority; + return new; +end; +$$; + +comment on function insert_scheduling_model_specification_goal_func() is e'' + 'Checks that the inserted priority is consecutive, and reorders (increments) higher or equal priorities to make room.'; + +create trigger insert_scheduling_model_specification_goal + before insert + on scheduling_model_specification_goals + for each row +execute function insert_scheduling_model_specification_goal_func(); + +create function update_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_model_specification_goals smg + where smg.model_id = new.model_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for model_id % is not consecutive', new.priority, new.model_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority > old.priority then + update scheduling_model_specification_goals + set priority = priority - 1 + where model_id = new.model_id + and priority between old.priority + 1 and new.priority + and goal_id != new.goal_id; + else + update scheduling_model_specification_goals + set priority = priority + 1 + where model_id = new.model_id + and priority between new.priority and old.priority - 1 + and goal_id != new.goal_id; + end if; + return new; +end; +$$; + +comment on function update_scheduling_model_specification_goal_func() is e'' + 'Checks that the updated priority is consecutive, and reorders priorities to make room.'; + +create trigger update_scheduling_model_specification_goal + before update + on scheduling_model_specification_goals + for each row + when (OLD.priority is distinct from NEW.priority and pg_trigger_depth() < 1) +execute function update_scheduling_model_specification_goal_func(); + +create function delete_scheduling_model_specification_goal_func() + returns trigger + language plpgsql as $$ +begin + update scheduling_model_specification_goals + set priority = priority - 1 + where model_id = old.model_id + and priority > old.priority; + return null; +end; +$$; + +comment on function delete_scheduling_model_specification_goal_func() is e'' + 'Reorders (decrements) priorities to fill the gap from deleted priority.'; + +create trigger delete_scheduling_model_specification_goal + after delete + on scheduling_model_specification_goals + for each row +execute function delete_scheduling_model_specification_goal_func(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_specification.sql b/scheduler-server/sql/scheduler/tables/scheduling_specification.sql index e26c133da9..7e82f023c7 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_specification.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_specification.sql @@ -39,26 +39,8 @@ language plpgsql as $$begin return new; end$$; -create function increment_revision_on_goal_update() - returns trigger - security definer -language plpgsql as $$begin - with goals as ( - select g.specification_id from scheduling_specification_goals as g - where g.goal_id = new.id - ) - update scheduling_specification set revision = revision + 1 - where exists(select 1 from goals where specification_id = id); - return new; -end$$; - create trigger increment_revision_on_update_trigger before update on scheduling_specification for each row when (pg_trigger_depth() < 1) execute function increment_revision_on_update(); - -create trigger increment_revision_on_goal_update - before update on scheduling_goal - for each row - execute function increment_revision_on_goal_update(); diff --git a/scheduler-server/sql/scheduler/tables/scheduling_specification_goals.sql b/scheduler-server/sql/scheduler/tables/scheduling_specification_goals.sql index b2f7d21b2d..c39013dbbb 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_specification_goals.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_specification_goals.sql @@ -1,12 +1,9 @@ create table scheduling_specification_goals ( specification_id integer not null, goal_id integer not null, - priority integer - not null - default null -- Nulls are detected and replaced with the next - -- available priority by the insert trigger - constraint non_negative_specification_goal_priority check (priority >= 0), - enabled boolean default true, + goal_revision integer, -- latest is null + priority integer not null, + enabled boolean not null default true, simulate_after boolean not null default true, @@ -14,98 +11,74 @@ create table scheduling_specification_goals ( primary key (specification_id, goal_id), constraint scheduling_specification_goals_unique_priorities unique (specification_id, priority) deferrable initially deferred, - constraint scheduling_specification_goals_references_scheduling_specification + constraint scheduling_specification_goals_specification_exists foreign key (specification_id) references scheduling_specification on update cascade on delete cascade, - constraint scheduling_specification_goals_references_scheduling_goals + constraint non_negative_specification_goal_priority check (priority >= 0), + constraint scheduling_spec_goal_exists foreign key (goal_id) - references scheduling_goal + references scheduling_goal_metadata on update cascade - on delete cascade, - constraint scheduling_specification_unique_goal_id - unique (goal_id) + on delete restrict, + constraint scheduling_spec_goal_definition_exists + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete restrict ); comment on table scheduling_specification_goals is e'' - 'A join table associating scheduling specifications with scheduling goals.'; + 'The scheduling goals to be executed against a given plan.'; comment on column scheduling_specification_goals.specification_id is e'' - 'The ID of the scheduling specification a scheduling goal is associated with.'; + 'The plan scheduling specification this goal is on. Half of the primary key.'; comment on column scheduling_specification_goals.goal_id is e'' - 'The ID of the scheduling goal a scheduling specification is associated with.'; + 'The id of a specific goal in the specification. Half of the primary key.'; +comment on column scheduling_specification_goals.goal_revision is e'' + 'The version of the goal definition to use. Leave NULL to use the latest version.'; comment on column scheduling_specification_goals.priority is e'' 'The relative priority of a scheduling goal in relation to other ' 'scheduling goals within the same specification.'; +comment on column scheduling_specification_goals.enabled is e'' + 'Whether to run a given goal. Defaults to TRUE.'; comment on column scheduling_specification_goals.simulate_after is e'' 'Whether to re-simulate after evaluating this goal and before the next goal.'; -create or replace function insert_scheduling_specification_goal_func() - returns trigger as $$begin - if NEW.priority IS NULL then - NEW.priority = ( - select coalesce(max(priority), -1) from scheduling_specification_goals - where specification_id = NEW.specification_id - ) + 1; - elseif NEW.priority > ( - select coalesce(max(priority), -1) from scheduling_specification_goals - where specification_id = new.specification_id - ) + 1 then - raise exception 'Inserted priority % for specification_id % is not consecutive', NEW.priority, NEW.specification_id; - end if; - update scheduling_specification_goals - set priority = priority + 1 - where specification_id = NEW.specification_id - and priority >= NEW.priority; - return NEW; - end;$$ -language plpgsql; +create function insert_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_specification_goals ssg + where ssg.specification_id = new.specification_id + order by priority desc + limit 1), -1) + 1 + into next_priority; -comment on function insert_scheduling_specification_goal_func is e'' - 'Checks that the inserted priority is consecutive, and reorders (increments) higher or equal priorities to make room.'; + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for specification_id % is not consecutive', new.priority, new.specification_id), + hint = ('The next available priority is %.', next_priority); + end if; -create or replace function update_scheduling_specification_goal_func() - returns trigger as $$begin - if (pg_trigger_depth() = 1) then - if NEW.priority > OLD.priority then - if NEW.priority > ( - select coalesce(max(priority), -1) from scheduling_specification_goals - where specification_id = new.specification_id - ) + 1 then - raise exception 'Updated priority % for specification_id % is not consecutive', NEW.priority, new.specification_id; - end if; - update scheduling_specification_goals - set priority = priority - 1 - where specification_id = NEW.specification_id - and priority between OLD.priority + 1 and NEW.priority - and goal_id <> NEW.goal_id; - else - update scheduling_specification_goals - set priority = priority + 1 - where specification_id = NEW.specification_id - and priority between NEW.priority and OLD.priority - 1 - and goal_id <> NEW.goal_id; - end if; - end if; - return NEW; - end;$$ -language plpgsql; + if new.priority is null then + new.priority = next_priority; + end if; -comment on function update_scheduling_specification_goal_func is e'' - 'Checks that the updated priority is consecutive, and reorders priorities to make room.'; + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = new.specification_id + and priority >= new.priority; + return new; +end; +$$; -create function delete_scheduling_specification_goal_func() - returns trigger as $$begin - update scheduling_specification_goals - set priority = priority - 1 - where specification_id = OLD.specification_id - and priority > OLD.priority; - return null; - end;$$ -language plpgsql; - -comment on function delete_scheduling_specification_goal_func() is e'' - 'Reorders (decrements) priorities to fill the gap from deleted priority.'; +comment on function insert_scheduling_specification_goal_func is e'' + 'Checks that the inserted priority is consecutive, and reorders (increments) higher or equal priorities to make room.'; create trigger insert_scheduling_specification_goal before insert @@ -113,15 +86,100 @@ create trigger insert_scheduling_specification_goal for each row execute function insert_scheduling_specification_goal_func(); +create function update_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ + declare + next_priority integer; +begin + select coalesce( + (select priority + from scheduling_specification_goals ssg + where ssg.specification_id = new.specification_id + order by priority desc + limit 1), -1) + 1 + into next_priority; + + if new.priority > next_priority then + raise numeric_value_out_of_range using + message = ('Updated priority % for specification_id % is not consecutive', new.priority, new.specification_id), + hint = ('The next available priority is %.', next_priority); + end if; + + if new.priority > old.priority then + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = new.specification_id + and priority between old.priority + 1 and new.priority + and goal_id != new.goal_id; + else + update scheduling_specification_goals + set priority = priority + 1 + where specification_id = new.specification_id + and priority between new.priority and old.priority - 1 + and goal_id != new.goal_id; + end if; + return new; +end; +$$; + +comment on function update_scheduling_specification_goal_func is e'' + 'Checks that the updated priority is consecutive, and reorders priorities to make room.'; + create trigger update_scheduling_specification_goal before update on scheduling_specification_goals for each row - when (OLD.priority is distinct from NEW.priority) + when (OLD.priority is distinct from NEW.priority and pg_trigger_depth() < 1) execute function update_scheduling_specification_goal_func(); +create function delete_scheduling_specification_goal_func() + returns trigger + language plpgsql as $$ +begin + update scheduling_specification_goals + set priority = priority - 1 + where specification_id = old.specification_id + and priority > old.priority; + return null; +end; +$$; + +comment on function delete_scheduling_specification_goal_func() is e'' + 'Reorders (decrements) priorities to fill the gap from deleted priority.'; + create trigger delete_scheduling_specification_goal after delete on scheduling_specification_goals for each row execute function delete_scheduling_specification_goal_func(); + +create function increment_spec_revision_on_goal_spec_update() + returns trigger + security definer +language plpgsql as $$begin + update scheduling_specification + set revision = revision + 1 + where id = new.specification_id; + return new; +end$$; + +create trigger increment_revision_on_goal_update + before insert or update on scheduling_specification_goals + for each row + execute function increment_spec_revision_on_goal_spec_update(); + +create function increment_spec_revision_on_goal_spec_delete() + returns trigger + security definer +language plpgsql as $$begin + update scheduling_specification + set revision = revision + 1 + where id = old.specification_id; + return old; +end$$; + +create trigger increment_revision_on_goal_delete + before delete on scheduling_specification_goals + for each row + execute function increment_spec_revision_on_goal_spec_delete(); From 663cbc5d277600c9b3509f29924ea8da70139179 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 1 Feb 2024 16:19:24 -0800 Subject: [PATCH 03/17] Update Scheduling Request Tables - Record additional data from the specification in the request record - Update analysis tables to include the definition of the goal used --- .../down.sql | 111 +++++++++++++ .../up.sql | 154 ++++++++++++++++++ .../tables/scheduling_goal_analysis.sql | 10 +- ...uling_goal_analysis_created_activities.sql | 9 +- ...ng_goal_analysis_satisfying_activities.sql | 9 +- .../scheduler/tables/scheduling_request.sql | 55 +++++-- 6 files changed, 323 insertions(+), 25 deletions(-) diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql index a6b39092fa..86b0a91888 100644 --- a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/down.sql @@ -59,6 +59,117 @@ create trigger update_logging_on_update_scheduling_goal_trigger when (pg_trigger_depth() < 1) execute function update_logging_on_update_scheduling_goal(); +/* +ANALYSIS TABLES +*/ +/* Dropped FKs are restored first */ +alter table scheduling_goal_analysis_satisfying_activities + drop constraint satisfying_activities_references_scheduling_goal, + add constraint satisfying_activities_references_scheduling_goal + foreign key (goal_id) + references scheduling_goal + on update cascade + on delete cascade, + drop constraint satisfying_activities_primary_key, + add constraint satisfying_activities_primary_key + primary key (analysis_id, goal_id, activity_id), + drop column goal_revision; + +alter table scheduling_goal_analysis_created_activities + drop constraint created_activities_references_scheduling_goal, + add constraint created_activities_references_scheduling_goal + foreign key (goal_id) + references scheduling_goal + on update cascade + on delete cascade, + drop constraint created_activities_primary_key, + add constraint created_activities_primary_key + primary key (analysis_id, goal_id, activity_id), + drop column goal_revision; + +alter table scheduling_goal_analysis + drop constraint scheduling_goal_analysis_references_scheduling_goal, + add constraint scheduling_goal_analysis_references_scheduling_goal + foreign key (goal_id) + references scheduling_goal + on update cascade + on delete cascade, + drop constraint scheduling_goal_analysis_primary_key, + add constraint scheduling_goal_analysis_primary_key + primary key (analysis_id, goal_id), + drop column goal_revision; + +/* +SCHEDULING REQUEST +*/ +create or replace function notify_scheduler_workers () +returns trigger +security definer +language plpgsql as $$ +begin + perform ( + with payload(specification_revision, + specification_id, + analysis_id) as + ( + select NEW.specification_revision, + NEW.specification_id, + NEW.analysis_id + ) + select pg_notify('scheduling_request_notification', json_strip_nulls(row_to_json(payload))::text) + from payload + ); + return null; +end$$; + +/* These FKs are dropped ahead of the pkey swap to remove the dependency on the PK's index */ +alter table scheduling_goal_analysis_satisfying_activities + drop constraint satisfying_activities_references_scheduling_request; +alter table scheduling_goal_analysis_created_activities + drop constraint created_activities_references_scheduling_request; +alter table scheduling_goal_analysis + drop constraint scheduling_goal_analysis_references_scheduling_request; + +alter table scheduling_request + drop constraint start_before_end, + drop constraint scheduling_request_unique, + add constraint scheduling_request_analysis_unique + unique (analysis_id), + drop constraint scheduling_request_pkey, + add constraint scheduling_request_primary_key + primary key(specification_id, specification_revision), + drop column simulation_arguments, + drop column horizon_end, + drop column horizon_start, + drop column plan_revision; + +comment on column scheduling_request.canceled is null; +comment on column scheduling_request.reason is e'' + 'The reason for failure when a scheduling request fails.'; +comment on column scheduling_request.dataset_id is null; + +/* Restore dropped FKs */ +alter table scheduling_goal_analysis_satisfying_activities + add constraint satisfying_activities_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + +alter table scheduling_goal_analysis_created_activities + add constraint created_activities_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + +alter table scheduling_goal_analysis + add constraint scheduling_goal_analysis_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + /* DATA MIGRATION */ diff --git a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql index f9ff0a991f..e2e8af38f9 100644 --- a/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql +++ b/deployment/hasura/migrations/AerieScheduler/13_versioning_scheduling_goals_conditions/up.sql @@ -718,6 +718,160 @@ before update on scheduling_goal_metadata for each row execute function scheduling_goal_metadata_set_updated_at(); +/* +SCHEDULING REQUEST +*/ +/* These FKs are dropped ahead of the pkey swap to remove the dependency on analysisId's index */ +alter table scheduling_goal_analysis_satisfying_activities + drop constraint satisfying_activities_references_scheduling_request; +alter table scheduling_goal_analysis_created_activities + drop constraint created_activities_references_scheduling_request; +alter table scheduling_goal_analysis + drop constraint scheduling_goal_analysis_references_scheduling_request; + +alter table scheduling_request + add column plan_revision integer not null default -1, + add column horizon_start timestamptz, + add column horizon_end timestamptz, + add column simulation_arguments jsonb not null default '{}', + drop constraint scheduling_request_primary_key, + add constraint scheduling_request_pkey primary key(analysis_id), + drop constraint scheduling_request_analysis_unique, + add constraint scheduling_request_unique + unique (specification_id, specification_revision, plan_revision), + add constraint start_before_end + check (horizon_start <= horizon_end); + +-- Insert values from the current config for the horizon +-- This is fine as temporal subset scheduling isn't really a feature +update scheduling_request + set horizon_start = s.horizon_start, + horizon_end = s.horizon_end +from scheduling_specification s +where s.id = scheduling_request.specification_id; + +-- Drop defaults +alter table scheduling_request + alter column plan_revision drop default, + alter column horizon_start set not null, + alter column horizon_end set not null, + alter column simulation_arguments drop default ; + +comment on column scheduling_request.dataset_id is e'' + 'The dataset containing the final simulation results for the simulation. NULL if no simulations were run during scheduling.'; +comment on column scheduling_request.plan_revision is e'' + 'The revision of the plan corresponding to the given revision of the dataset.'; +comment on column scheduling_request.reason is e'' + 'The reason for failure in the event a scheduling request fails.'; +comment on column scheduling_request.canceled is e'' + 'Whether the scheduling run has been marked as canceled.'; +comment on column scheduling_request.horizon_start is e'' + 'The start of the scheduling and simulation horizon for this scheduling run.'; +comment on column scheduling_request.horizon_end is e'' + 'The end of the scheduling and simulation horizon for this scheduling run.'; +comment on column scheduling_request.simulation_arguments is e'' + 'The arguments simulations run during the scheduling run will use.'; + +/* Restore dropped FKs */ +alter table scheduling_goal_analysis_satisfying_activities + add constraint satisfying_activities_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + +alter table scheduling_goal_analysis_created_activities + add constraint created_activities_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + +alter table scheduling_goal_analysis + add constraint scheduling_goal_analysis_references_scheduling_request + foreign key (analysis_id) + references scheduling_request (analysis_id) + on update cascade + on delete cascade; + +create or replace function notify_scheduler_workers () +returns trigger +security definer +language plpgsql as $$ +begin + perform ( + with payload(specification_revision, + plan_revision, + specification_id, + analysis_id) as + ( + select NEW.specification_revision, + NEW.plan_revision, + NEW.specification_id, + NEW.analysis_id + ) + select pg_notify('scheduling_request_notification', json_strip_nulls(row_to_json(payload))::text) + from payload + ); + return null; +end$$; + +/* +ANALYSIS TABLES +*/ +/* Dropped FKs are restored first */ +/* 0 is the initial default as all revisions will be at 0 by this point in the migration */ +alter table scheduling_goal_analysis + add column goal_revision integer not null default 0, + drop constraint scheduling_goal_analysis_primary_key, + add constraint scheduling_goal_analysis_primary_key + primary key (analysis_id, goal_id, goal_revision), + drop constraint scheduling_goal_analysis_references_scheduling_goal, + add constraint scheduling_goal_analysis_references_scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete cascade; +alter table scheduling_goal_analysis + alter column goal_revision drop default; + +comment on column scheduling_goal_analysis.goal_revision is e'' + 'The associated version of the goal definition used.'; + +alter table scheduling_goal_analysis_created_activities + add column goal_revision integer not null default 0, + drop constraint created_activities_primary_key, + add constraint created_activities_primary_key + primary key (analysis_id, goal_id, goal_revision, activity_id), + drop constraint created_activities_references_scheduling_goal, + add constraint created_activities_references_scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete cascade; +alter table scheduling_goal_analysis_created_activities + alter column goal_revision drop default; + +comment on column scheduling_goal_analysis_created_activities.goal_revision is e'' + 'The associated version of the goal definition used.'; + +alter table scheduling_goal_analysis_satisfying_activities + add column goal_revision integer not null default 0, + drop constraint satisfying_activities_primary_key, + add constraint satisfying_activities_primary_key + primary key (analysis_id, goal_id, goal_revision, activity_id), + drop constraint satisfying_activities_references_scheduling_goal, + add constraint satisfying_activities_references_scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition + on update cascade + on delete cascade; +alter table scheduling_goal_analysis_satisfying_activities + alter column goal_revision drop default; + +comment on column scheduling_goal_analysis_satisfying_activities.goal_revision is e'' + 'The associated version of the goal definition used.'; + /* DROP ORIGINAL */ diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis.sql index f5cf8a9215..fd4039e465 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis.sql @@ -1,19 +1,19 @@ create table scheduling_goal_analysis ( analysis_id integer not null, goal_id integer not null, - + goal_revision integer not null, satisfied boolean not null, constraint scheduling_goal_analysis_primary_key - primary key (analysis_id, goal_id), + primary key (analysis_id, goal_id, goal_revision), constraint scheduling_goal_analysis_references_scheduling_request foreign key (analysis_id) references scheduling_request (analysis_id) on update cascade on delete cascade, constraint scheduling_goal_analysis_references_scheduling_goal - foreign key (goal_id) - references scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition on update cascade on delete cascade ); @@ -24,5 +24,7 @@ comment on column scheduling_goal_analysis.analysis_id is e'' 'The associated analysis ID.'; comment on column scheduling_goal_analysis.goal_id is e'' 'The associated goal ID.'; +comment on column scheduling_goal_analysis.goal_revision is e'' + 'The associated version of the goal definition used.'; comment on column scheduling_goal_analysis.satisfied is e'' 'Whether the associated goal was satisfied by the scheduling run.'; diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_created_activities.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_created_activities.sql index ceb09eb6cc..4d231c9cc2 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_created_activities.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_created_activities.sql @@ -1,18 +1,19 @@ create table scheduling_goal_analysis_created_activities ( analysis_id integer not null, goal_id integer not null, + goal_revision integer not null, activity_id integer not null, constraint created_activities_primary_key - primary key (analysis_id, goal_id, activity_id), + primary key (analysis_id, goal_id, goal_revision, activity_id), constraint created_activities_references_scheduling_request foreign key (analysis_id) references scheduling_request (analysis_id) on update cascade on delete cascade, constraint created_activities_references_scheduling_goal - foreign key (goal_id) - references scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition on update cascade on delete cascade ); @@ -23,5 +24,7 @@ comment on column scheduling_goal_analysis_created_activities.analysis_id is e'' 'The associated analysis ID.'; comment on column scheduling_goal_analysis_created_activities.goal_id is e'' 'The associated goal ID.'; +comment on column scheduling_goal_analysis_created_activities.goal_revision is e'' + 'The associated version of the goal definition used.'; comment on column scheduling_goal_analysis_created_activities.activity_id is e'' 'The ID of an activity instance created to satisfy the associated goal.'; diff --git a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_satisfying_activities.sql b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_satisfying_activities.sql index a9401aa110..2b88ae2069 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_satisfying_activities.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_goal_analysis_satisfying_activities.sql @@ -1,18 +1,19 @@ create table scheduling_goal_analysis_satisfying_activities ( analysis_id integer not null, goal_id integer not null, + goal_revision integer not null, activity_id integer not null, constraint satisfying_activities_primary_key - primary key (analysis_id, goal_id, activity_id), + primary key (analysis_id, goal_id, goal_revision, activity_id), constraint satisfying_activities_references_scheduling_request foreign key (analysis_id) references scheduling_request (analysis_id) on update cascade on delete cascade, constraint satisfying_activities_references_scheduling_goal - foreign key (goal_id) - references scheduling_goal + foreign key (goal_id, goal_revision) + references scheduling_goal_definition on update cascade on delete cascade ); @@ -23,5 +24,7 @@ comment on column scheduling_goal_analysis_satisfying_activities.analysis_id is 'The associated analysis ID.'; comment on column scheduling_goal_analysis_satisfying_activities.goal_id is e'' 'The associated goal ID.'; +comment on column scheduling_goal_analysis_satisfying_activities.goal_revision is e'' + 'The associated version of the goal definition used.'; comment on column scheduling_goal_analysis_satisfying_activities.activity_id is e'' 'The ID of an activity instance satisfying the associated goal.'; diff --git a/scheduler-server/sql/scheduler/tables/scheduling_request.sql b/scheduler-server/sql/scheduler/tables/scheduling_request.sql index 0586877ec3..431b1bb5d8 100644 --- a/scheduler-server/sql/scheduler/tables/scheduling_request.sql +++ b/scheduler-server/sql/scheduler/tables/scheduling_request.sql @@ -1,41 +1,64 @@ create type status_t as enum('pending', 'incomplete', 'failed', 'success'); create table scheduling_request ( - specification_id integer not null, analysis_id integer generated always as identity, - requested_by text, - requested_at timestamptz not null default now(), + specification_id integer not null, + dataset_id integer default null, + specification_revision integer not null, + plan_revision integer not null, + + -- Scheduling State status status_t not null default 'pending', reason jsonb null, canceled boolean not null default false, - dataset_id integer default null, - specification_revision integer not null, + -- Simulation Arguments Used in Scheduling + horizon_start timestamptz not null, + horizon_end timestamptz not null, + simulation_arguments jsonb not null, + + -- Additional Metadata + requested_by text, + requested_at timestamptz not null default now(), - constraint scheduling_request_primary_key - primary key(specification_id, specification_revision), - constraint scheduling_request_analysis_unique - unique (analysis_id), + constraint scheduling_request_pkey + primary key(analysis_id), + constraint scheduling_request_unique + unique (specification_id, specification_revision, plan_revision), constraint scheduling_request_references_scheduling_specification foreign key(specification_id) references scheduling_specification on update cascade - on delete cascade + on delete cascade, + constraint start_before_end + check (horizon_start <= horizon_end) ); comment on table scheduling_request is e'' 'The status of a scheduling run that is to be performed (or has been performed).'; -comment on column scheduling_request.specification_id is e'' - 'The ID of scheduling specification for this scheduling run.'; comment on column scheduling_request.analysis_id is e'' 'The ID associated with the analysis of this scheduling run.'; +comment on column scheduling_request.specification_id is e'' + 'The ID of scheduling specification for this scheduling run.'; +comment on column scheduling_request.dataset_id is e'' + 'The dataset containing the final simulation results for the simulation. NULL if no simulations were run during scheduling.'; +comment on column scheduling_request.specification_revision is e'' + 'The revision of the scheduling_specification associated with this request.'; +comment on column scheduling_request.plan_revision is e'' + 'The revision of the plan corresponding to the given revision of the dataset.'; comment on column scheduling_request.status is e'' 'The state of the the scheduling request.'; comment on column scheduling_request.reason is e'' - 'The reason for failure when a scheduling request fails.'; -comment on column scheduling_request.specification_revision is e'' - 'The revision of the scheduling_specification associated with this request.'; + 'The reason for failure in the event a scheduling request fails.'; +comment on column scheduling_request.canceled is e'' + 'Whether the scheduling run has been marked as canceled.'; +comment on column scheduling_request.horizon_start is e'' + 'The start of the scheduling and simulation horizon for this scheduling run.'; +comment on column scheduling_request.horizon_end is e'' + 'The end of the scheduling and simulation horizon for this scheduling run.'; +comment on column scheduling_request.simulation_arguments is e'' + 'The arguments simulations run during the scheduling run will use.'; comment on column scheduling_request.requested_by is e'' 'The user who made the scheduling request.'; comment on column scheduling_request.requested_at is e'' @@ -51,10 +74,12 @@ language plpgsql as $$ begin perform ( with payload(specification_revision, + plan_revision, specification_id, analysis_id) as ( select NEW.specification_revision, + NEW.plan_revision, NEW.specification_id, NEW.analysis_id ) From f41e20c69bcb89e6e4af166577197e65550f1b7f Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Mon, 29 Jan 2024 12:54:47 -0800 Subject: [PATCH 04/17] Update Scheduler DB init code --- .../sql/scheduler/applied_migrations.sql | 1 + scheduler-server/sql/scheduler/init.sql | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/scheduler-server/sql/scheduler/applied_migrations.sql b/scheduler-server/sql/scheduler/applied_migrations.sql index 347ff9e5ab..e09c03fe65 100644 --- a/scheduler-server/sql/scheduler/applied_migrations.sql +++ b/scheduler-server/sql/scheduler/applied_migrations.sql @@ -15,3 +15,4 @@ call migrations.mark_migration_applied('9'); call migrations.mark_migration_applied('10'); call migrations.mark_migration_applied('11'); call migrations.mark_migration_applied('12'); +call migrations.mark_migration_applied('13'); diff --git a/scheduler-server/sql/scheduler/init.sql b/scheduler-server/sql/scheduler/init.sql index 0bc728cb22..4eb1ac9463 100644 --- a/scheduler-server/sql/scheduler/init.sql +++ b/scheduler-server/sql/scheduler/init.sql @@ -7,17 +7,30 @@ begin; \ir tables/schema_migrations.sql \ir applied_migrations.sql - -- Scheduling intents. - \ir tables/scheduling_goal.sql + -- Scheduling Goals + \ir tables/scheduling_goal_metadata.sql + \ir tables/scheduling_goal_definition.sql + + -- Scheduling Conditions + \ir tables/scheduling_condition_metadata.sql + \ir tables/scheduling_condition_definition.sql + + -- Scheduling Specification \ir tables/scheduling_specification.sql \ir tables/scheduling_specification_goals.sql + \ir tables/scheduling_specification_conditions.sql + \ir tables/scheduling_model_specification_conditions.sql + \ir tables/scheduling_model_specification_goals.sql + + -- Scheduling Output \ir tables/scheduling_request.sql \ir tables/scheduling_goal_analysis.sql \ir tables/scheduling_goal_analysis_created_activities.sql \ir tables/scheduling_goal_analysis_satisfying_activities.sql - \ir tables/scheduling_condition.sql - \ir tables/scheduling_specification_conditions.sql -- Table-specific Metadata \ir tables/metadata/scheduling_goal_tags.sql + \ir tables/metadata/scheduling_goal_definition_tags.sql + \ir tables/metadata/scheduling_condition_tags.sql + \ir tables/metadata/scheduling_condition_definition_tags.sql end; From fa64348e99f67c4cf1cee429f361fe35b8eac93c Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Tue, 30 Jan 2024 13:56:45 -0800 Subject: [PATCH 05/17] Hasura Metadata --- .../tables/public_activity_directive.yaml | 2 +- .../scheduling_condition_definition_tags.yaml | 58 ++++++++++++ .../metadata/scheduling_condition_tags.yaml | 52 +++++++++++ .../scheduling_goal_definition_tags.yaml | 58 ++++++++++++ .../tables/metadata/scheduling_goal_tags.yaml | 6 +- ...ublic_scheduling_condition_definition.yaml | 80 ++++++++++++++++ .../public_scheduling_condition_metadata.yaml | 84 +++++++++++++++++ .../tables/public_scheduling_goal.yaml | 91 ------------------- .../public_scheduling_goal_analysis.yaml | 14 ++- .../public_scheduling_goal_definition.yaml | 80 ++++++++++++++++ .../public_scheduling_goal_metadata.yaml | 84 +++++++++++++++++ ...uling_model_specification_conditions.yaml} | 35 +++---- ..._scheduling_model_specification_goals.yaml | 65 +++++++++++++ ...c_scheduling_specification_conditions.yaml | 25 +++-- ...public_scheduling_specification_goals.yaml | 25 +++-- .../AerieScheduler/tables/tables.yaml | 11 ++- 16 files changed, 629 insertions(+), 141 deletions(-) create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_definition_tags.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_tags.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_definition_tags.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_definition.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_metadata.yaml delete mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml rename deployment/hasura/metadata/databases/AerieScheduler/tables/{public_scheduling_condition.yaml => public_scheduling_model_specification_conditions.yaml} (56%) create mode 100644 deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_goals.yaml diff --git a/deployment/hasura/metadata/databases/AerieMerlin/tables/public_activity_directive.yaml b/deployment/hasura/metadata/databases/AerieMerlin/tables/public_activity_directive.yaml index 288a6cd887..c4e2bf5cf1 100644 --- a/deployment/hasura/metadata/databases/AerieMerlin/tables/public_activity_directive.yaml +++ b/deployment/hasura/metadata/databases/AerieMerlin/tables/public_activity_directive.yaml @@ -70,7 +70,7 @@ remote_relationships: source: AerieScheduler table: schema: public - name: scheduling_goal + name: scheduling_goal_metadata field_mapping: source_scheduling_goal_id: id select_permissions: diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_definition_tags.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_definition_tags.yaml new file mode 100644 index 0000000000..142301412f --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_definition_tags.yaml @@ -0,0 +1,58 @@ +table: + name: scheduling_condition_definition_tags + schema: metadata +configuration: + custom_name: "scheduling_condition_definition_tags" +object_relationships: + - name: condition_definition + using: + foreign_key_constraint_on: + - condition_id + - condition_revision +remote_relationships: + - name: tag + definition: + to_source: + relationship_type: object + source: AerieMerlin + table: + schema: metadata + name: tags + field_mapping: + tag_id: id +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [condition_id, condition_revision, tag_id] + check: {} + - role: user + permission: + columns: [condition_id, condition_revision, tag_id] + check: {"condition_definition":{"_or":[ + {"author":{"_eq":"X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]}} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: {"condition_definition":{"_or":[ + {"author":{"_eq":"X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]}} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_tags.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_tags.yaml new file mode 100644 index 0000000000..eab069b8eb --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_condition_tags.yaml @@ -0,0 +1,52 @@ +table: + name: scheduling_condition_tags + schema: metadata +configuration: + custom_name: "scheduling_condition_tags" +object_relationships: + - name: condition_metadata + using: + foreign_key_constraint_on: condition_id +remote_relationships: + - name: tag + definition: + to_source: + relationship_type: object + source: AerieMerlin + table: + schema: metadata + name: tags + field_mapping: + tag_id: id +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [condition_id, tag_id] + check: {} + - role: user + permission: + columns: [condition_id, tag_id] + check: {"condition_metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: {"condition_metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_definition_tags.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_definition_tags.yaml new file mode 100644 index 0000000000..a75ff73f3e --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_definition_tags.yaml @@ -0,0 +1,58 @@ +table: + name: scheduling_goal_definition_tags + schema: metadata +configuration: + custom_name: "scheduling_goal_definition_tags" +object_relationships: + - name: goal_definition + using: + foreign_key_constraint_on: + - goal_id + - goal_revision +remote_relationships: + - name: tag + definition: + to_source: + relationship_type: object + source: AerieMerlin + table: + schema: metadata + name: tags + field_mapping: + tag_id: id +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [goal_id, goal_revision, tag_id] + check: {} + - role: user + permission: + columns: [goal_id, goal_revision, tag_id] + check: {"goal_definition":{"_or":[ + {"author":{"_eq":"X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]}} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: {"goal_definition":{"_or":[ + {"author":{"_eq":"X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]}} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_tags.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_tags.yaml index 98e03b3855..1a1c49c8c6 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_tags.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/metadata/scheduling_goal_tags.yaml @@ -4,7 +4,7 @@ table: configuration: custom_name: "scheduling_goal_tags" object_relationships: - - name: scheduling_goal + - name: goal_metadata using: foreign_key_constraint_on: goal_id remote_relationships: @@ -42,11 +42,11 @@ insert_permissions: - role: user permission: columns: [goal_id, tag_id] - check: {} + check: {"goal_metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}} delete_permissions: - role: aerie_admin permission: filter: {} - role: user permission: - filter: {} + filter: {"goal_metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_definition.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_definition.yaml new file mode 100644 index 0000000000..47fc8119c6 --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_definition.yaml @@ -0,0 +1,80 @@ +table: + name: scheduling_condition_definition + schema: public +object_relationships: + - name: metadata + using: + foreign_key_constraint_on: condition_id +array_relationships: + - name: tags + using: + foreign_key_constraint_on: + columns: + - condition_id + - condition_revision + table: + name: scheduling_condition_definition_tags + schema: metadata + - name: models_using + using: + foreign_key_constraint_on: + columns: + - condition_id + - condition_revision + table: + name: scheduling_model_specification_conditions + schema: public + - name: plans_using + using: + foreign_key_constraint_on: + columns: + - condition_id + - condition_revision + table: + name: scheduling_specification_conditions + schema: public +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' +# This should have filtering based on privacy, but cross-database permissions restrictions prevent that + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [condition_id, definition] + check: {} + set: + author: "x-hasura-user-id" + - role: user + permission: + columns: [condition_id, definition] + check: {"_or":[{"metadata":{"public":{"_eq":true}}},{"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]} + set: + author: "x-hasura-user-id" +update_permissions: + - role: aerie_admin + permission: + columns: [definition, author] + filter: {} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: + {"_or":[ + {"author": {"_eq": "X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_metadata.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_metadata.yaml new file mode 100644 index 0000000000..163317aa07 --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition_metadata.yaml @@ -0,0 +1,84 @@ +table: + name: scheduling_condition_metadata + schema: public +array_relationships: + - name: tags + using: + foreign_key_constraint_on: + column: condition_id + table: + name: scheduling_condition_tags + schema: metadata + - name: versions + using: + foreign_key_constraint_on: + column: condition_id + table: + name: scheduling_condition_definition + schema: public + - name: models_using + using: + foreign_key_constraint_on: + column: condition_id + table: + name: scheduling_model_specification_conditions + schema: public + - name: plans_using + using: + foreign_key_constraint_on: + column: condition_id + table: + name: scheduling_specification_conditions + schema: public +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' +# This should have filtering based on privacy, but cross-database permissions restrictions prevent that + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [name, description, public] + check: {} + set: + owner: "x-hasura-user-id" + updated_by: "x-hasura-user-id" + - role: user + permission: + columns: [name, description, public] + check: {} + set: + owner: "x-hasura-user-id" + updated_by: "x-hasura-user-id" +update_permissions: + - role: aerie_admin + permission: + columns: [name, description, public, owner] + filter: {} + set: + updated_by: "x-hasura-user-id" + - role: user + permission: + columns: [name, description, public, owner] + filter: { "owner": { "_eq": "X-Hasura-User-Id" } } + set: + updated_by: "x-hasura-user-id" +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: { "owner": { "_eq": "X-Hasura-User-Id" } } diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal.yaml deleted file mode 100644 index cf7720e5a0..0000000000 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal.yaml +++ /dev/null @@ -1,91 +0,0 @@ -table: - name: scheduling_goal - schema: public -object_relationships: - - name: scheduling_specification_goal - using: - foreign_key_constraint_on: - column: goal_id - table: - name: scheduling_specification_goals - schema: public -array_relationships: -- name: analyses - using: - foreign_key_constraint_on: - column: goal_id - table: - name: scheduling_goal_analysis - schema: public -- name: tags - using: - manual_configuration: - remote_table: - name: scheduling_goal_tags - schema: metadata - insertion_order: null - column_mapping: - id: goal_id -remote_relationships: -- name: model - definition: - to_source: - relationship_type: object - source: AerieMerlin - table: - schema: public - name: mission_model - field_mapping: - model_id: id -select_permissions: - - role: aerie_admin - permission: - columns: '*' - filter: {} - allow_aggregations: true - - role: user - permission: - columns: '*' - filter: {} - allow_aggregations: true - - role: viewer - permission: - columns: '*' - filter: {} - allow_aggregations: true -# TODO: Modify these once we have a solution for cross-db auth (These permissions should be based on plan ownership/collaboratorship) -insert_permissions: - - role: aerie_admin - permission: - columns: [name, definition, model_id, description] - check: {} - set: - author: "x-hasura-user-id" - last_modified_by: "x-hasura-user-id" - - role: user - permission: - columns: [name, definition, model_id, description] - check: {} - set: - author: "x-hasura-user-id" - last_modified_by: "x-hasura-user-id" -update_permissions: - - role: aerie_admin - permission: - columns: [name, definition, model_id, description] - filter: {} - set: - last_modified_by: "x-hasura-user-id" - - role: user - permission: - columns: [name, definition, description] - filter: {} - set: - last_modified_by: "x-hasura-user-id" -delete_permissions: - - role: aerie_admin - permission: - filter: {} - - role: user - permission: - filter: {} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_analysis.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_analysis.yaml index 590ff89be1..c22c697185 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_analysis.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_analysis.yaml @@ -11,9 +11,19 @@ object_relationships: insertion_order: null column_mapping: analysis_id: analysis_id -- name: goal +- name: goal_metadata using: - foreign_key_constraint_on: goal_id + manual_configuration: + column_mapping: + goal_id: id + remote_table: + name: scheduling_goal_metadata + schema: public +- name: goal_definition + using: + foreign_key_constraint_on: + - goal_id + - goal_revision array_relationships: - name: satisfying_activities using: diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml new file mode 100644 index 0000000000..9f57b83529 --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml @@ -0,0 +1,80 @@ +table: + name: scheduling_goal_definition + schema: public +object_relationships: + - name: metadata + using: + foreign_key_constraint_on: goal_id +array_relationships: + - name: tags + using: + foreign_key_constraint_on: + columns: + - goal_id + - goal_revision + table: + name: scheduling_goal_definition_tags + schema: metadata + - name: models_using + using: + foreign_key_constraint_on: + columns: + - goal_id + - goal_revision + table: + name: scheduling_model_specification_goals + schema: public + - name: plans_using + using: + foreign_key_constraint_on: + columns: + - goal_id + - goal_revision + table: + name: scheduling_specification_goals + schema: public +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' +# This should have filtering based on privacy, but cross-database permissions restrictions prevent that + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [goal_id, definition] + check: {} + set: + author: "x-hasura-user-id" + - role: user + permission: + columns: [goal_id, definition] + check: {"_or":[{"metadata":{"public":{"_eq":true}}},{"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]} + set: + author: "x-hasura-user-id" +update_permissions: + - role: aerie_admin + permission: + columns: [definition, author] + filter: {} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: + {"_or":[ + {"author": {"_eq": "X-Hasura-User-Id"}}, + {"metadata":{"owner":{"_eq":"X-Hasura-User-Id"}}}]} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml new file mode 100644 index 0000000000..ddfe1b6a0b --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml @@ -0,0 +1,84 @@ +table: + name: scheduling_goal_metadata + schema: public +array_relationships: + - name: tags + using: + foreign_key_constraint_on: + column: goal_id + table: + name: scheduling_goal_tags + schema: metadata + - name: versions + using: + foreign_key_constraint_on: + column: goal_id + table: + name: scheduling_goal_definition + schema: public + - name: models_using + using: + foreign_key_constraint_on: + column: goal_id + table: + name: scheduling_model_specification_goals + schema: public + - name: plans_using + using: + foreign_key_constraint_on: + column: goal_id + table: + name: scheduling_specification_goals + schema: public +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' +# This should have filtering based on privacy, but cross-database permissions restrictions prevent that + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +insert_permissions: + - role: aerie_admin + permission: + columns: [name, description, public] + check: {} + set: + owner: "x-hasura-user-id" + updated_by: "x-hasura-user-id" + - role: user + permission: + columns: [name, description, public] + check: {} + set: + owner: "x-hasura-user-id" + updated_by: "x-hasura-user-id" +update_permissions: + - role: aerie_admin + permission: + columns: [name, description, public, owner] + filter: {} + set: + updated_by: "x-hasura-user-id" + - role: user + permission: + columns: [name, description, public, owner] + filter: { "owner": { "_eq": "X-Hasura-User-Id" } } + set: + updated_by: "x-hasura-user-id" +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: { "owner": { "_eq": "X-Hasura-User-Id" } } diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_conditions.yaml similarity index 56% rename from deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition.yaml rename to deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_conditions.yaml index fdb37c7dce..034c84ef91 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_condition.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_conditions.yaml @@ -1,14 +1,15 @@ table: - name: scheduling_condition + name: scheduling_model_specification_conditions schema: public -array_relationships: - - name: scheduling_specification_conditions +object_relationships: + - name: condition_metadata + using: + foreign_key_constraint_on: condition_id + - name: condition_definition using: foreign_key_constraint_on: - column: condition_id - table: - name: scheduling_specification_conditions - schema: public + - condition_id + - condition_revision remote_relationships: - name: model definition: @@ -36,35 +37,25 @@ select_permissions: columns: '*' filter: {} allow_aggregations: true -# TODO: Modify these once we have a solution for cross-db auth (These permissions should be based on plan ownership/collaboratorship) +# TODO: Modify these once we have a solution for cross-db auth (These permissions should be based on model ownership) insert_permissions: - role: aerie_admin permission: - columns: [name, definition, model_id, description] + columns: [model_id, condition_id, condition_revision] check: {} - set: - author: "x-hasura-user-id" - last_modified_by: "x-hasura-user-id" - role: user permission: - columns: [name, definition, model_id, description] + columns: [model_id, condition_id, condition_revision] check: {} - set: - author: "x-hasura-user-id" - last_modified_by: "x-hasura-user-id" update_permissions: - role: aerie_admin permission: - columns: [name, definition, description, model_id] + columns: [condition_revision] filter: {} - set: - last_modified_by: "x-hasura-user-id" - role: user permission: - columns: [name, definition, description] + columns: [condition_revision] filter: {} - set: - last_modified_by: "x-hasura-user-id" delete_permissions: - role: aerie_admin permission: diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_goals.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_goals.yaml new file mode 100644 index 0000000000..3e80cf581e --- /dev/null +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_model_specification_goals.yaml @@ -0,0 +1,65 @@ +table: + name: scheduling_model_specification_goals + schema: public +object_relationships: + - name: goal_metadata + using: + foreign_key_constraint_on: goal_id + - name: goal_definition + using: + foreign_key_constraint_on: + - goal_id + - goal_revision +remote_relationships: +- name: model + definition: + to_source: + relationship_type: object + source: AerieMerlin + table: + schema: public + name: mission_model + field_mapping: + model_id: id +select_permissions: + - role: aerie_admin + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: user + permission: + columns: '*' + filter: {} + allow_aggregations: true + - role: viewer + permission: + columns: '*' + filter: {} + allow_aggregations: true +# TODO: Modify these once we have a solution for cross-db auth (These permissions should be based on model ownership) +insert_permissions: + - role: aerie_admin + permission: + columns: [model_id, goal_id, goal_revision] + check: {} + - role: user + permission: + columns: [model_id, goal_id, goal_revision] + check: {} +update_permissions: + - role: aerie_admin + permission: + columns: [goal_revision] + filter: {} + - role: user + permission: + columns: [goal_revision] + filter: {} +delete_permissions: + - role: aerie_admin + permission: + filter: {} + - role: user + permission: + filter: {} diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_conditions.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_conditions.yaml index 8f4a672ae2..2f60212f94 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_conditions.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_conditions.yaml @@ -2,12 +2,17 @@ table: name: scheduling_specification_conditions schema: public object_relationships: -- name: condition - using: - foreign_key_constraint_on: condition_id -- name: specification - using: - foreign_key_constraint_on: specification_id + - name: condition_metadata + using: + foreign_key_constraint_on: condition_id + - name: condition_definition + using: + foreign_key_constraint_on: + - condition_id + - condition_revision + - name: specification + using: + foreign_key_constraint_on: specification_id select_permissions: - role: aerie_admin permission: @@ -28,20 +33,20 @@ select_permissions: insert_permissions: - role: aerie_admin permission: - columns: [specification_id, condition_id, enabled] + columns: [specification_id, condition_id, condition_revision, enabled] check: {} - role: user permission: - columns: [specification_id, condition_id, enabled] + columns: [specification_id, condition_id, condition_revision, enabled] check: {} update_permissions: - role: aerie_admin permission: - columns: [specification_id, condition_id, enabled] + columns: [condition_revision, enabled] filter: {} - role: user permission: - columns: [enabled] + columns: [condition_revision, enabled] filter: {} delete_permissions: - role: aerie_admin diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml index d82486e5c6..c71679a788 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml @@ -2,12 +2,17 @@ table: name: scheduling_specification_goals schema: public object_relationships: -- name: goal - using: - foreign_key_constraint_on: goal_id -- name: specification - using: - foreign_key_constraint_on: specification_id + - name: goal_metadata + using: + foreign_key_constraint_on: goal_id + - name: goal_definition + using: + foreign_key_constraint_on: + - goal_id + - goal_revision + - name: specification + using: + foreign_key_constraint_on: specification_id select_permissions: - role: aerie_admin permission: @@ -28,20 +33,20 @@ select_permissions: insert_permissions: - role: aerie_admin permission: - columns: [specification_id, goal_id, priority, enabled, simulate_after] + columns: [specification_id, goal_id, goal_revision, priority, enabled] check: {} - role: user permission: - columns: [specification_id, goal_id, priority, enabled, simulate_after] + columns: [specification_id, goal_id, goal_revision, priority, enabled] check: {} update_permissions: - role: aerie_admin permission: - columns: [specification_id, goal_id, priority, enabled, simulate_after] + columns: [goal_revision, priority, enabled] filter: {} - role: user permission: - columns: [priority, enabled, simulate_after] + columns: [goal_revision, priority, enabled] filter: {} delete_permissions: - role: aerie_admin diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/tables.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/tables.yaml index b6303b348d..ec6669c746 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/tables.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/tables.yaml @@ -1,11 +1,18 @@ -- "!include public_scheduling_condition.yaml" -- "!include public_scheduling_goal.yaml" +- "!include public_scheduling_condition_definition.yaml" +- "!include public_scheduling_condition_metadata.yaml" +- "!include public_scheduling_goal_definition.yaml" +- "!include public_scheduling_goal_metadata.yaml" - "!include public_scheduling_goal_analysis.yaml" - "!include public_scheduling_goal_analysis_created_activities.yaml" - "!include public_scheduling_goal_analysis_satisfying_activities.yaml" +- "!include public_scheduling_model_specification_goals.yaml" +- "!include public_scheduling_model_specification_conditions.yaml" - "!include public_scheduling_request.yaml" - "!include public_scheduling_specification.yaml" - "!include public_scheduling_specification_goals.yaml" - "!include public_scheduling_specification_conditions.yaml" # Metadata +- "!include metadata/scheduling_condition_tags.yaml" +- "!include metadata/scheduling_condition_definition_tags.yaml" - "!include metadata/scheduling_goal_tags.yaml" +- "!include metadata/scheduling_goal_definition_tags.yaml" From 65bb1eece6084e68adf7048a80594a32611fd86e Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 12:32:30 -0800 Subject: [PATCH 06/17] Update DBTests --- .../database/SchedulerDatabaseTests.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/db-tests/src/test/java/gov/nasa/jpl/aerie/database/SchedulerDatabaseTests.java b/db-tests/src/test/java/gov/nasa/jpl/aerie/database/SchedulerDatabaseTests.java index b2a3a04256..0368bcbe6e 100644 --- a/db-tests/src/test/java/gov/nasa/jpl/aerie/database/SchedulerDatabaseTests.java +++ b/db-tests/src/test/java/gov/nasa/jpl/aerie/database/SchedulerDatabaseTests.java @@ -49,16 +49,21 @@ insert into scheduling_specification( int insertGoal() throws SQLException { try (final var statement = connection.createStatement()) { final var res = statement.executeQuery(""" - insert into scheduling_goal( - revision, name, definition, model_id, description, author, last_modified_by, created_date, modified_date - ) values (0, 'goal', 'does thing', 0, 'hey there', 'its me', 'also me', now(), now()) returning id; + with metadata(id, owner) as ( + insert into scheduling_goal_metadata(name, description, owner, updated_by) + values ('test goal', 'no-op', 'scheduler db tests', 'scheduler db tests') + returning id, owner + ) + insert into scheduling_goal_definition(goal_id, definition, author) + select m.id, 'nothing', m.owner + from metadata m + returning goal_id as id; """); res.next(); return res.getInt("id"); } } - @Nested class TestSpecificationAndTemplateGoalTriggers { int[] specificationIds; @@ -73,7 +78,8 @@ void beforeEach() throws SQLException { @AfterEach void afterEach() throws SQLException { helper.clearTable("scheduling_specification"); - helper.clearTable("scheduling_goal"); + helper.clearTable("scheduling_goal_metadata"); + helper.clearTable("scheduling_goal_definition"); helper.clearTable("scheduling_specification_goals"); } @@ -143,12 +149,13 @@ private int getSpecificationRevision(int specificationId) throws SQLException { } @Test - void shouldIncrementSpecRevisionAfterModifyingGoal() throws SQLException { + void shouldIncrementSpecRevisionAfterModifyingGoalSpec() throws SQLException { insertGoalPriorities(0, new int[] {0, 1, 2, 3, 4}, new int[]{0, 1, 2, 3, 4}); final var revisionBefore = getSpecificationRevision(specificationIds[0]); connection.createStatement().executeUpdate(""" - update scheduling_goal - set name = 'other name' where id = %d; + update scheduling_specification_goals + set goal_revision = 0 + where goal_id = %d; """.formatted(goalIds[3])); final var revisionAfter = getSpecificationRevision(specificationIds[0]); assertEquals(revisionBefore + 1, revisionAfter); From 2f136be8d03e9758a4b1a1b43047bb683c00c79f Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 12:41:02 -0800 Subject: [PATCH 07/17] Remove `InMemoryStore` option - The Scheduler uses a `Mock` setup for tests. Additionally, this code is completely unsupported --- .../scheduler/server/SchedulerAppDriver.java | 9 ----- .../server/config/InMemoryStore.java | 3 -- .../aerie/scheduler/server/config/Store.java | 2 +- .../mocks/InMemoryResultsCellRepository.java | 33 ------------------- .../InMemorySpecificationRepository.java | 22 ------------- 5 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/InMemoryStore.java delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemoryResultsCellRepository.java delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemorySpecificationRepository.java diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java index 3f87b46791..3ebba98005 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java @@ -6,12 +6,9 @@ import gov.nasa.jpl.aerie.permissions.PermissionsService; import gov.nasa.jpl.aerie.permissions.gql.GraphQLPermissionsService; import gov.nasa.jpl.aerie.scheduler.server.config.AppConfiguration; -import gov.nasa.jpl.aerie.scheduler.server.config.InMemoryStore; import gov.nasa.jpl.aerie.scheduler.server.config.PostgresStore; import gov.nasa.jpl.aerie.scheduler.server.config.Store; import gov.nasa.jpl.aerie.scheduler.server.http.SchedulerBindings; -import gov.nasa.jpl.aerie.scheduler.server.mocks.InMemoryResultsCellRepository; -import gov.nasa.jpl.aerie.scheduler.server.mocks.InMemorySpecificationRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PostgresResultsCellRepository; @@ -115,12 +112,6 @@ private static Stores loadStores( return new Stores( new PostgresSpecificationRepository(hikariDataSource), new PostgresResultsCellRepository(hikariDataSource)); - } else if (store instanceof InMemoryStore) { - final var inMemorySchedulerRepository = new InMemorySpecificationRepository(); - return new Stores( - inMemorySchedulerRepository, - new InMemoryResultsCellRepository(inMemorySchedulerRepository)); - } else { throw new UnexpectedSubtypeError(Store.class, store); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/InMemoryStore.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/InMemoryStore.java deleted file mode 100644 index f7d5f5fb01..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/InMemoryStore.java +++ /dev/null @@ -1,3 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.config; - -public record InMemoryStore() implements Store { } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/Store.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/Store.java index 5ecd9d09df..eac491b853 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/Store.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/config/Store.java @@ -1,3 +1,3 @@ package gov.nasa.jpl.aerie.scheduler.server.config; -public sealed interface Store permits PostgresStore, InMemoryStore { } +public sealed interface Store permits PostgresStore { } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemoryResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemoryResultsCellRepository.java deleted file mode 100644 index a096305e84..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemoryResultsCellRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.mocks; - -import java.util.Optional; -import gov.nasa.jpl.aerie.scheduler.server.ResultsProtocol; -import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; -import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; - -public record InMemoryResultsCellRepository(SpecificationRepository specificationRepository) implements ResultsCellRepository { - @Override - public ResultsProtocol.OwnerRole allocate(final SpecificationId specificationId, final String requestedBy) - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } - - @Override - public Optional claim(SpecificationId specificationId) - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } - - @Override - public Optional lookup(final SpecificationId specificationId) - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } - - @Override - public void deallocate(final ResultsProtocol.OwnerRole resultsCell) - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } -} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemorySpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemorySpecificationRepository.java deleted file mode 100644 index 87fd35a580..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/mocks/InMemorySpecificationRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.mocks; - -import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.models.Specification; -import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; - -public final class InMemorySpecificationRepository implements SpecificationRepository { - @Override - public Specification getSpecification(final SpecificationId specificationId) throws NoSuchSpecificationException - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } - - @Override - public RevisionData getSpecificationRevisionData(final SpecificationId specificationId) - throws NoSuchSpecificationException - { - throw new UnsupportedOperationException(); // TODO stubbed method must be implemented - } -} From f7cac71a0f2699addb00af5b803e3ca409db5aa3 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 12:46:46 -0800 Subject: [PATCH 08/17] Simplify Scheduler Services - `MockSpecificationService` was really a `MockSpecificationRepository`, as indicated by its private field that was a repository of Specifications - The one remaining implementation of `SpecificationService` deferred exclusively to the implementation of its repository, so `SpecificationService` was flattened - The above applies for `SchedulerService` as well --- .../scheduler/server/SchedulerAppDriver.java | 8 +++---- .../services/CachedSchedulerService.java | 24 ------------------- .../services/LocalSpecificationService.java | 23 ------------------ .../server/services/SchedulerService.java | 17 ++++++++++--- .../server/services/SpecificationService.java | 17 ++++++++++--- .../worker/SchedulerWorkerAppDriver.java | 4 ++-- ....java => MockSpecificationRepository.java} | 17 ++++++------- .../services/SchedulingIntegrationTests.java | 3 ++- 8 files changed, 45 insertions(+), 68 deletions(-) delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/CachedSchedulerService.java delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/LocalSpecificationService.java rename scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/{MockSpecificationService.java => MockSpecificationRepository.java} (50%) diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java index 3ebba98005..6fa88c4f91 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/SchedulerAppDriver.java @@ -13,11 +13,11 @@ import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PostgresResultsCellRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PostgresSpecificationRepository; -import gov.nasa.jpl.aerie.scheduler.server.services.CachedSchedulerService; import gov.nasa.jpl.aerie.scheduler.server.services.GenerateSchedulingLibAction; import gov.nasa.jpl.aerie.scheduler.server.services.GraphQLMerlinService; -import gov.nasa.jpl.aerie.scheduler.server.services.LocalSpecificationService; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleAction; +import gov.nasa.jpl.aerie.scheduler.server.services.SchedulerService; +import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; import gov.nasa.jpl.aerie.scheduler.server.services.UnexpectedSubtypeError; import io.javalin.Javalin; import org.eclipse.jetty.server.Connector; @@ -54,8 +54,8 @@ public static void main(final String[] args) { final var stores = loadStores(config); //create objects in each service abstraction layer (mirroring MerlinApp) - final var specificationService = new LocalSpecificationService(stores.specifications()); - final var schedulerService = new CachedSchedulerService(stores.results()); + final var specificationService = new SpecificationService(stores.specifications()); + final var schedulerService = new SchedulerService(stores.results()); final var scheduleAction = new ScheduleAction(specificationService, schedulerService); final var generateSchedulingLibAction = new GenerateSchedulingLibAction(merlinService); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/CachedSchedulerService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/CachedSchedulerService.java deleted file mode 100644 index 3910ca94af..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/CachedSchedulerService.java +++ /dev/null @@ -1,24 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.services; - -import gov.nasa.jpl.aerie.scheduler.server.ResultsProtocol; -import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; - -public record CachedSchedulerService( - ResultsCellRepository store -) implements SchedulerService { - - @Override - public ResultsProtocol.State getScheduleResults(final ScheduleRequest request, final String requestedBy) { - final var specificationId = request.specificationId(); - final var cell$ = this.store.lookup(specificationId); - if (cell$.isPresent()) { - return cell$.get().get(); - } else { - // Allocate a fresh cell. - final var cell = this.store.allocate(specificationId, requestedBy); - - // Return the current value of the reader; if it's incomplete, the caller can check it again later. - return cell.get(); - } - } -} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/LocalSpecificationService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/LocalSpecificationService.java deleted file mode 100644 index 26bd5c1cc8..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/LocalSpecificationService.java +++ /dev/null @@ -1,23 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.services; - -import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; -import gov.nasa.jpl.aerie.scheduler.server.models.Specification; -import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; - -public record LocalSpecificationService(SpecificationRepository specificationRepository) implements SpecificationService { - @Override - public Specification getSpecification(final SpecificationId specificationId) - throws NoSuchSpecificationException, SpecificationLoadException - { - return specificationRepository.getSpecification(specificationId); - } - - @Override - public RevisionData getSpecificationRevisionData(final SpecificationId specificationId) - throws NoSuchSpecificationException - { - return specificationRepository.getSpecificationRevisionData(specificationId); - } -} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SchedulerService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SchedulerService.java index 6a40223998..7fb813f769 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SchedulerService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SchedulerService.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.services; import gov.nasa.jpl.aerie.scheduler.server.ResultsProtocol; +import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; /** * services operations at the intersection of plans and scheduling goals; eg scheduling instances to satisfy goals @@ -8,13 +9,23 @@ * provides both mutation operations to actively improve a plan's goal satisfaction score (eg by inserting activity * instances into the plan) and passive queries to ascertain the current satisfaction level of a plan */ -//TODO: add separate scheduling goal and prioritization management service -public interface SchedulerService { +public record SchedulerService(ResultsCellRepository store) { /** * schedules activity instances into the target plan in order to further satisfy the associated scheduling goals * * @param request details of the scheduling request, including the target plan and goals to operate on * @return summary of the scheduling run, including goal satisfaction metrics and changes made */ - ResultsProtocol.State getScheduleResults(final ScheduleRequest request, final String requestedBy); + public ResultsProtocol.State getScheduleResults(final ScheduleRequest request, final String requestedBy) { + final var cell$ = this.store.lookup(request); + if (cell$.isPresent()) { + return cell$.get().get(); + } else { + // Allocate a fresh cell. + final var cell = this.store.allocate(request.specificationId(), requestedBy); + + // Return the current value of the reader; if it's incomplete, the caller can check it again later. + return cell.get(); + } + } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java index e1c629d700..a4716ca025 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java @@ -4,9 +4,20 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; +import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; +import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; -public interface SpecificationService { +public record SpecificationService(SpecificationRepository specificationRepository) { // Queries - Specification getSpecification(SpecificationId specificationId) throws NoSuchSpecificationException, SpecificationLoadException; - RevisionData getSpecificationRevisionData(SpecificationId specificationId) throws NoSuchSpecificationException; + public Specification getSpecification(final SpecificationId specificationId) + throws NoSuchSpecificationException, SpecificationLoadException + { + return specificationRepository.getSpecification(specificationId); + } + + public SpecificationRevisionData getSpecificationRevisionData(final SpecificationId specificationId) + throws NoSuchSpecificationException + { + return specificationRepository.getSpecificationRevisionData(specificationId); + } } diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java index 968be25e29..79361bd1ca 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java @@ -18,8 +18,8 @@ import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PostgresSpecificationRepository; import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; import gov.nasa.jpl.aerie.scheduler.server.services.GraphQLMerlinService; -import gov.nasa.jpl.aerie.scheduler.server.services.LocalSpecificationService; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; +import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; import gov.nasa.jpl.aerie.scheduler.server.services.UnexpectedSubtypeError; import gov.nasa.jpl.aerie.scheduler.worker.postgres.PostgresSchedulingRequestNotificationPayload; import gov.nasa.jpl.aerie.scheduler.worker.services.SchedulingDSLCompilationService; @@ -63,7 +63,7 @@ public static void main(String[] args) throws Exception { new PostgresSpecificationRepository(hikariDataSource), new PostgresResultsCellRepository(hikariDataSource)); - final var specificationService = new LocalSpecificationService(stores.specifications()); + final var specificationService = new SpecificationService(stores.specifications()); final var scheduleAgent = new SynchronousSchedulerAgent(specificationService, merlinService, config.merlinFileStore(), diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationService.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java similarity index 50% rename from scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationService.java rename to scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java index b1a31369bc..63ca06938c 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationService.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java @@ -3,32 +3,33 @@ import java.util.Map; import java.util.Optional; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; -import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; +import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; +import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; -class MockSpecificationService implements SpecificationService +class MockSpecificationRepository implements SpecificationRepository { Map specifications; - MockSpecificationService(final Map specifications) { + MockSpecificationRepository(final Map specifications) { this.specifications = specifications; } @Override public Specification getSpecification(final SpecificationId specificationId) - throws NoSuchSpecificationException, SpecificationLoadException + throws NoSuchSpecificationException { return Optional.ofNullable(specifications.get(specificationId)) .orElseThrow(() -> new NoSuchSpecificationException(specificationId)); } @Override - public RevisionData getSpecificationRevisionData(final SpecificationId specificationId) + public SpecificationRevisionData getSpecificationRevisionData(final SpecificationId specificationId) throws NoSuchSpecificationException { - return $ -> RevisionData.MatchResult.success(); + if(!specifications.containsKey(specificationId)) throw new NoSuchSpecificationException(specificationId); + final var spec = specifications.get(specificationId); + return new SpecificationRevisionData(spec.specificationRevision(), spec.planRevision()); } } diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java index fd5731ff8f..9ff6bb6ed1 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java @@ -55,6 +55,7 @@ import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.model.Plan; +import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -2010,7 +2011,7 @@ private SchedulingRunResults runScheduler( for (final var goal : goals) { goalsByPriority.add(new GoalRecord(goal.goalId(), new GoalSource(goal.definition()), goal.enabled(), goal.simulateAfter())); } - final var specificationService = new MockSpecificationService(Map.of(new SpecificationId(1L), new Specification( + final var specificationService = new SpecificationService(new MockSpecificationRepository(Map.of(new SpecificationId(1L), new Specification( planId, 1L, goalsByPriority, From d302c560b99d132cf9f6ce8aaa2ea04413fd100c Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 12:58:04 -0800 Subject: [PATCH 09/17] Update Postgres Actions - Make `ClaimRequestAction` return the request instead of requiring a separate DB call. - Insert additional request information in `CreateRequestAction` - Make `GetSpecificationConditionsAction` return `SchedulingConditionRecord`s instead of forcing the caller to do post-processing - Make `GetSpecificationGoalsAction` return `GoalRecord`s instead of forcing the caller to do post-processing - Rename `GlobalSchedulingConditionRecord` to `SchedulingConditionRecord` - Update GoalId, GoalRecord, RequestRecord, Specification, and SchedulingConditionRecord types - Update Satisfaction actions to reflect changes to GoalId --- .../GlobalSchedulingConditionRecord.java | 6 - .../aerie/scheduler/server/models/GoalId.java | 2 +- .../scheduler/server/models/GoalRecord.java | 6 +- .../server/models/SchedulingConditionId.java | 4 + .../models/SchedulingConditionRecord.java | 8 + ...ce.java => SchedulingConditionSource.java} | 2 +- .../server/models/Specification.java | 6 +- .../remotes/postgres/ClaimRequestAction.java | 62 +++- .../remotes/postgres/CreateRequestAction.java | 29 +- .../postgres/GetCreatedActivitiesAction.java | 3 +- .../postgres/GetGoalSatisfactionAction.java | 3 +- .../remotes/postgres/GetRequestAction.java | 16 +- .../GetSatisfyingActivitiesAction.java | 3 +- .../GetSpecificationConditionsAction.java | 33 ++- .../postgres/GetSpecificationGoalsAction.java | 35 ++- .../InsertCreatedActivitiesAction.java | 11 +- .../InsertGoalSatisfactionAction.java | 10 +- .../InsertSatisfyingActivitiesAction.java | 11 +- .../remotes/postgres/PostgresGoalRecord.java | 10 - .../PostgresResultsCellRepository.java | 8 +- .../PostgresSchedulingConditionRecord.java | 9 - .../PostgresSpecificationRepository.java | 38 +-- .../remotes/postgres/RequestRecord.java | 1 + .../remotes/postgres/SpecificationRecord.java | 2 +- .../services/SynchronousSchedulerAgent.java | 22 +- .../services/SchedulingIntegrationTests.java | 279 +++++++++--------- 26 files changed, 329 insertions(+), 290 deletions(-) delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionRecord.java create mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionId.java create mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionRecord.java rename scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/{GlobalSchedulingConditionSource.java => SchedulingConditionSource.java} (69%) delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresGoalRecord.java delete mode 100644 scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSchedulingConditionRecord.java diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionRecord.java deleted file mode 100644 index 83d86e6c83..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionRecord.java +++ /dev/null @@ -1,6 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.models; - -public record GlobalSchedulingConditionRecord( - GlobalSchedulingConditionSource source, - boolean enabled -) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java index ab6e9822e2..70aec3c026 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java @@ -1,3 +1,3 @@ package gov.nasa.jpl.aerie.scheduler.server.models; -public record GoalId(long id) { } +public record GoalId(long id, long revision) { } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalRecord.java index 966839ae23..620745011e 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalRecord.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalRecord.java @@ -1,3 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.models; -public record GoalRecord(GoalId id, GoalSource definition, boolean enabled, boolean simulateAfter) {} +public record GoalRecord( + GoalId id, + String name, + GoalSource definition, + boolean simulateAfter) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionId.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionId.java new file mode 100644 index 0000000000..fba4464fb6 --- /dev/null +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionId.java @@ -0,0 +1,4 @@ +package gov.nasa.jpl.aerie.scheduler.server.models; + +public record SchedulingConditionId(long id) { +} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionRecord.java new file mode 100644 index 0000000000..1eecda1db0 --- /dev/null +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionRecord.java @@ -0,0 +1,8 @@ +package gov.nasa.jpl.aerie.scheduler.server.models; + +public record SchedulingConditionRecord( + SchedulingConditionId id, + long revision, + String name, + SchedulingConditionSource source +) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionSource.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionSource.java similarity index 69% rename from scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionSource.java rename to scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionSource.java index f265b28aa8..61424ad9d9 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GlobalSchedulingConditionSource.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/SchedulingConditionSource.java @@ -3,4 +3,4 @@ /** * @param source The typescript code describing this global scheduling condition. */ -public record GlobalSchedulingConditionSource(String source) {} +public record SchedulingConditionSource(String source) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java index 049881b879..8f92a55986 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java @@ -6,12 +6,14 @@ import java.util.Map; public record Specification( + SpecificationId specificationId, + long specificationRevision, PlanId planId, long planRevision, - List goalsByPriority, Timestamp horizonStartTimestamp, Timestamp horizonEndTimestamp, Map simulationArguments, boolean analysisOnly, - List globalSchedulingConditions + List goalsByPriority, + List schedulingConditions ) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/ClaimRequestAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/ClaimRequestAction.java index 683276baac..9b1b75bf42 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/ClaimRequestAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/ClaimRequestAction.java @@ -8,9 +8,16 @@ /*package local*/ public class ClaimRequestAction implements AutoCloseable { private static final @Language("SQL") String sql = """ update scheduling_request - set - status = 'incomplete' - where (specification_id = ? and status = 'pending' and not canceled); + set status = 'incomplete' + where (analysis_id = ? and status = 'pending' and not canceled) + returning + specification_id, + specification_revision, + plan_revision, + status, + reason, + canceled, + dataset_id; """; private final PreparedStatement statement; @@ -19,16 +26,51 @@ public ClaimRequestAction(final Connection connection) throws SQLException { this.statement = connection.prepareStatement(sql); } - public void apply(final long specificationId) throws SQLException, UnclaimableRequestException { - this.statement.setLong(1, specificationId); + public RequestRecord apply(final long analysisId) throws SQLException, UnclaimableRequestException { + this.statement.setLong(1, analysisId); - final var count = this.statement.executeUpdate(); - if (count < 1) { - throw new UnclaimableRequestException(specificationId); - } else if (count > 1) { + final var resultSet = this.statement.executeQuery(); + if (!resultSet.next()) { + throw new UnclaimableRequestException(analysisId); + } + + final var specificationId = resultSet.getLong("specification_id"); + final var specificationRevision = resultSet.getLong("specification_revision"); + final var planRevision = resultSet.getLong("plan_revision"); + + final RequestRecord.Status status; + try { + status = RequestRecord.Status.fromString(resultSet.getString("status")); + } catch (final RequestRecord.Status.InvalidRequestStatusException ex) { + throw new Error( + String.format( + "Scheduling request for specification with ID %d and revision %d has invalid state %s", + specificationId, + specificationRevision, + ex.invalidStatus)); + } + + final var failureReason$ = PreparedStatements.getFailureReason(resultSet, "reason"); + final var canceled = resultSet.getBoolean("canceled"); + final var datasetId = PreparedStatements.getDatasetId(resultSet, "dataset_id"); + + final var request = new RequestRecord( + specificationId, + analysisId, + specificationRevision, + planRevision, + status, + failureReason$, + canceled, + datasetId + ); + + if (resultSet.next()) { throw new SQLException( - String.format("Claiming a scheduling request for specification id %s returned more than one result row.", specificationId)); + String.format("Claiming a scheduling request with analysis id %s returned more than one result row.", analysisId)); } + + return request; } @Override diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/CreateRequestAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/CreateRequestAction.java index 4206c913fc..c95320ec4d 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/CreateRequestAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/CreateRequestAction.java @@ -5,13 +5,30 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import static gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PostgresParsers.simulationArgumentsP; import static gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.PreparedStatements.getDatasetId; /*package-local*/ final class CreateRequestAction implements AutoCloseable { + private static final DateTimeFormatter TIMESTAMP_FORMAT = + new DateTimeFormatterBuilder() + .appendPattern("uuuu-MM-dd HH:mm:ss") + .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true) + .appendOffset("+HH:mm:ss", "+00") + .toFormatter(); private final @Language("SQL") String sql = """ - insert into scheduling_request (specification_id, specification_revision, requested_by) - values (?, ?, ?) + insert into scheduling_request ( + specification_id, + specification_revision, + plan_revision, + horizon_start, + horizon_end, + simulation_arguments, + requested_by) + values (?, ?, ?, ?::timestamptz, ?::timestamptz, ?::jsonb, ?) returning analysis_id, status, @@ -29,7 +46,12 @@ public CreateRequestAction(final Connection connection) throws SQLException { public RequestRecord apply(final SpecificationRecord specification, final String requestedBy) throws SQLException { this.statement.setLong(1, specification.id()); this.statement.setLong(2, specification.revision()); - this.statement.setString(3, requestedBy); + this.statement.setLong(3, specification.planRevision()); + //TODO: extract PreparedStatements into a shared library and replace these calls + this.statement.setString(4, TIMESTAMP_FORMAT.format(specification.horizonStartTimestamp().time())); + this.statement.setString(5, TIMESTAMP_FORMAT.format(specification.horizonEndTimestamp().time())); + this.statement.setString(6, simulationArgumentsP.unparse(specification.simulationArguments()).toString()); + this.statement.setString(7, requestedBy); final var result = this.statement.executeQuery(); if (!result.next()) throw new FailedInsertException("scheduling_request"); @@ -50,6 +72,7 @@ public RequestRecord apply(final SpecificationRecord specification, final String specification.id(), analysis_id, specification.revision(), + specification.planRevision(), status, failureReason$, canceled, diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java index f39210d42e..7622976f3b 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java @@ -16,6 +16,7 @@ private static final @Language("SQL") String sql = """ select c.goal_id, + c.goal_revision, c.activity_id from scheduling_goal_analysis_created_activities as c where c.analysis_id = ? @@ -33,7 +34,7 @@ public Map> get(final long analysisId) throws final var createdActivities = new HashMap>(); while (resultSet.next()) { - final var goalId = new GoalId(resultSet.getLong("goal_id")); + final var goalId = new GoalId(resultSet.getLong("goal_id"), resultSet.getLong("goal_revision")); final var activityId = new ActivityDirectiveId(resultSet.getLong("activity_id")); if (!createdActivities.containsKey(goalId)) createdActivities.put(goalId, new ArrayList<>()); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java index d33d76473f..2294bb0ece 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java @@ -12,6 +12,7 @@ private final static @Language("SQL") String sql = """ select goal.goal_id, + goal.goal_revision, goal.satisfied from scheduling_goal_analysis as goal where goal.analysis_id = ? @@ -30,7 +31,7 @@ public Map get(final long analysisId) throws SQLException { final var goals = new HashMap(); while (resultSet.next()) { goals.put( - new GoalId(resultSet.getLong("goal_id")), + new GoalId(resultSet.getLong("goal_id"), resultSet.getLong("goal_revision")), resultSet.getBoolean("satisfied") ); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetRequestAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetRequestAction.java index e92ffeb9f7..8ee5e8a575 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetRequestAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetRequestAction.java @@ -1,12 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import javax.json.Json; -import gov.nasa.jpl.aerie.scheduler.server.http.SchedulerParsers; -import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleFailure; import org.intellij.lang.annotations.Language; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -21,9 +16,9 @@ r.canceled, r.dataset_id from scheduling_request as r - where - r.specification_id = ? and - r.specification_revision = ? + where r.specification_id = ? + and r.specification_revision = ? + and r.plan_revision = ? """; private final PreparedStatement statement; @@ -34,10 +29,12 @@ public GetRequestAction(final Connection connection) throws SQLException { public Optional get( final long specificationId, - final long specificationRevision + final long specificationRevision, + final long planRevision ) throws SQLException { this.statement.setLong(1, specificationId); this.statement.setLong(2, specificationRevision); + this.statement.setLong(3, planRevision); final var resultSet = this.statement.executeQuery(); if (!resultSet.next()) return Optional.empty(); @@ -62,6 +59,7 @@ public Optional get( specificationId, analysisId, specificationRevision, + planRevision, status, failureReason$, canceled, diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java index a58383267f..e049fc4593 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java @@ -16,6 +16,7 @@ private static final @Language("SQL") String sql = """ select s.goal_id, + s.goal_revision, s.activity_id from scheduling_goal_analysis_satisfying_activities as s where s.analysis_id = ? @@ -33,7 +34,7 @@ public Map> get(final long analysisId) throws final var satisfyingActivities = new HashMap>(); while (resultSet.next()) { - final var goalId = new GoalId(resultSet.getLong("goal_id")); + final var goalId = new GoalId(resultSet.getLong("goal_id"), resultSet.getLong("goal_revision")); final var activityId = new ActivityDirectiveId(resultSet.getLong("activity_id")); satisfyingActivities diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationConditionsAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationConditionsAction.java index d6ddfcecd4..61434da968 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationConditionsAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationConditionsAction.java @@ -1,5 +1,8 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionId; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionRecord; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionSource; import org.intellij.lang.annotations.Language; import java.sql.Connection; @@ -10,16 +13,17 @@ /*package-local*/ final class GetSpecificationConditionsAction implements AutoCloseable { private final @Language("SQL") String sql = """ - select - s.condition_id, - s.enabled, - c.name, - c.definition, - c.revision - from scheduling_specification_conditions as s - join scheduling_condition as c - on s.specification_id = ? - and s.condition_id = c.id + select s.condition_id, cd.revision, cm.name, cd.definition + from scheduling_specification_conditions s + left join scheduling_condition_definition cd using (condition_id) + left join scheduling_condition_metadata cm on s.condition_id = cm.id + where s.specification_id = ? + and s.enabled + and ((s.condition_revision is not null and s.condition_revision = cd.revision) + or (s.condition_revision is null and cd.revision = (select def.revision + from scheduling_condition_definition def + where def.condition_id = s.condition_id + order by def.revision desc limit 1))); """; private final PreparedStatement statement; @@ -28,21 +32,20 @@ public GetSpecificationConditionsAction(final Connection connection) throws SQLE this.statement = connection.prepareStatement(sql); } - public List get(final long specificationId) throws SQLException { + public List get(final long specificationId) throws SQLException { this.statement.setLong(1, specificationId); final var resultSet = this.statement.executeQuery(); - final var goals = new ArrayList(); + final var conditions = new ArrayList(); while (resultSet.next()) { final var id = resultSet.getLong("condition_id"); final var revision = resultSet.getLong("revision"); final var name = resultSet.getString("name"); final var definition = resultSet.getString("definition"); - final var enabled = resultSet.getBoolean("enabled"); - goals.add(new PostgresSchedulingConditionRecord(id, revision, name, definition, enabled)); + conditions.add(new SchedulingConditionRecord(new SchedulingConditionId(id), revision, name, new SchedulingConditionSource(definition))); } - return goals; + return conditions; } @Override diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java index 37eda38d6e..73ef450634 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java @@ -1,5 +1,8 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; +import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.server.models.GoalRecord; +import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import org.intellij.lang.annotations.Language; import java.sql.Connection; @@ -10,18 +13,19 @@ /*package-local*/ final class GetSpecificationGoalsAction implements AutoCloseable { private final @Language("SQL") String sql = """ - select - s.goal_id, - g.name, - g.definition, - g.revision, - s.enabled, - s.simulate_after - from scheduling_specification_goals as s - left join scheduling_goal as g on s.goal_id = g.id - where s.specification_id = ? - order by s.priority; - """; + select s.goal_id, gd.revision, gm.name, gd.definition, s.simulate_after + from scheduling_specification_goals s + left join scheduling_goal_definition gd using (goal_id) + left join scheduling_goal_metadata gm on s.goal_id = gm.id + where s.specification_id = ? + and s.enabled + and ((s.goal_revision is not null and s.goal_revision = gd.revision) + or (s.goal_revision is null and gd.revision = (select def.revision + from scheduling_goal_definition def + where def.goal_id = s.goal_id + order by def.revision desc limit 1))) + order by s.priority; + """; private final PreparedStatement statement; @@ -29,19 +33,18 @@ public GetSpecificationGoalsAction(final Connection connection) throws SQLExcept this.statement = connection.prepareStatement(sql); } - public List get(final long specificationId) throws SQLException { + public List get(final long specificationId) throws SQLException { this.statement.setLong(1, specificationId); final var resultSet = this.statement.executeQuery(); - final var goals = new ArrayList(); + final var goals = new ArrayList(); while (resultSet.next()) { final var id = resultSet.getLong("goal_id"); final var revision = resultSet.getLong("revision"); final var name = resultSet.getString("name"); final var definition = resultSet.getString("definition"); - final var enabled = resultSet.getBoolean("enabled"); final var simulateAfter = resultSet.getBoolean("simulate_after"); - goals.add(new PostgresGoalRecord(id, revision, name, definition, enabled, simulateAfter)); + goals.add(new GoalRecord(new GoalId(id, revision), name, new GoalSource(definition), simulateAfter)); } return goals; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java index 53c0691803..84647e1ca0 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java @@ -13,8 +13,8 @@ /*package-local*/ final class InsertCreatedActivitiesAction implements AutoCloseable { private static final @Language("SQL") String sql = """ - insert into scheduling_goal_analysis_created_activities (analysis_id, goal_id, activity_id) - values (?, ?, ?) + insert into scheduling_goal_analysis_created_activities (analysis_id, goal_id, goal_revision, activity_id) + values (?, ?, ?, ?) """; private final PreparedStatement statement; @@ -28,11 +28,12 @@ public void apply( final Map> createdActivities ) throws SQLException { for (final var entry : createdActivities.entrySet()) { - final var goalId = entry.getKey().id(); + final var goal = entry.getKey(); for (final var activityId : entry.getValue()) { this.statement.setLong(1, analysisId); - this.statement.setLong(2, goalId); - this.statement.setLong(3, activityId.id()); + this.statement.setLong(2, goal.id()); + this.statement.setLong(3, goal.revision()); + this.statement.setLong(4, activityId.id()); this.statement.addBatch(); } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java index 1554e0eca5..873992e088 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java @@ -11,8 +11,8 @@ /*package-local*/ final class InsertGoalSatisfactionAction implements AutoCloseable { private static final @Language("SQL") String sql = """ - insert into scheduling_goal_analysis (analysis_id, goal_id, satisfied) - values (?, ?, ?) + insert into scheduling_goal_analysis (analysis_id, goal_id, goal_revision, satisfied) + values (?, ?, ?, ?) """; private final PreparedStatement statement; @@ -23,9 +23,11 @@ public InsertGoalSatisfactionAction(final Connection connection) throws SQLExcep public void apply(final long analysisId, final Map goalSatisfactions) throws SQLException { for (final var entry : goalSatisfactions.entrySet()) { + final var goal = entry.getKey(); this.statement.setLong(1, analysisId); - this.statement.setLong(2, entry.getKey().id()); - this.statement.setBoolean(3, entry.getValue()); + this.statement.setLong(2, goal.id()); + this.statement.setLong(3, goal.revision()); + this.statement.setBoolean(4, entry.getValue()); this.statement.addBatch(); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java index 344e826e81..d08f7870a4 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java @@ -13,8 +13,8 @@ /*package-local*/ final class InsertSatisfyingActivitiesAction implements AutoCloseable { private static final @Language("SQL") String sql = """ - insert into scheduling_goal_analysis_satisfying_activities (analysis_id, goal_id, activity_id) - values (?, ?, ?) + insert into scheduling_goal_analysis_satisfying_activities (analysis_id, goal_id, goal_revision, activity_id) + values (?, ?, ?, ?) """; private final PreparedStatement statement; @@ -28,11 +28,12 @@ public void apply( final Map> satisfyingActivities ) throws SQLException { for (final var entry : satisfyingActivities.entrySet()) { - final var goalId = entry.getKey().id(); + final var goal = entry.getKey(); for (final var activityId : entry.getValue()) { this.statement.setLong(1, analysisId); - this.statement.setLong(2, goalId); - this.statement.setLong(3, activityId.id()); + this.statement.setLong(2, goal.id()); + this.statement.setLong(3, goal.revision()); + this.statement.setLong(4, activityId.id()); this.statement.addBatch(); } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresGoalRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresGoalRecord.java deleted file mode 100644 index e996ad02bf..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresGoalRecord.java +++ /dev/null @@ -1,10 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; - -public record PostgresGoalRecord( - long id, - long revision, - String name, - String definition, - boolean enabled, - boolean simulateAfter -) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index 97a3866ec7..96e4330ae7 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -216,10 +216,10 @@ private static void postResults( final var createdActivities = new HashMap>(numGoals); final var satisfyingActivities = new HashMap>(numGoals); - results.goalResults().forEach((goalId, result) -> { - goalSatisfaction.put(goalId, result.satisfied()); - createdActivities.put(goalId, result.createdActivities()); - satisfyingActivities.put(goalId, result.satisfyingActivities()); + results.goalResults().forEach((goal, result) -> { + goalSatisfaction.put(goal, result.satisfied()); + createdActivities.put(goal, result.createdActivities()); + satisfyingActivities.put(goal, result.satisfyingActivities()); }); try ( diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSchedulingConditionRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSchedulingConditionRecord.java deleted file mode 100644 index bdb6664ab4..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSchedulingConditionRecord.java +++ /dev/null @@ -1,9 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; - -public record PostgresSchedulingConditionRecord( - long id, - long revision, - String name, - String definition, - boolean enabled -) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java index f9a26ff51b..355a4a6e7a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java @@ -1,16 +1,12 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.models.GlobalSchedulingConditionRecord; -import gov.nasa.jpl.aerie.scheduler.server.models.GlobalSchedulingConditionSource; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalRecord; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; import gov.nasa.jpl.aerie.scheduler.server.remotes.SpecificationRepository; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; import javax.sql.DataSource; import java.sql.SQLException; @@ -29,8 +25,8 @@ public Specification getSpecification(final SpecificationId specificationId) { final SpecificationRecord specificationRecord; final PlanId planId; - final List postgresGoalRecords; - final List postgresSchedulingConditionRecords; + final List goals; + final List schedulingConditions; try (final var connection = this.dataSource.getConnection(); final var getSpecificationAction = new GetSpecificationAction(connection); final var getSpecificationGoalsAction = new GetSpecificationGoalsAction(connection); @@ -40,39 +36,23 @@ public Specification getSpecification(final SpecificationId specificationId) .get(specificationId.id()) .orElseThrow(() -> new NoSuchSpecificationException(specificationId)); planId = new PlanId(specificationRecord.planId()); - postgresGoalRecords = getSpecificationGoalsAction.get(specificationId.id()); - postgresSchedulingConditionRecords = getSpecificationConditionsAction.get(specificationId.id()); + goals = getSpecificationGoalsAction.get(specificationId.id()); + schedulingConditions = getSpecificationConditionsAction.get(specificationId.id()); } catch (final SQLException ex) { throw new DatabaseException("Failed to get scheduling specification", ex); } - final var goals = postgresGoalRecords - .stream() - .map((PostgresGoalRecord pgGoal) -> new GoalRecord( - new GoalId(pgGoal.id()), - new GoalSource(pgGoal.definition()), - pgGoal.enabled(), - pgGoal.simulateAfter() - )) - .toList(); - - final var globalSchedulingConditions = postgresSchedulingConditionRecords - .stream() - .map((PostgresSchedulingConditionRecord pgCondition) -> new GlobalSchedulingConditionRecord( - new GlobalSchedulingConditionSource(pgCondition.definition()), - pgCondition.enabled() - )) - .toList(); - return new Specification( + specificationId, + specificationRecord.revision(), planId, specificationRecord.planRevision(), - goals, specificationRecord.horizonStartTimestamp(), specificationRecord.horizonEndTimestamp(), specificationRecord.simulationArguments(), specificationRecord.analysisOnly(), - globalSchedulingConditions + goals, + schedulingConditions ); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/RequestRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/RequestRecord.java index 108a801e63..6d3fc19355 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/RequestRecord.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/RequestRecord.java @@ -7,6 +7,7 @@ public record RequestRecord( long specificationId, long analysisId, long specificationRevision, + long planRevision, Status status, Optional reason, boolean canceled, diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java index 9870891293..c97bca9a34 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java @@ -5,7 +5,7 @@ import java.util.Map; -public final record SpecificationRecord( +public record SpecificationRecord( long id, long revision, long planId, diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java index 637286c7f7..1d2893c6d1 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java @@ -30,7 +30,6 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.scheduler.SchedulingInterruptedException; -import gov.nasa.jpl.aerie.scheduler.constraints.scheduling.GlobalConstraint; import gov.nasa.jpl.aerie.scheduler.goals.Goal; import gov.nasa.jpl.aerie.scheduler.model.ActivityType; import gov.nasa.jpl.aerie.scheduler.model.Plan; @@ -147,8 +146,7 @@ public void schedule( //apply constraints/goals to the problem final var compiledGlobalSchedulingConditions = new ArrayList(); final var failedGlobalSchedulingConditions = new ArrayList>(); - specification.globalSchedulingConditions().forEach($ -> { - if (!$.enabled()) return; + specification.schedulingConditions().forEach($ -> { final var result = schedulingDSLCompilationService.compileGlobalSchedulingCondition( merlinService, planMetadata.planId(), @@ -182,7 +180,6 @@ public void schedule( final var compiledGoals = new ArrayList>(); final var failedGoals = new ArrayList>>(); for (final var goalRecord : specification.goalsByPriority()) { - if (!goalRecord.enabled()) continue; final var result = compileGoalDefinition( merlinService, planMetadata.planId(), @@ -376,23 +373,6 @@ private void ensureRequestIsCurrent(final ScheduleRequest request) throws NoSuch } } - /** - * collects the scheduling goals that apply to the current scheduling run on the target plan - * - * @param planMetadata details of the plan container whose associated goals should be collected - * @param mission the mission model that the plan adheres to, possibly associating additional relevant goals - * @return the list of goals relevant to the target plan - * @throws ResultsProtocolFailure when the constraints could not be loaded, or the data stores could not be - * reached - */ - private List loadConstraints(final PlanMetadata planMetadata, final MissionModel mission) { - //TODO: is the plan and mission model enough to find the relevant constraints? (eg what about sandbox toggling?) - //TODO: load global constraints from scheduler data store? - //TODO: load activity type constraints from somewhere (scheduler store? mission model?) - //TODO: somehow apply user control over which constraints to enforce during scheduling - return List.of(); - } - /** * load the activity instance content of the specified merlin plan into scheduler-ready objects * diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java index 9ff6bb6ed1..7c600303cd 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java @@ -40,8 +40,9 @@ import gov.nasa.jpl.aerie.scheduler.server.config.PlanOutputMode; import gov.nasa.jpl.aerie.scheduler.server.http.SchedulerParsers; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; -import gov.nasa.jpl.aerie.scheduler.server.models.GlobalSchedulingConditionRecord; -import gov.nasa.jpl.aerie.scheduler.server.models.GlobalSchedulingConditionSource; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionId; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionRecord; +import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionSource; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; @@ -50,8 +51,8 @@ import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; +import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinService; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.model.Plan; @@ -104,7 +105,7 @@ void testEmptyPlanEmptySpecification() { @Test void testEmptyPlanSimpleRecurrenceGoal() { - final var results = runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L), """ + final var results = runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: "fromStem", @@ -113,7 +114,7 @@ export default () => Goal.ActivityRecurrenceGoal({ }) """, true)), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(4, goalResult.createdActivities().size()); for (final var activity : goalResult.createdActivities()) { @@ -133,7 +134,7 @@ export default () => Goal.ActivityRecurrenceGoal({ @Test void testRecurrenceGoalNegative() { try { - runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L), """ + runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: "fromStem", @@ -153,7 +154,7 @@ export default () => Goal.ActivityRecurrenceGoal({ @Test void testEmptyPlanDurationCardinalityGoal() { - final var results = runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L), """ + final var results = runScheduler(BANANANATION, List.of(), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function myGoal() { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.GrowBanana({ @@ -165,7 +166,7 @@ export default function myGoal() { } """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(10, goalResult.createdActivities().size()); @@ -194,7 +195,7 @@ export default function myGoal() { void testEmptyPlanOccurrenceCardinalityGoal() { final var results = runScheduler(BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function myGoal() { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.GrowBanana({ @@ -207,7 +208,7 @@ export default function myGoal() { """, true)),List.of(createAutoMutex("GrowBanana")),PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(10, goalResult.createdActivities().size()); @@ -237,7 +238,7 @@ void testEmptyPlanOccurrenceUnitaryGoalTimeInstant() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: Temporal.Instant.from("2021-01-01T05:00:00.000Z"), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -247,7 +248,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -272,7 +273,7 @@ void testEmptyPlanOccurrenceUnitaryGoalTimeInterval() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: Interval.Between(Temporal.Instant.from("2021-01-01T05:00:00.000Z"), Temporal.Instant.from("2021-01-01T10:00:00.000Z"), Inclusivity.Inclusive, Inclusivity.Exclusive), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -282,7 +283,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -314,7 +315,7 @@ void testSingleActivityPlanSimpleRecurrenceGoal() { Map.of("biteSize", SerializedValue.of(1)), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), interval: Temporal.Duration.from({days: 1}) @@ -323,7 +324,7 @@ export default () => Goal.ActivityRecurrenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(4, goalResult.createdActivities().size()); @@ -361,7 +362,7 @@ void testSingleActivityPlanSimpleCoexistenceGoalWithValueAtParams() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (growBananaActivity) => ActivityTemplates.ChangeProducer({producer: Discrete.Resource("/producer").valueAt(growBananaActivity.span().starts())}), @@ -371,7 +372,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -418,7 +419,7 @@ void testCoexistencePartialAct() { true ) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.BiteBanana), activityFinder: ActivityExpression.ofType(ActivityTypes.GrowBanana), @@ -429,7 +430,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(0, goalResult.createdActivities().size()); @@ -480,7 +481,7 @@ void testCoexistencePartialActWithParameter() { true ) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.BiteBanana), activityFinder: ActivityExpression.build(ActivityTypes.GrowBanana, {quantity: 1}), @@ -491,7 +492,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -520,6 +521,7 @@ export default () => Goal.CoexistenceGoal({ for(final var actAtTime10: planByTime.get(MINUTES.times(10))){ if(actAtTime10.serializedActivity().equals(expectedCreation)){ lookingFor = true; + break; } } assertTrue(lookingFor); @@ -558,7 +560,7 @@ void testRecurrenceWithActivityFinder() { null, true) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.GrowBanana({quantity: 2, growingDuration: Temporal.Duration.from({seconds:1})}), activityFinder: ActivityExpression.build(ActivityTypes.GrowBanana, {quantity: 2}), @@ -568,7 +570,7 @@ export default () => Goal.ActivityRecurrenceGoal({ PLANNING_HORIZON ); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); var foundFirst = false; var foundSecond = false; for(final var satisfyingActivity: goalResult.satisfyingActivities()){ @@ -598,7 +600,7 @@ void testCardinalityGoalWithActivityFinder() { "growingDuration", SerializedValue.of(Duration.of(5, SECONDS).in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function myGoal() { return Goal.CardinalityGoal({ @@ -612,7 +614,7 @@ export default function myGoal() { } """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(5, goalResult.createdActivities().size()); @@ -636,7 +638,7 @@ void testSingleActivityPlanSimpleCoexistenceGoalWithFunctionalParameters() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (growBananaActivity) => ActivityTemplates.PickBanana({quantity: growBananaActivity.parameters.quantity}), @@ -646,7 +648,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -693,7 +695,7 @@ void testSingleActivityPlanSimpleCoexistenceGoalWithWindowReference() { true ) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: Real.Resource("/fruit").lessThan(4), activityTemplate: (interval) => ActivityTemplates.GrowBanana({quantity: 10, growingDuration: interval.duration() }), @@ -703,7 +705,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -742,7 +744,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -752,7 +754,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -791,7 +793,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_constrainEndTime() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -801,7 +803,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -844,7 +846,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenBefore() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -854,7 +856,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -898,7 +900,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenEquals() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ hours : 1})}), @@ -909,7 +911,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -955,7 +957,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenMeets() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -965,7 +967,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1008,7 +1010,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenOverlaps() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ hours : 1})}), @@ -1018,7 +1020,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1059,7 +1061,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenContains() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ minutes : 50})}), @@ -1070,7 +1072,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1119,7 +1121,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenStarts() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1129,7 +1131,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1174,7 +1176,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenFinishesAt() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ minutes : 50})}), @@ -1184,7 +1186,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1228,7 +1230,7 @@ void testSingleActivityPlanSimpleCoexistenceGoal_AllenFinishesWithin() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ minutes : 50})}), @@ -1238,7 +1240,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -1287,7 +1289,7 @@ void testStateCoexistenceGoal_greaterThan() { "PickBanana", Map.of("quantity", SerializedValue.of(100)), null, - true)), List.of(new SchedulingGoal(new GoalId(0L), """ + true)), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1331,7 +1333,7 @@ void testLessThan() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1370,7 +1372,7 @@ void testLinear_atChangePoints() { Map.of("biteSize", SerializedValue.of(1.0)), null, true) - ), List.of(new SchedulingGoal(new GoalId(0L), """ + ), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1407,7 +1409,7 @@ void testLinear_interpolated() { null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromTip"}), @@ -1453,7 +1455,7 @@ void testEqualTo_satsified() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1498,7 +1500,7 @@ void testEqualTo_neverSatisfied() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1541,7 +1543,7 @@ void testNotEqualTo_satisfied() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1586,7 +1588,7 @@ void testBetweenInTermsOfAnd() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1634,7 +1636,7 @@ void testWindowsOr() { "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1669,7 +1671,7 @@ void testWindowsTransition() { "ChangeProducer", Map.of(), null, - true)), List.of(new SchedulingGoal(new GoalId(0L), """ + true)), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1698,7 +1700,7 @@ void testWindowsTransition_unsatisfied() { "ChangeProducer", Map.of("producer", SerializedValue.of("Fyffes")), null, - true)), List.of(new SchedulingGoal(new GoalId(0L), """ + true)), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1726,7 +1728,7 @@ void testExternalResource() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -1790,7 +1792,7 @@ void testApplyWhen() { null, true) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.ChangeProducer({producer: "Morpheus"}), interval: Temporal.Duration.from({ hours : 24 }) @@ -1816,7 +1818,7 @@ void testGlobalSchedulingConditions_conditionNeverOccurs() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.ChangeProducer({producer: "Morpheus"}), interval: Temporal.Duration.from({days: 1}) @@ -1824,12 +1826,13 @@ export default () => Goal.ActivityRecurrenceGoal({ """, true) ), List.of( - new GlobalSchedulingConditionRecord( - new GlobalSchedulingConditionSource(""" + new SchedulingConditionRecord( + new SchedulingConditionId(0L), + 0L, + "fruit greater than 5", + new SchedulingConditionSource(""" export default () => GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource(\"/fruit\").greaterThan(5.0)) - """), - true - ), + """)), createAutoMutex("ChangeProducer") ), PLANNING_HORIZON); @@ -1841,7 +1844,7 @@ void testGlobalSchedulingConditions_conditionAlwaysTrue() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.ChangeProducer({producer: "Morpheus"}), interval: Temporal.Duration.from({days: 1}) @@ -1849,10 +1852,11 @@ export default () => Goal.ActivityRecurrenceGoal({ """, true) ), List.of( - new GlobalSchedulingConditionRecord( - new GlobalSchedulingConditionSource("export default () => GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource(\"/fruit\").lessThan(5.0))"), - true - ) + new SchedulingConditionRecord( + new SchedulingConditionId(0L), + 0L, + "fruit less than 5", + new SchedulingConditionSource("export default () => GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource(\"/fruit\").lessThan(5.0))")) ), PLANNING_HORIZON); assertEquals(4, results.updatedPlan().size()); @@ -1870,7 +1874,7 @@ void testGlobalSchedulingConditions_conditionSometimesTrue() { null, true) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.ChangeProducer({producer: "Morpheus"}), interval: Temporal.Duration.from({days: 1}) @@ -1878,10 +1882,11 @@ export default () => Goal.ActivityRecurrenceGoal({ """, true) ), List.of( - new GlobalSchedulingConditionRecord( - new GlobalSchedulingConditionSource("export default () => GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource(\"/fruit\").greaterThan(3.5))"), - true - ) + new SchedulingConditionRecord( + new SchedulingConditionId(0L), + 0L, + "fruit greater than 3.5", + new SchedulingConditionSource("export default () => GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource(\"/fruit\").greaterThan(3.5))")) ), PLANNING_HORIZON); assertEquals(2, results.updatedPlan().size()); @@ -1930,15 +1935,17 @@ private static File getLatestJarFile(final Path libPath) { return files[0]; } - public static GlobalSchedulingConditionRecord createAutoMutex(String activityType){ - return new GlobalSchedulingConditionRecord( - new GlobalSchedulingConditionSource(""" + public static SchedulingConditionRecord createAutoMutex(String activityType){ + return new SchedulingConditionRecord( + new SchedulingConditionId(0L), + 0L, + "auto-mutex activity type "+activityType, + new SchedulingConditionSource(""" export default function myCondition() { return GlobalSchedulingCondition.mutex([ActivityTypes.%s], [ActivityTypes.%s]) } - """.formatted(activityType, activityType)), - true - ); + """.formatted(activityType, activityType))); + } private SchedulingRunResults runScheduler( @@ -1970,7 +1977,7 @@ private SchedulingRunResults runScheduler( final MissionModelDescription desc, final List plannedActivities, final Iterable goals, - final List globalSchedulingConditions, + final List globalSchedulingConditions, final PlanningHorizon planningHorizon ){ return runScheduler(desc, plannedActivities, goals, globalSchedulingConditions, planningHorizon, Optional.empty()); @@ -1980,7 +1987,7 @@ private SchedulingRunResults runScheduler( final MissionModelDescription desc, final List plannedActivities, final Iterable goals, - final List globalSchedulingConditions, + final List globalSchedulingConditions, final PlanningHorizon planningHorizon, final Optional externalProfiles ){ @@ -1996,7 +2003,7 @@ private SchedulingRunResults runScheduler( final MissionModelDescription desc, final Map plannedActivities, final Iterable goals, - final List globalSchedulingConditions, + final List globalSchedulingConditions, final PlanningHorizon planningHorizon, final Optional externalProfiles ) { @@ -2009,17 +2016,19 @@ private SchedulingRunResults runScheduler( final var goalsByPriority = new ArrayList(); for (final var goal : goals) { - goalsByPriority.add(new GoalRecord(goal.goalId(), new GoalSource(goal.definition()), goal.enabled(), goal.simulateAfter())); + goalsByPriority.add(new GoalRecord(goal.goalId(), "test goal", new GoalSource(goal.definition()), goal.simulateAfter())); } final var specificationService = new SpecificationService(new MockSpecificationRepository(Map.of(new SpecificationId(1L), new Specification( + new SpecificationId(1L), + 1L, planId, 1L, - goalsByPriority, new Timestamp(planningHorizon.getStartInstant()), new Timestamp(planningHorizon.getEndInstant()), Map.of(), false, - globalSchedulingConditions))); + goalsByPriority, + globalSchedulingConditions)))); final var agent = new SynchronousSchedulerAgent( specificationService, mockMerlinService, @@ -2103,7 +2112,7 @@ void testAndFailure(){ "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.GrowBanana({ @@ -2164,7 +2173,7 @@ void testOrFailure(){ "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.GrowBanana({ @@ -2216,7 +2225,7 @@ void testOr(){ "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2272,7 +2281,7 @@ export default function myGoal() { Map.of(), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), goalDefinition, true)), + List.of(new SchedulingGoal(new GoalId(0L, 0L), goalDefinition, true)), planningHorizon); final var planByActivityType = partitionByActivityType(results.updatedPlan()); final var biteBanana = planByActivityType.get("BiteBanana").stream().map((bb) -> bb.startOffset()).toList(); @@ -2307,7 +2316,7 @@ export default function myGoal() { Map.of(), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), goalDefinition, true)), + List.of(new SchedulingGoal(new GoalId(0L, 0L), goalDefinition, true)), List.of(), planningHorizon); final var planByActivityType = partitionByActivityType(results.updatedPlan()); @@ -2329,7 +2338,7 @@ void testDurationParameter() { final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function myGoal() { return Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.DurationParameterActivity({ @@ -2351,7 +2360,7 @@ void testUnfinishedActivity(){ final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => { return Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.parent({ @@ -2363,7 +2372,7 @@ export default (): Goal => { """, true)), PLANNING_HORIZON); //parent takes much more than 134 - 90 = 44 days to finish assertEquals(0, results.updatedPlan.size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertFalse(goalResult.satisfied()); } @@ -2375,7 +2384,7 @@ public void testBugDurationInMicroseconds(){ final var results = runScheduler( BANANANATION, List.of(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.BakeBananaBread({ temperature: 325.0, tbSugar: 2, glutenFree: false }), @@ -2386,7 +2395,7 @@ export default (): Goal => runScheduler( BANANANATION, results.updatedPlan.stream().toList(), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.BakeBananaBread({ temperature: 325.0, tbSugar: 2, glutenFree: false }), @@ -2407,20 +2416,20 @@ void test_inf_loop(){ Map.of("biteSize", SerializedValue.of(10)), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default (): Goal => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.BakeBananaBread({ temperature: 325.0, tbSugar: 2, glutenFree: false }), interval: Temporal.Duration.from({ hours: 2 }), }); """, true)), - List.of(new GlobalSchedulingConditionRecord(new GlobalSchedulingConditionSource( + List.of(new SchedulingConditionRecord(new SchedulingConditionId(0), 0L, "fruit less than 3", new SchedulingConditionSource( """ export default function myFirstSchedulingCondition(): GlobalSchedulingCondition { return GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource('/fruit').lessThan(3.0)); } """ - ), true)), + ))), new PlanningHorizon( TimeUtility.fromDOY("2022-318T00:00:00"), TimeUtility.fromDOY("2022-319T00:00:00"))); @@ -2467,7 +2476,7 @@ void testRelativeActivityPlanZeroStartOffsetEnd() { "duration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), new ActivityDirectiveId(2L), false)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2477,7 +2486,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2551,7 +2560,7 @@ void testRelativeActivityPlanZeroStartOffsetStart() { "duration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), new ActivityDirectiveId(2L), false)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2561,7 +2570,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2627,7 +2636,7 @@ void testRelativeActivityPlanNegativeStartOffsetStart() { "growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), new ActivityDirectiveId(1L), true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2637,7 +2646,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2701,7 +2710,7 @@ void testRelativeActivityPlanPositiveStartOffsetStart() { "quantity", SerializedValue.of(1)), new ActivityDirectiveId(1L), true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.PickBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2711,7 +2720,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2772,7 +2781,7 @@ void testJustAfter(String timepoint, Duration resultingStartTime) { "growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function(){ TimingConstraint.defaultPadding = Temporal.Duration.from({milliseconds:1}) return Goal.CoexistenceGoal({ @@ -2785,7 +2794,7 @@ export default function(){ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2842,7 +2851,7 @@ void testJustBefore(String timepoint, Duration resultingStartTime) { "growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), null, true)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2852,7 +2861,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2913,7 +2922,7 @@ void testRelativeActivityPlanPositiveEndOffsetEnd() { "growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), new ActivityDirectiveId(1L), false)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2923,7 +2932,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); assertEquals(1, goalResult.createdActivities().size()); @@ -2985,7 +2994,7 @@ void testDontScheduleFromOutsidePlanBounds(){ "growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))), null, false)), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -2995,7 +3004,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); // The goal is satisfied, but placed no activities, // because all activities it could've matched on are outside the plan bounds @@ -3049,7 +3058,7 @@ void testOptionalSimulationAfterGoal_unsimulatedActivities() { true) ), List.of( - new SchedulingGoal(new GoalId(0L), """ + new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.BananaNap(), @@ -3057,7 +3066,7 @@ export default () => Goal.CoexistenceGoal({ }) """, true, config.getKey() ), - new SchedulingGoal(new GoalId(1L), """ + new SchedulingGoal(new GoalId(1L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.BananaNap), activityTemplate: ActivityTemplates.DownloadBanana({connection: "DSL"}), @@ -3068,8 +3077,8 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(2, results.scheduleResults.goalResults().size()); - final var goalResult1 = results.scheduleResults.goalResults().get(new GoalId(0L)); - final var goalResult2 = results.scheduleResults.goalResults().get(new GoalId(1L)); + final var goalResult1 = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); + final var goalResult2 = results.scheduleResults.goalResults().get(new GoalId(1L, 0L)); assertTrue(goalResult1.satisfied()); assertTrue(goalResult2.satisfied()); @@ -3112,7 +3121,7 @@ void testOptionalSimulationAfterGoal_staleResources() { true) ), List.of( - new SchedulingGoal(new GoalId(0L), """ + new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), activityTemplate: ActivityTemplates.DownloadBanana({connection: "DSL"}), @@ -3120,7 +3129,7 @@ export default () => Goal.CoexistenceGoal({ }) """, true, config.getKey() ), - new SchedulingGoal(new GoalId(1L), """ + new SchedulingGoal(new GoalId(1L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: Real.Resource("/fruit").greaterThan(5), activityTemplate: ActivityTemplates.BananaNap(), @@ -3131,8 +3140,8 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(2, results.scheduleResults.goalResults().size()); - final var goalResult1 = results.scheduleResults.goalResults().get(new GoalId(0L)); - final var goalResult2 = results.scheduleResults.goalResults().get(new GoalId(1L)); + final var goalResult1 = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); + final var goalResult2 = results.scheduleResults.goalResults().get(new GoalId(1L, 0L)); assertTrue(goalResult1.satisfied()); assertTrue(goalResult2.satisfied()); @@ -3169,7 +3178,7 @@ void daemonTaskTest(){ null, true) ), - List.of(new SchedulingGoal(new GoalId(0L), """ + List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.ofType(ActivityTypes.ZeroDurationUncontrollableActivity), activityTemplate: ActivityTemplates.DaemonCheckerActivity({ @@ -3180,7 +3189,7 @@ export default () => Goal.CoexistenceGoal({ """, true)), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - assertTrue(results.scheduleResults.goalResults().get(new GoalId(0L)).satisfied()); + assertTrue(results.scheduleResults.goalResults().get(new GoalId(0L, 0L)).satisfied()); assertEquals(2, results.updatedPlan.size()); @@ -3204,7 +3213,7 @@ export default () => Goal.CoexistenceGoal({ */ @Test void testEmptyPlanMinimalMissionModelSimpleRecurrenceGoal() { - runScheduler(MINIMAL_MISSION_MODEL, List.of(), List.of(new SchedulingGoal(new GoalId(0L), """ + runScheduler(MINIMAL_MISSION_MODEL, List.of(), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.SingleActivity(), interval: Temporal.Duration.from({ milliseconds: 24 * 60 * 60 * 1000 }) @@ -3268,7 +3277,7 @@ void testForEachWithActivityArguments() { true) ), List.of( - new SchedulingGoal(new GoalId(0L), """ + new SchedulingGoal(new GoalId(0L, 0L), """ export default () => Goal.CoexistenceGoal({ forEach: ActivityExpression.build(ActivityTypes.GrowBanana, {quantity: 1}), activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), @@ -3280,7 +3289,7 @@ export default () => Goal.CoexistenceGoal({ PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); @@ -3294,7 +3303,7 @@ export default () => Goal.CoexistenceGoal({ @Test void testListOfListParam() { - final var results = runScheduler(FOO, List.of(), List.of(new SchedulingGoal(new GoalId(0L), """ + final var results = runScheduler(FOO, List.of(), List.of(new SchedulingGoal(new GoalId(0L, 0L), """ export default function myGoal() { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.foo({ @@ -3308,7 +3317,7 @@ export default function myGoal() { } """, true)),List.of(), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); - final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L)); + final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); assertTrue(goalResult.satisfied()); From 4fe7e3d7172a00f76e4198e3c42f882084466647 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 13:04:11 -0800 Subject: [PATCH 10/17] Retrieve Plan Revision from Request Notifications --- .../worker/postgres/PostgresNotificationJsonParsers.java | 2 ++ .../postgres/PostgresSchedulingRequestNotificationPayload.java | 1 + 2 files changed, 3 insertions(+) diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresNotificationJsonParsers.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresNotificationJsonParsers.java index abc2203f23..6bf3f060f9 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresNotificationJsonParsers.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresNotificationJsonParsers.java @@ -11,11 +11,13 @@ public final class PostgresNotificationJsonParsers { public static final JsonParser postgresSchedulingRequestNotificationP = productP . field("specification_revision", longP) + . field("plan_revision", longP) . field("specification_id", longP) . field("analysis_id", longP) . map( untuple(PostgresSchedulingRequestNotificationPayload::new), $ -> tuple($.specificationRevision(), + $.planRevision(), $.specificationId(), $.analysisId())); } diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresSchedulingRequestNotificationPayload.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresSchedulingRequestNotificationPayload.java index 133cdf5b3c..7175561a0d 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresSchedulingRequestNotificationPayload.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/postgres/PostgresSchedulingRequestNotificationPayload.java @@ -2,6 +2,7 @@ public record PostgresSchedulingRequestNotificationPayload( long specificationRevision, + long planRevision, long specificationId, long analysisId ) { } From 4de3b486ba30b6012ad7ee8dd01fbeb61535a347 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 13:02:23 -0800 Subject: [PATCH 11/17] Expand `SpecificationRevisionData` to include Plan Revision data - Use `SpecificationRevisionData` in place of `RevisionData` --- .../remotes/SpecificationRepository.java | 4 ++-- .../PostgresResultsCellRepository.java | 19 +++++++++++++------ .../PostgresSpecificationRepository.java | 9 ++++----- .../postgres/SpecificationRevisionData.java | 2 +- .../server/services/ScheduleAction.java | 9 ++++----- .../server/services/ScheduleRequest.java | 5 +++-- .../worker/SchedulerWorkerAppDriver.java | 3 ++- .../services/SchedulingIntegrationTests.java | 2 +- 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java index 9479808150..fbd62ab67b 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java @@ -4,11 +4,11 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; +import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; public interface SpecificationRepository { // Queries Specification getSpecification(SpecificationId specificationId) throws NoSuchSpecificationException, SpecificationLoadException; - RevisionData getSpecificationRevisionData(SpecificationId specificationId) throws NoSuchSpecificationException; + SpecificationRevisionData getSpecificationRevisionData(SpecificationId specificationId) throws NoSuchSpecificationException; } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index 96e4330ae7..90ccff0b4e 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -43,6 +43,7 @@ public ResultsProtocol.OwnerRole allocate(final SpecificationId specificationId, this.dataSource, new SpecificationId(request.specificationId()), request.specificationRevision(), + request.planRevision(), request.analysisId()); } catch (final SQLException ex) { throw new DatabaseException("Failed to get schedule specification", ex); @@ -70,6 +71,7 @@ public Optional claim(final SpecificationId specifica this.dataSource, new SpecificationId(request.specificationId()), request.specificationRevision(), + request.planRevision(), request.analysisId())); } catch (UnclaimableRequestException ex) { return Optional.empty(); @@ -130,11 +132,11 @@ private static SpecificationRecord getSpecification( private static Optional getRequest( final Connection connection, final SpecificationId specificationId, - final long specificationRevision + final long specificationRevision, + final long planRevision ) throws SQLException { try (final var getRequestAction = new GetRequestAction(connection)) { - return getRequestAction - .get(specificationId.id(), specificationRevision); + return getRequestAction.get(specificationId.id(), specificationRevision, planRevision); } } @@ -262,9 +264,10 @@ private static ScheduleResults getResults( private static Optional getRequestState( final Connection connection, final SpecificationId specId, - final long specRevision + final long specRevision, + final long planRevision ) throws SQLException { - final var request$ = getRequest(connection, specId, specRevision); + final var request$ = getRequest(connection, specId, specRevision, planRevision); if (request$.isEmpty()) return Optional.empty(); final var request = request$.get(); @@ -289,17 +292,20 @@ public static final class PostgresResultsCell implements ResultsProtocol.OwnerRo private final DataSource dataSource; private final SpecificationId specId; private final long specRevision; + private final long planRevision; private final long analysisId; public PostgresResultsCell( final DataSource dataSource, final SpecificationId specId, final long specRevision, + final long planRevision, final long analysisId ) { this.dataSource = dataSource; this.specId = specId; this.specRevision = specRevision; + this.planRevision = planRevision; this.analysisId = analysisId; } @@ -309,7 +315,8 @@ public ResultsProtocol.State get() { return getRequestState( connection, specId, - specRevision) + specRevision, + planRevision) .orElseThrow(() -> new Error("Scheduling request no longer exists")); } catch (final SQLException ex) { throw new DatabaseException("Failed to get scheduling request status", ex); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java index 355a4a6e7a..b09b690abd 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java @@ -57,17 +57,16 @@ public Specification getSpecification(final SpecificationId specificationId) } @Override - public RevisionData getSpecificationRevisionData(final SpecificationId specificationId) + public SpecificationRevisionData getSpecificationRevisionData(final SpecificationId specificationId) throws NoSuchSpecificationException { try (final var connection = this.dataSource.getConnection()) { try (final var getSpecificationAction = new GetSpecificationAction(connection)) { - final var specificationRevision = getSpecificationAction + final var spec = getSpecificationAction .get(specificationId.id()) - .orElseThrow(() -> new NoSuchSpecificationException(specificationId)) - .revision(); + .orElseThrow(() -> new NoSuchSpecificationException(specificationId)); - return new SpecificationRevisionData(specificationRevision); + return new SpecificationRevisionData(spec.revision(), spec.planRevision()); } } catch (final SQLException ex) { throw new DatabaseException("Failed to get scheduling specification revision data", ex); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRevisionData.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRevisionData.java index b9cf82bc36..8176a3b903 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRevisionData.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRevisionData.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; -public record SpecificationRevisionData(long specificationRevision) implements RevisionData { +public record SpecificationRevisionData(long specificationRevision, long planRevision) implements RevisionData { @Override public MatchResult matches(final RevisionData other) { if (!(other instanceof SpecificationRevisionData o)) return MatchResult.failure(""); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleAction.java index 56ac81661b..47f315fced 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleAction.java @@ -55,12 +55,11 @@ record Complete(ScheduleResults results, long analysisId, Optional dataset public Response run(final SpecificationId specificationId, final HasuraAction.Session session) throws NoSuchSpecificationException, IOException { - //record the plan revision as of the scheduling request time (in case work commences much later eg in worker thread) - //TODO may also need to verify the model revision / other volatile metadata matches one from request - final var specificationRev = this.specificationService.getSpecificationRevisionData(specificationId); + //record the plan and specification revision as of the scheduling request time (in case work commences much later in worker thread) + final var request = new ScheduleRequest(specificationId, this.specificationService.getSpecificationRevisionData(specificationId)); - //submit request to run scheduler (possibly asynchronously or even cached depending on service) - final var response = this.schedulerService.getScheduleResults(new ScheduleRequest(specificationId, specificationRev), session.hasuraUserId()); + //submit request to run scheduler workers + final var response = this.schedulerService.getScheduleResults(request, session.hasuraUserId()); return repackResponse(response); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleRequest.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleRequest.java index 002e86ba57..2a0ddcfeea 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleRequest.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleRequest.java @@ -2,14 +2,15 @@ import java.util.Objects; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; +import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; /** * details of a scheduling request, including the target schedule specification version and goals to operate on * * @param specificationId target schedule specification to read as schedule configuration - * @param specificationRev the revision of the schedule specification when the schedule request was placed (to determine if stale) + * @param specificationRev the revision of the schedule specification and plan when the schedule request was placed (to determine if stale) */ -public record ScheduleRequest(SpecificationId specificationId, RevisionData specificationRev) { +public record ScheduleRequest(SpecificationId specificationId, SpecificationRevisionData specificationRev) { public ScheduleRequest { Objects.requireNonNull(specificationId); Objects.requireNonNull(specificationRev); diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java index 79361bd1ca..f67a739832 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java @@ -83,6 +83,7 @@ public static void main(String[] args) throws Exception { final var notification = notificationQueue.poll(1, TimeUnit.MINUTES); if (notification == null) continue; final var specificationRevision = notification.specificationRevision(); + final var planRevision = notification.planRevision(); final var specificationId = new SpecificationId(notification.specificationId()); // Register as early as possible to avoid potentially missing a canceled signal @@ -94,7 +95,7 @@ public static void main(String[] args) throws Exception { continue; } - final var revisionData = new SpecificationRevisionData(specificationRevision); + final var revisionData = new SpecificationRevisionData(specificationRevision, planRevision); final ResultsProtocol.WriterRole writer = owner.get(); try { scheduleAgent.schedule(new ScheduleRequest(specificationId, revisionData), writer, canceledListener); diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java index 7c600303cd..e78247d804 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java @@ -2038,7 +2038,7 @@ private SchedulingRunResults runScheduler( schedulingDSLCompiler); // Scheduling Goals -> Scheduling Specification final var writer = new MockResultsProtocolWriter(); - agent.schedule(new ScheduleRequest(new SpecificationId(1L), $ -> RevisionData.MatchResult.success()), writer, () -> false); + agent.schedule(new ScheduleRequest(new SpecificationId(1L), new SpecificationRevisionData(1L, 1L)), writer, () -> false); assertEquals(1, writer.results.size()); final var result = writer.results.get(0); if (result instanceof MockResultsProtocolWriter.Result.Failure e) { From 71a784d5811e707021f783c930ee8ef190ec02ac Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 13:09:23 -0800 Subject: [PATCH 12/17] Have `claim` use analysisId instead of SpecificationId - This is a simpler key. Additionally, if the specification is deleted, the request will fail to be claimable (as it would've also been deleted), which removes a call to the database. --- .../server/remotes/ResultsCellRepository.java | 2 +- .../postgres/PostgresResultsCellRepository.java | 16 +++------------- .../worker/SchedulerWorkerAppDriver.java | 3 ++- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java index 14ba31abc4..966595a0e3 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java @@ -7,7 +7,7 @@ public interface ResultsCellRepository { ResultsProtocol.OwnerRole allocate(SpecificationId specificationId, final String requestedBy); - Optional claim(SpecificationId specificationId); + Optional claim(long analysisId); Optional lookup(SpecificationId specificationId); diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index 90ccff0b4e..c795f3fc6f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -53,20 +53,14 @@ public ResultsProtocol.OwnerRole allocate(final SpecificationId specificationId, } @Override - public Optional claim(final SpecificationId specificationId) + public Optional claim(final long analysisId) { try ( final var connection = this.dataSource.getConnection(); final var claimSimulationAction = new ClaimRequestAction(connection) ) { - claimSimulationAction.apply(specificationId.id()); - - final var spec = getSpecification(connection, specificationId); - final var request$ = getRequest(connection, specificationId, spec.revision()); - if (request$.isEmpty()) return Optional.empty(); - final var request = request$.get(); - - logger.info("Claimed scheduling request with specification id {}", specificationId); + final var request = claimSimulationAction.apply(analysisId); + logger.info("Claimed scheduling request with analysis id {}", analysisId); return Optional.of(new PostgresResultsCell( this.dataSource, new SpecificationId(request.specificationId()), @@ -75,10 +69,6 @@ public Optional claim(final SpecificationId specifica request.analysisId())); } catch (UnclaimableRequestException ex) { return Optional.empty(); - } catch (final NoSuchSpecificationException ex) { - throw new Error(String.format( - "Cannot process scheduling request for nonexistent specification %s%n", - specificationId), ex); } catch (final SQLException | DatabaseException ex) { throw new Error(ex.getMessage()); } diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java index f67a739832..253105dcaa 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/SchedulerWorkerAppDriver.java @@ -85,11 +85,12 @@ public static void main(String[] args) throws Exception { final var specificationRevision = notification.specificationRevision(); final var planRevision = notification.planRevision(); final var specificationId = new SpecificationId(notification.specificationId()); + final var analysisId = notification.analysisId(); // Register as early as possible to avoid potentially missing a canceled signal canceledListener.register(specificationId); - final Optional owner = stores.results().claim(specificationId); + final Optional owner = stores.results().claim(analysisId); if (owner.isEmpty()) { canceledListener.unregister(); continue; From 733e58138d6a78ce8bde077f874b499469666d73 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 13:12:40 -0800 Subject: [PATCH 13/17] Have `lookup` use SchedulingRequest instead of SpecificationId - The request already contains all the data that was being previously lookup up, saving a DB query and avoiding race conditions --- .../server/remotes/ResultsCellRepository.java | 3 ++- .../PostgresResultsCellRepository.java | 27 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java index 966595a0e3..1d64ec949b 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/ResultsCellRepository.java @@ -3,13 +3,14 @@ import java.util.Optional; import gov.nasa.jpl.aerie.scheduler.server.ResultsProtocol; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; +import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; public interface ResultsCellRepository { ResultsProtocol.OwnerRole allocate(SpecificationId specificationId, final String requestedBy); Optional claim(long analysisId); - Optional lookup(SpecificationId specificationId); + Optional lookup(ScheduleRequest request); void deallocate(ResultsProtocol.OwnerRole resultsCell); } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index c795f3fc6f..4e80ce37cd 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -18,6 +18,7 @@ import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleFailure; +import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults.GoalResult; import org.slf4j.Logger; @@ -75,23 +76,21 @@ public Optional claim(final long analysisId) } @Override - public Optional lookup(final SpecificationId specificationId) + public Optional lookup(final ScheduleRequest request) { try (final var connection = this.dataSource.getConnection()) { - final var spec = getSpecification(connection, specificationId); - final var request$ = getRequest(connection, specificationId, spec.revision()); + final var request$ = getRequest(connection, request); if (request$.isEmpty()) return Optional.empty(); - final var request = request$.get(); + final var r = request$.get(); return Optional.of(new PostgresResultsCell( this.dataSource, - new SpecificationId(request.specificationId()), - request.specificationRevision(), - request.analysisId())); + new SpecificationId(r.specificationId()), + r.specificationRevision(), + r.planRevision(), + r.analysisId())); } catch (final SQLException ex) { throw new DatabaseException("Failed to get schedule specification", ex); - } catch (final NoSuchSpecificationException ex) { - return Optional.empty(); } } @@ -119,6 +118,16 @@ private static SpecificationRecord getSpecification( } } + private static Optional getRequest( + final Connection connection, + final ScheduleRequest request + ) throws SQLException { + return getRequest(connection, + request.specificationId(), + request.specificationRev().specificationRevision(), + request.specificationRev().planRevision()); + } + private static Optional getRequest( final Connection connection, final SpecificationId specificationId, From f77708c721798775516954682f13da5bd2acf3a6 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Thu, 25 Jan 2024 13:16:29 -0800 Subject: [PATCH 14/17] Avoid needlessly reobtaining specification data --- .../services/SynchronousSchedulerAgent.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java index 1d2893c6d1..072667c14a 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java @@ -62,7 +62,6 @@ import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.GoalBuilder; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinService; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinServiceException; -import gov.nasa.jpl.aerie.scheduler.server.services.RevisionData; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.server.services.SchedulerAgent; @@ -118,8 +117,8 @@ public void schedule( final var specification = specificationService.getSpecification(request.specificationId()); final var planMetadata = merlinService.getPlanMetadata(specification.planId()); - ensureRequestIsCurrent(request); ensurePlanRevisionMatch(specification, planMetadata.planRev()); + ensureRequestIsCurrent(specification, request); //create scheduler problem seeded with initial plan final var schedulerMissionModel = loadMissionModel(planMetadata); final var planningHorizon = new PlanningHorizon( @@ -359,17 +358,19 @@ private long getMerlinPlanRev(final PlanId planId) { return merlinService.getPlanRevision(planId); } + /** - * confirms that specification revision still matches that expected by the scheduling request + * confirms that the scheduling request is still relevant + * (spec hasn't been updated between request being made and now) * * @param request the original request for scheduling, containing an intended starting specification revision * @throws ResultsProtocolFailure when the requested specification revision does not match the actual revision */ - private void ensureRequestIsCurrent(final ScheduleRequest request) throws NoSuchSpecificationException { - final var currentRevisionData = specificationService.getSpecificationRevisionData(request.specificationId()); - if (currentRevisionData.matches(request.specificationRev()) instanceof final RevisionData.MatchResult.Failure failure) { - throw new ResultsProtocolFailure("schedule specification with id %s is stale: %s".formatted( - request.specificationId(), failure)); + private void ensureRequestIsCurrent(final Specification specification, final ScheduleRequest request) + throws NoSuchSpecificationException { + if (specification.specificationRevision() != request.specificationRev().specificationRevision()) { + throw new ResultsProtocolFailure("schedule specification with id %s is no longer at revision %d".formatted( + request.specificationId(), request.specificationRev().specificationRevision())); } } From 3ef94c620c6d006bc2ef842e16ef0c3204a8cedf Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Wed, 31 Jan 2024 14:50:24 -0800 Subject: [PATCH 15/17] Add E2E Tests - Update queries used in tests - Collapse "insert goal" and "add goal to scheduling spec" into a single function --- .../nasa/jpl/aerie/e2e/SchedulingTests.java | 179 +++++++++++++----- .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 67 ++++--- .../jpl/aerie/e2e/utils/HasuraRequests.java | 97 ++++++---- 3 files changed, 232 insertions(+), 111 deletions(-) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java index 38c4ea588a..2955842836 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java @@ -11,11 +11,13 @@ import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.opentest4j.AssertionFailedError; import javax.json.Json; import javax.json.JsonObject; @@ -143,11 +145,11 @@ private void insertActivities() throws IOException { @Test void twoInARow() throws IOException { // Setup: Add Goal - final int bakeBananaBreadGoalId = hasura.insertSchedulingGoal( + final int bakeBananaBreadGoalId = hasura.createSchedulingSpecGoal( "BakeBanana Scheduling Test Goal", - modelId, - bakeBananaGoalDefinition); - hasura.createSchedulingSpecGoal(bakeBananaBreadGoalId, schedulingSpecId, 0); + bakeBananaGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan hasura.awaitScheduling(schedulingSpecId); @@ -174,14 +176,14 @@ void getSchedulingDSLTypeScript() throws IOException { @Test void schedulingRecurrenceGoal() throws IOException { // Setup: Add Goal - final int recurrenceGoalId = hasura.insertSchedulingGoal( + final int recurrenceGoalId = hasura.createSchedulingSpecGoal( "Recurrence Scheduling Test Goal", - modelId, - recurrenceGoalDefinition); - hasura.createSchedulingSpecGoal(recurrenceGoalId, schedulingSpecId, 0); + recurrenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan - hasura.awaitScheduling(schedulingSpecId); + hasura.awaitScheduling(schedulingSpecId, 100000000); final var plan = hasura.getPlan(planId); final var activities = plan.activityDirectives(); @@ -198,11 +200,11 @@ void schedulingRecurrenceGoal() throws IOException { void schedulingCoexistenceGoal() throws IOException { // Setup: Add Goal and Activities insertActivities(); - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan @@ -235,16 +237,16 @@ void schedulingCoexistenceGoal() throws IOException { void schedulingMultipleGoals() throws IOException { // Setup: Add Goals insertActivities(); - final int recurrenceGoalId = hasura.insertSchedulingGoal( + final int recurrenceGoalId = hasura.createSchedulingSpecGoal( "Recurrence Scheduling Test Goal", - modelId, - recurrenceGoalDefinition); - hasura.createSchedulingSpecGoal(recurrenceGoalId, schedulingSpecId, 0); - final int coexistenceGoalId = hasura.insertSchedulingGoal( + recurrenceGoalDefinition, + schedulingSpecId, + 0); + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 1); + coexistenceGoalDefinition, + schedulingSpecId, + 1); try { // Schedule and get Plan hasura.awaitScheduling(schedulingSpecId); @@ -360,11 +362,11 @@ void outdatedPlanRevision() throws IOException { hasura.insertActivity(planId, "GrowBanana", "5h", JsonObject.EMPTY_JSON_OBJECT); // Setup: Add Goal - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { hasura.updatePlanRevisionSchedulingSpec(planId); @@ -411,11 +413,11 @@ void outdatedSimConfig() throws IOException { hasura.awaitSimulation(planId); hasura.deleteSimTemplate(templateId); // Return to blank sim config args - final int plantGoal = hasura.insertSchedulingGoal( + final int plantGoal = hasura.createSchedulingSpecGoal( "Scheduling Test: When Plant < 300", - modelId, - plantCountGoalDefinition); - hasura.createSchedulingSpecGoal(plantGoal, schedulingSpecId, 0); + plantCountGoalDefinition, + schedulingSpecId, + 0); try { hasura.awaitScheduling(schedulingSpecId); @@ -458,11 +460,11 @@ void injectedResultsLoaded() throws IOException{ List.of(new ProfileSegment("0h", false, Json.createValue(400)))); // Insert Goal - final int plantGoal = hasura.insertSchedulingGoal( + final int plantGoal = hasura.createSchedulingSpecGoal( "Scheduling Test: When Plant < 300", - modelId, - plantCountGoalDefinition); - hasura.createSchedulingSpecGoal(plantGoal, schedulingSpecId, 0); + plantCountGoalDefinition, + schedulingSpecId, + 0); try { hasura.awaitScheduling(schedulingSpecId); @@ -486,11 +488,11 @@ void temporalSubsetExcluded() throws IOException { hasura.awaitSimulation(planId); // Setup: Add Goal - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan @@ -544,16 +546,16 @@ void beforeEach() throws IOException { false); // Add Goal - cardinalityGoalId = hasura.insertSchedulingGoal( + cardinalityGoalId = hasura.createSchedulingSpecGoal( "Cardinality and Decomposition Scheduling Test Goal", - modelId, """ export default function cardinalityGoalExample() { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.parent({ label: "unlabeled"}), specification: { duration: Temporal.Duration.from({ seconds: 10 }) }, - });}"""); - hasura.createSchedulingSpecGoal(cardinalityGoalId, schedulingSpecId, 0); + });}""", + schedulingSpecId, + 0); } @AfterEach @@ -632,9 +634,8 @@ void beforeEach() throws IOException { List.of(myBooleanProfile)); // Insert Goal - edGoalId = hasura.insertSchedulingGoal( + edGoalId = hasura.createSchedulingSpecGoal( "On my_boolean true", - modelId, """ export default function myGoal() { return Goal.CoexistenceGoal({ @@ -642,9 +643,9 @@ export default function myGoal() { activityTemplate: ActivityTemplates.BiteBanana({ biteSize: 1, }), startsAt:TimingConstraint.singleton(WindowProperty.END) }) - }"""); - // Add the goal - hasura.createSchedulingSpecGoal(edGoalId, schedulingSpecId, 0); + }""", + schedulingSpecId, + 0); } @AfterEach @@ -730,7 +731,7 @@ void beforeEach() throws IOException, InterruptedException { gateway.uploadFooJar(), "Foo (e2e tests)", "aerie_e2e_tests", - "Simulation Tests"); + "Scheduling Tests"); } // Insert the Plan fooPlan = hasura.createPlan( @@ -749,17 +750,17 @@ void beforeEach() throws IOException, InterruptedException { false); // Add Goal - fooGoalId = hasura.insertSchedulingGoal( + fooGoalId = hasura.createSchedulingSpecGoal( "Foo Recurrence Test Goal", - fooId, """ export default function recurrenceGoalExample() { return Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.bar(), interval: Temporal.Duration.from({ hours: 2 }), }); - }"""); - hasura.createSchedulingSpecGoal(fooGoalId, fooSchedulingSpecId, 0); + }""", + fooSchedulingSpecId, + 0); } @AfterEach @@ -795,4 +796,80 @@ void cancelingSchedulingUpdatesRequestReason() throws IOException { assertEquals("Scheduling was interrupted while "+ reasonData.getString("location"), reasonData.getString("message")); } } + + @Nested + class VersioningSchedulingGoals { + @Test + void goalVersionLocking() throws IOException { + final int goalId = hasura.createSchedulingSpecGoal( + "coexistence goal", + coexistenceGoalDefinition, + schedulingSpecId, + 0); + + try { + // Update the plan's constraint specification to use a specific version + hasura.updateSchedulingSpecVersion(schedulingSpecId, goalId, 0); + + // Update definition to have invalid syntax + final int newRevision = hasura.updateGoalDefinition( + goalId, + "error :-("); + + // Schedule -- should succeed + final var initResults = hasura.awaitScheduling(schedulingSpecId); + assertEquals("complete", initResults.status()); + + // Update scheduling spec to use invalid definition + hasura.updateSchedulingSpecVersion(schedulingSpecId, goalId, newRevision); + + // Schedule -- should fail + final var error = Assertions.assertThrows( + AssertionFailedError.class, + () -> hasura.awaitScheduling(schedulingSpecId)); + final var expectedMsg = "Scheduling returned bad status failed with reason {\"data\":[{\"errors\":[" + + "{\"location\":{\"column\":1,\"line\":1},\"message\":\"TypeError: TS2306 No default " + + "export. Expected a default export function with the signature:"; + if (!error.getMessage().contains(expectedMsg)) { + throw error; + } + } finally { + hasura.deleteSchedulingGoal(goalId); + } + } + + @Test + void schedulingIgnoreDisabledGoals() throws IOException { + // Add a problematic goal to the spec, then disable it + final int problemGoalId = hasura.createSchedulingSpecGoal( + "bad goal", + "error :-(", + "Goal that won't compile", + schedulingSpecId, + 0); + try { + hasura.updateSchedulingSpecEnabled(schedulingSpecId, problemGoalId, false); + + // Schedule -- Validate that the plan didn't change + hasura.awaitScheduling(schedulingSpecId); + assertEquals(0, hasura.getPlan(planId).activityDirectives().size()); + + // Enable disabled constraint + hasura.updateSchedulingSpecEnabled(schedulingSpecId, problemGoalId, true); + + // Schedule -- Assert Fail + final var error = Assertions.assertThrows( + AssertionFailedError.class, + () -> hasura.awaitScheduling(schedulingSpecId)); + final var expectedMsg = "Scheduling returned bad status failed with reason {\"data\":[{\"errors\":[" + + "{\"location\":{\"column\":1,\"line\":1},\"message\":\"TypeError: TS2306 No default " + + "export. Expected a default export function with the signature:"; + if (!error.getMessage().contains(expectedMsg)) { + throw error; + } + } finally { + hasura.deleteSchedulingGoal(problemGoalId); + } + } + } } diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java index a2dda9d2d1..fd9b550dd1 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java @@ -27,15 +27,13 @@ mutation AssignTemplateToSimulation($simulation_id: Int!, $simulation_template_i }"""), CANCEL_SCHEDULING(""" mutation cancelScheduling($analysis_id: Int!) { - update_scheduling_request(where: {analysis_id: {_eq: $analysis_id}}, _set: {canceled: true}) { - returning { - analysis_id - specification_id - specification_revision - canceled - reason - status - } + update_scheduling_request_by_pk(pk_columns: {analysis_id: $analysis_id}, _set: {canceled: true}) { + analysis_id + specification_id + specification_revision + canceled + reason + status } }"""), CANCEL_SIMULATION(""" @@ -97,21 +95,6 @@ mutation CreatePlan($plan: plan_insert_input!) { revision } }"""), - CREATE_SCHEDULING_GOAL(""" - mutation CreateSchedulingGoal($goal: scheduling_goal_insert_input!) { - goal: insert_scheduling_goal_one(object: $goal) { - author - created_date - definition - description - id - last_modified_by - model_id - modified_date - name - revision - } - }"""), CREATE_SCHEDULING_SPEC_GOAL(""" mutation CreateSchedulingSpecGoal($spec_goal: scheduling_specification_goals_insert_input!) { insert_scheduling_specification_goals_one(object: $spec_goal) { @@ -184,9 +167,11 @@ mutation DeletePlan($id: Int!) { }"""), DELETE_SCHEDULING_GOAL(""" mutation DeleteSchedulingGoal($goalId: Int!) { - delete_scheduling_goal_by_pk(id: $goalId) { + delete_scheduling_specification_goals(where: {goal_id: {_eq: $goalId}}){ + affected_rows + } + delete_scheduling_goal_metadata_by_pk(id: $goalId) { name - definition } }"""), DELETE_SIMULATION_PRESET(""" @@ -374,8 +359,8 @@ query GetSchedulingDslTypeScript($missionModelId: Int!, $planId: Int) { } }"""), GET_SCHEDULING_REQUEST(""" - query GetSchedulingRequest($specificationId: Int!, $specificationRev: Int!) { - scheduling_request_by_pk(specification_id: $specificationId, specification_revision: $specificationRev) { + query GetSchedulingRequest($analysisId: Int!) { + scheduling_request_by_pk(analysis_id: $analysisId) { specification_id specification_revision analysis_id @@ -561,6 +546,12 @@ mutation updateConstraintSpecVersion($plan_id: Int!, $constraint_id: Int!, $enab enabled } }"""), + UPDATE_GOAL_DEFINITION(""" + mutation updateGoalDefinition($goal_id: Int!, $definition: String!) { + definition: insert_scheduling_goal_definition_one(object: {goal_id: $goal_id, definition: $definition}) { + revision + } + }"""), UPDATE_ROLE_ACTION_PERMISSIONS(""" mutation updateRolePermissions($role: user_roles_enum!, $action_permissions: jsonb!) { permissions: update_user_role_permission_by_pk( @@ -570,6 +561,26 @@ mutation updateRolePermissions($role: user_roles_enum!, $action_permissions: jso action_permissions } }"""), + UPDATE_SCHEDULING_SPEC_GOALS_ENABLED(""" + mutation updateSchedulingSpecGoalVersion($spec_id: Int!, $goal_id: Int!, $enabled: Boolean!) { + update_scheduling_specification_goals_by_pk( + pk_columns: {specification_id: $spec_id, goal_id: $goal_id}, + _set: {enabled: $enabled}) + { + goal_revision + enabled + } + }"""), + UPDATE_SCHEDULING_SPEC_GOALS_VERSION(""" + mutation updateSchedulingSpecGoalVersion($spec_id: Int!, $goal_id: Int!, $goal_revision: Int!) { + update_scheduling_specification_goals_by_pk( + pk_columns: {specification_id: $spec_id, goal_id: $goal_id}, + _set: {goal_revision: $goal_revision}) + { + goal_revision + enabled + } + }"""), UPDATE_SCHEDULING_SPECIFICATION_PLAN_REVISION(""" mutation updateSchedulingSpec($planId: Int!, $planRev: Int!) { update_scheduling_specification( diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java index 6d66ea2333..29f55cff88 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java @@ -455,18 +455,12 @@ private SchedulingResponse schedule(int schedulingSpecId) throws IOException { private SchedulingRequest cancelSchedulingRun(int analysisId, int timeout) throws IOException { final var variables = Json.createObjectBuilder().add("analysis_id", analysisId).build(); - //assert that we only canceled one task - final var cancelRequest = makeRequest(GQL.CANCEL_SCHEDULING, variables) - .getJsonObject("update_scheduling_request") - .getJsonArray("returning"); - assertEquals(1, cancelRequest.size()); - final int specId = cancelRequest.getJsonObject(0).getInt("specification_id"); - final int specRev = cancelRequest.getJsonObject(0).getInt("specification_revision"); + makeRequest(GQL.CANCEL_SCHEDULING, variables); for(int i = 0; i Date: Fri, 1 Mar 2024 09:15:33 -0800 Subject: [PATCH 16/17] Restore dropped relationship "analyses" from scheduling goal tables --- .../tables/public_scheduling_goal_definition.yaml | 9 +++++++++ .../tables/public_scheduling_goal_metadata.yaml | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml index 9f57b83529..d6cd74353e 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_definition.yaml @@ -6,6 +6,15 @@ object_relationships: using: foreign_key_constraint_on: goal_id array_relationships: + - name: analyses + using: + foreign_key_constraint_on: + columns: + - goal_id + - goal_revision + table: + name: scheduling_goal_analysis + schema: public - name: tags using: foreign_key_constraint_on: diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml index ddfe1b6a0b..cbd9c47f25 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_goal_metadata.yaml @@ -2,6 +2,14 @@ table: name: scheduling_goal_metadata schema: public array_relationships: + - name: analyses + using: + manual_configuration: + column_mapping: + id: goal_id + remote_table: + name: scheduling_goal_analysis + schema: public - name: tags using: foreign_key_constraint_on: From ca5ecf9d049f085142249e8d31e12bc0b4fcb625 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Tue, 5 Mar 2024 09:02:02 -0800 Subject: [PATCH 17/17] Restore dropped permission to insert/update "simulate_after" in the scheduling spec --- .../tables/public_scheduling_specification_goals.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml index c71679a788..8af1f31c22 100644 --- a/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml +++ b/deployment/hasura/metadata/databases/AerieScheduler/tables/public_scheduling_specification_goals.yaml @@ -33,20 +33,20 @@ select_permissions: insert_permissions: - role: aerie_admin permission: - columns: [specification_id, goal_id, goal_revision, priority, enabled] + columns: [specification_id, goal_id, goal_revision, priority, enabled, simulate_after] check: {} - role: user permission: - columns: [specification_id, goal_id, goal_revision, priority, enabled] + columns: [specification_id, goal_id, goal_revision, priority, enabled, simulate_after] check: {} update_permissions: - role: aerie_admin permission: - columns: [goal_revision, priority, enabled] + columns: [goal_revision, priority, enabled, simulate_after] filter: {} - role: user permission: - columns: [goal_revision, priority, enabled] + columns: [goal_revision, priority, enabled, simulate_after] filter: {} delete_permissions: - role: aerie_admin