View Javadoc
1   /*
2    *    Copyright 2010-2026 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration.operations;
17  
18  import java.io.PrintStream;
19  import java.io.Reader;
20  import java.sql.Connection;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.ibatis.migration.Change;
27  import org.apache.ibatis.migration.ConnectionProvider;
28  import org.apache.ibatis.migration.MigrationException;
29  import org.apache.ibatis.migration.MigrationLoader;
30  import org.apache.ibatis.migration.hook.HookContext;
31  import org.apache.ibatis.migration.hook.MigrationHook;
32  import org.apache.ibatis.migration.options.DatabaseOperationOption;
33  import org.apache.ibatis.migration.utils.Util;
34  
35  public final class PendingOperation extends DatabaseOperation {
36  
37    public PendingOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
38        DatabaseOperationOption option, PrintStream printStream) {
39      return operate(connectionProvider, migrationsLoader, option, printStream, null);
40    }
41  
42    public PendingOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
43        DatabaseOperationOption option, PrintStream printStream, MigrationHook hook) {
44      try (Connection con = connectionProvider.getConnection()) {
45        if (option == null) {
46          option = new DatabaseOperationOption();
47        }
48        if (!changelogExists(con, option)) {
49          throw new MigrationException("Change log doesn't exist, no migrations applied.  Try running 'up' instead.");
50        }
51        List<Change> pending = getPendingChanges(con, migrationsLoader, option);
52        int stepCount = 0;
53        Map<String, Object> hookBindings = new HashMap<>();
54        println(printStream, "WARNING: Running pending migrations out of order can create unexpected results.");
55        try {
56          ScriptRunner runner = getScriptRunner(con, option, printStream);
57          for (Change change : pending) {
58            if (stepCount == 0 && hook != null) {
59              hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
60              hook.before(hookBindings);
61            }
62            if (hook != null) {
63              hookBindings.put(MigrationHook.HOOK_CONTEXT,
64                  new HookContext(connectionProvider, runner, new Change(change)));
65              hook.beforeEach(hookBindings);
66            }
67            println(printStream, Util.horizontalLine("Applying: " + change.getFilename(), 80));
68            try (Reader scriptReader = migrationsLoader.getScriptReader(change, false)) {
69              runner.runScript(scriptReader);
70            }
71            insertChangelog(change, con, option);
72            println(printStream);
73            if (hook != null) {
74              hookBindings.put(MigrationHook.HOOK_CONTEXT,
75                  new HookContext(connectionProvider, runner, new Change(change)));
76              hook.afterEach(hookBindings);
77            }
78            stepCount++;
79          }
80          if (stepCount > 0 && hook != null) {
81            hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
82            hook.after(hookBindings);
83          }
84          return this;
85        } catch (Exception e) {
86          throw new MigrationException("Error executing command.  Cause: " + e, e);
87        }
88      } catch (Throwable e) {
89        while (e instanceof MigrationException && e.getCause() != null) {
90          e = e.getCause();
91        }
92        throw new MigrationException("Error executing command.  Cause: " + e, e);
93      }
94    }
95  
96    private List<Change> getPendingChanges(Connection con, MigrationLoader migrationsLoader,
97        DatabaseOperationOption option) {
98      List<Change> pending = new ArrayList<>();
99      List<Change> migrations = migrationsLoader.getMigrations();
100     List<Change> changelog = getChangelog(con, option);
101     for (Change change : migrations) {
102       int index = changelog.indexOf(change);
103       if (index < 0) {
104         pending.add(change);
105       }
106     }
107     pending.sort(null);
108     return pending;
109   }
110 }