@Override
    public <T extends Schedule> void updateSchedules(
        int projId, List<T> schedules, ScheduleUpdateAction<T> func)
        throws ResourceConflictException {
      Map<String, Integer> oldScheduleNames = idNameListToHashMap(dao.getScheduleNames(projId));

      // Concurrent call of updateSchedules doesn't happen because having
      // ProjectControlStore means that the project is locked.
      //
      // However, ScheduleExecutor modifies schedules without locking the
      // project. Instead, ScheduleExecutor locks schedules. To avoid
      // concurrent update of schedules, here needs to lock schedules
      // before UPDATE.

      for (T schedule : schedules) {
        Integer matchedSchedId = oldScheduleNames.get(schedule.getWorkflowName());
        if (matchedSchedId != null) {
          // found the same name. lock it and update
          ScheduleStatus status = dao.lockScheduleById(matchedSchedId);
          if (status != null) {
            ScheduleTime newSchedule = func.apply(status, schedule);
            dao.updateScheduleById(
                matchedSchedId,
                schedule.getWorkflowDefinitionId(),
                newSchedule.getRunTime().getEpochSecond(),
                newSchedule.getTime().getEpochSecond());
            oldScheduleNames.remove(schedule.getWorkflowName());
          }
        } else {
          // not found this name. inserting a new entry.
          catchConflict(
              () ->
                  dao.insertSchedule(
                      projId,
                      schedule.getWorkflowDefinitionId(),
                      schedule.getNextRunTime().getEpochSecond(),
                      schedule.getNextScheduleTime().getEpochSecond()),
              "workflow_definition_id=%d",
              schedule.getWorkflowDefinitionId());
        }
      }

      // delete unused schedules
      if (!oldScheduleNames.isEmpty()) {
        // those names don exist any more.
        handle
            .createStatement(
                "delete from schedules"
                    + " where id "
                    + inLargeIdListExpression(oldScheduleNames.values()))
            .execute();
      }
    }
 @Override
 public ScheduleStatus map(int index, ResultSet r, StatementContext ctx) throws SQLException {
   return ScheduleStatus.of(
       ScheduleTime.of(
           Instant.ofEpochSecond(r.getLong("next_schedule_time")),
           Instant.ofEpochSecond(r.getLong("next_run_time"))),
       getOptionalLong(r, "last_session_time").transform(it -> Instant.ofEpochSecond(it)));
 }