View Javadoc
1   /*
2    *    Copyright 2010-2023 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.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.ibatis.migration.Change;
28  import org.apache.ibatis.migration.ConnectionProvider;
29  import org.apache.ibatis.migration.MigrationException;
30  import org.apache.ibatis.migration.MigrationLoader;
31  import org.apache.ibatis.migration.hook.HookContext;
32  import org.apache.ibatis.migration.hook.MigrationHook;
33  import org.apache.ibatis.migration.options.DatabaseOperationOption;
34  import org.apache.ibatis.migration.utils.Util;
35  
36  public final class PendingOperation extends DatabaseOperation {
37  
38    public PendingOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
39        DatabaseOperationOption option, PrintStream printStream) {
40      return operate(connectionProvider, migrationsLoader, option, printStream, null);
41    }
42  
43    public PendingOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
44        DatabaseOperationOption option, PrintStream printStream, MigrationHook hook) {
45      try (Connection con = connectionProvider.getConnection()) {
46        if (option == null) {
47          option = new DatabaseOperationOption();
48        }
49        if (!changelogExists(con, option)) {
50          throw new MigrationException("Change log doesn't exist, no migrations applied.  Try running 'up' instead.");
51        }
52        List<Change> pending = getPendingChanges(con, migrationsLoader, option);
53        int stepCount = 0;
54        Map<String, Object> hookBindings = new HashMap<>();
55        println(printStream, "WARNING: Running pending migrations out of order can create unexpected results.");
56        try {
57          ScriptRunner runner = getScriptRunner(con, option, printStream);
58          for (Change change : pending) {
59            if (stepCount == 0 && hook != null) {
60              hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
61              hook.before(hookBindings);
62            }
63            if (hook != null) {
64              hookBindings.put(MigrationHook.HOOK_CONTEXT,
65                  new HookContext(connectionProvider, runner, new Change(change)));
66              hook.beforeEach(hookBindings);
67            }
68            println(printStream, Util.horizontalLine("Applying: " + change.getFilename(), 80));
69            try (Reader scriptReader = migrationsLoader.getScriptReader(change, false)) {
70              runner.runScript(scriptReader);
71            }
72            insertChangelog(change, con, option);
73            println(printStream);
74            if (hook != null) {
75              hookBindings.put(MigrationHook.HOOK_CONTEXT,
76                  new HookContext(connectionProvider, runner, new Change(change)));
77              hook.afterEach(hookBindings);
78            }
79            stepCount++;
80          }
81          if (stepCount > 0 && hook != null) {
82            hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
83            hook.after(hookBindings);
84          }
85          return this;
86        } catch (Exception e) {
87          throw new MigrationException("Error executing command.  Cause: " + e, e);
88        }
89      } catch (Throwable e) {
90        while (e instanceof MigrationException && e.getCause() != null) {
91          e = e.getCause();
92        }
93        throw new MigrationException("Error executing command.  Cause: " + e, e);
94      }
95    }
96  
97    private List<Change> getPendingChanges(Connection con, MigrationLoader migrationsLoader,
98        DatabaseOperationOption option) {
99      List<Change> pending = new ArrayList<>();
100     List<Change> migrations = migrationsLoader.getMigrations();
101     List<Change> changelog = getChangelog(con, option);
102     for (Change change : migrations) {
103       int index = changelog.indexOf(change);
104       if (index < 0) {
105         pending.add(change);
106       }
107     }
108     Collections.sort(pending);
109     return pending;
110   }
111 }