|
4 | 4 | import dev.dbos.transact.Constants; |
5 | 5 | import dev.dbos.transact.exceptions.*; |
6 | 6 | import dev.dbos.transact.json.JSONUtil; |
| 7 | +import dev.dbos.transact.workflow.ForkOptions; |
7 | 8 | import dev.dbos.transact.workflow.ListWorkflowsInput; |
8 | 9 | import dev.dbos.transact.workflow.WorkflowState; |
9 | 10 | import dev.dbos.transact.workflow.WorkflowStatus; |
@@ -658,6 +659,28 @@ public void recordChildWorkflow(String parentId, |
658 | 659 |
|
659 | 660 | } |
660 | 661 |
|
| 662 | + public Optional<String> checkChildWorkflow(String workflowUuid, int functionId) throws SQLException { |
| 663 | + String sql = "SELECT child_workflow_id " + |
| 664 | + " FROM dbos.operation_outputs " + |
| 665 | + "WHERE workflow_uuid = ? AND function_id = ? " ; |
| 666 | + |
| 667 | + |
| 668 | + try (Connection connection = dataSource.getConnection(); |
| 669 | + PreparedStatement stmt = connection.prepareStatement(sql)) { |
| 670 | + |
| 671 | + stmt.setString(1, workflowUuid); |
| 672 | + stmt.setInt(2, functionId); |
| 673 | + |
| 674 | + try (ResultSet rs = stmt.executeQuery()) { |
| 675 | + if (rs.next()) { |
| 676 | + String childWorkflowId = rs.getString("child_workflow_id"); |
| 677 | + return childWorkflowId != null ? Optional.of(childWorkflowId) : Optional.empty(); |
| 678 | + } |
| 679 | + return Optional.empty(); |
| 680 | + } |
| 681 | + } |
| 682 | + } |
| 683 | + |
661 | 684 | public void cancelWorkflow(String workflowId) throws SQLException { |
662 | 685 |
|
663 | 686 | try (Connection conn = dataSource.getConnection()) { |
@@ -733,6 +756,118 @@ public void resumeWorkflow(String workflowId) throws SQLException { |
733 | 756 | } |
734 | 757 | } |
735 | 758 |
|
| 759 | + public String forkWorkflow(String originalWorkflowId, |
| 760 | + int startStep, |
| 761 | + ForkOptions options) throws SQLException { |
| 762 | + |
| 763 | + String forkedWorkflowId = options.getForkedWorkflowId() == null ? |
| 764 | + UUID.randomUUID().toString() : options.getForkedWorkflowId() ; |
| 765 | + |
| 766 | + logger.info("Original " + originalWorkflowId + "forked " + forkedWorkflowId) ; |
| 767 | + |
| 768 | + String applicationVersion = options.getApplicationVersion() ; |
| 769 | + |
| 770 | + WorkflowStatus status = getWorkflowStatus(originalWorkflowId) ; |
| 771 | + |
| 772 | + long timeoutMs = options.getTimeoutMS() == 0 ? status.getWorkflowTimeoutMs() : options.getTimeoutMS(); |
| 773 | + |
| 774 | + if (status == null) { |
| 775 | + throw new NonExistentWorkflowException(originalWorkflowId); |
| 776 | + } |
| 777 | + |
| 778 | + try (Connection connection = dataSource.getConnection()) { |
| 779 | + connection.setAutoCommit(false); |
| 780 | + |
| 781 | + try { |
| 782 | + // Create entry for forked workflow |
| 783 | + insertForkedWorkflowStatus(connection, forkedWorkflowId, status, applicationVersion, timeoutMs); |
| 784 | + |
| 785 | + // Copy operation outputs if starting from step > 0 |
| 786 | + if (startStep > 0) { |
| 787 | + copyOperationOutputs(connection, originalWorkflowId, forkedWorkflowId, startStep); |
| 788 | + } |
| 789 | + |
| 790 | + connection.commit(); |
| 791 | + return forkedWorkflowId; |
| 792 | + |
| 793 | + } catch (SQLException e) { |
| 794 | + connection.rollback(); |
| 795 | + throw e; |
| 796 | + } |
| 797 | + } |
| 798 | + } |
| 799 | + |
| 800 | + private void insertForkedWorkflowStatus(Connection connection, |
| 801 | + String forkedWorkflowId, |
| 802 | + WorkflowStatus originalStatus, |
| 803 | + String applicationVersion, |
| 804 | + long timeoutMs) throws SQLException { |
| 805 | + |
| 806 | + long workflowDeadlineEpoch = 0 ; |
| 807 | + if (timeoutMs > 0) { |
| 808 | + workflowDeadlineEpoch = System.currentTimeMillis() + timeoutMs ; |
| 809 | + } |
| 810 | + |
| 811 | + String sql = "INSERT INTO dbos.workflow_status ( " + |
| 812 | + " workflow_uuid, status, name, class_name, config_name, application_version, application_id, " + |
| 813 | + " authenticated_user, authenticated_roles, assumed_role, queue_name, inputs, workflow_deadline_epoch_ms, workflow_timeout_ms " + |
| 814 | + " ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " ; |
| 815 | + |
| 816 | + try (PreparedStatement stmt = connection.prepareStatement(sql)) { |
| 817 | + stmt.setString(1, forkedWorkflowId); |
| 818 | + stmt.setString(2, WorkflowState.ENQUEUED.name()); |
| 819 | + stmt.setString(3, originalStatus.getName()); |
| 820 | + stmt.setString(4, originalStatus.getClassName()); |
| 821 | + stmt.setString(5, originalStatus.getConfigName()); |
| 822 | + |
| 823 | + // Use provided application version or fall back to original |
| 824 | + String appVersion = applicationVersion != null ? |
| 825 | + applicationVersion : originalStatus.getAppVersion(); |
| 826 | + stmt.setString(6, appVersion); |
| 827 | + |
| 828 | + stmt.setString(7, originalStatus.getAppId()); |
| 829 | + stmt.setString(8, originalStatus.getAuthenticatedUser()); |
| 830 | + stmt.setString(9, JSONUtil.serializeArray(originalStatus.getAuthenticatedRoles())); |
| 831 | + stmt.setString(10, originalStatus.getAssumedRole()); |
| 832 | + stmt.setString(11, Constants.DBOS_INTERNAL_QUEUE); |
| 833 | + stmt.setString(12, JSONUtil.serializeArray(originalStatus.getInput())); |
| 834 | + stmt.setLong(13, workflowDeadlineEpoch); |
| 835 | + stmt.setLong(14, originalStatus.getWorkflowTimeoutMs()); |
| 836 | + |
| 837 | + |
| 838 | + stmt.executeUpdate(); |
| 839 | + } |
| 840 | + } |
| 841 | + |
| 842 | + private void copyOperationOutputs(Connection connection, |
| 843 | + String originalWorkflowId, |
| 844 | + String forkedWorkflowId, |
| 845 | + int startStep) throws SQLException { |
| 846 | + |
| 847 | + String sql = "INSERT INTO dbos.operation_outputs ( " + |
| 848 | + " workflow_uuid, function_id, output, error, function_name, child_workflow_id) " + |
| 849 | + " SELECT ? as workflow_uuid, function_id, output, error, function_name, child_workflow_id " + |
| 850 | + " FROM dbos.operation_outputs " + |
| 851 | + " WHERE workflow_uuid = ? " + |
| 852 | + " AND function_id < ? " ; |
| 853 | + |
| 854 | + |
| 855 | + try (PreparedStatement stmt = connection.prepareStatement(sql)) { |
| 856 | + stmt.setString(1, forkedWorkflowId); |
| 857 | + stmt.setString(2, originalWorkflowId); |
| 858 | + stmt.setInt(3, startStep); |
| 859 | + |
| 860 | + int rowsCopied = stmt.executeUpdate(); |
| 861 | + System.out.println("Copied " + rowsCopied + " operation outputs to forked workflow"); |
| 862 | + } |
| 863 | + } |
| 864 | + |
| 865 | + /* public String forkWorkflow(String originalWorkflowId, |
| 866 | + String forkedWorkflowId, |
| 867 | + int startStep) throws SQLException { |
| 868 | + return forkWorkflow(originalWorkflowId, forkedWorkflowId, startStep, null); |
| 869 | + } */ |
| 870 | + |
736 | 871 | private String getWorkflowStatus(Connection connection, String workflowId) throws SQLException { |
737 | 872 | String sql = "SELECT status FROM dbos.workflow_status WHERE workflow_uuid = ?"; |
738 | 873 |
|
|
0 commit comments