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.sql.SQLException;
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 DownOperation extends DatabaseOperation {
37    private Integer steps;
38  
39    public DownOperation() {
40      this(null);
41    }
42  
43    public DownOperation(Integer steps) {
44      this.steps = steps;
45    }
46  
47    public DownOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
48        DatabaseOperationOption option, PrintStream printStream) {
49      return operate(connectionProvider, migrationsLoader, option, printStream, null);
50    }
51  
52    public DownOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
53        DatabaseOperationOption option, PrintStream printStream, MigrationHook hook) {
54      try (Connection con = connectionProvider.getConnection()) {
55        if (option == null) {
56          option = new DatabaseOperationOption();
57        }
58        List<Change> changesInDb = Collections.emptyList();
59        if (changelogExists(con, option)) {
60          changesInDb = getChangelog(con, option);
61        }
62        if (changesInDb.isEmpty()) {
63          println(printStream, "Changelog exist, but no migration found.");
64        } else {
65          List<Change> migrations = migrationsLoader.getMigrations();
66          Collections.sort(migrations);
67          String skippedOrMissing = checkSkippedOrMissing(changesInDb, migrations);
68          Collections.reverse(migrations);
69          int stepCount = 0;
70          ScriptRunner runner = getScriptRunner(con, option, printStream);
71  
72          Map<String, Object> hookBindings = new HashMap<>();
73  
74          for (Change change : migrations) {
75            if (change.equals(changesInDb.get(changesInDb.size() - 1))) {
76              if (stepCount == 0 && hook != null) {
77                hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
78                hook.before(hookBindings);
79              }
80              if (hook != null) {
81                hookBindings.put(MigrationHook.HOOK_CONTEXT,
82                    new HookContext(connectionProvider, runner, new Change(change)));
83                hook.beforeEach(hookBindings);
84              }
85              println(printStream, Util.horizontalLine("Undoing: " + change.getFilename(), 80));
86              try (Reader reader = migrationsLoader.getScriptReader(change, true)) {
87                runner.runScript(reader);
88              }
89              if (changelogExists(con, option)) {
90                deleteChange(con, change, option);
91              } else {
92                println(printStream,
93                    "Changelog doesn't exist. No further migrations will be undone (normal for the last migration).");
94                stepCount = steps;
95              }
96              println(printStream);
97              if (hook != null) {
98                hookBindings.put(MigrationHook.HOOK_CONTEXT,
99                    new HookContext(connectionProvider, runner, new Change(change)));
100               hook.afterEach(hookBindings);
101             }
102             stepCount++;
103             if (steps == null || stepCount >= steps) {
104               break;
105             }
106             changesInDb.remove(changesInDb.size() - 1);
107           }
108         }
109         if (stepCount > 0 && hook != null) {
110           hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
111           hook.after(hookBindings);
112         }
113         println(printStream, skippedOrMissing);
114       }
115       return this;
116     } catch (Throwable e) {
117       while (e instanceof MigrationException && e.getCause() != null) {
118         e = e.getCause();
119       }
120       throw new MigrationException("Error undoing last migration.  Cause: " + e, e);
121     }
122   }
123 
124   protected void deleteChange(Connection con, Change change, DatabaseOperationOption option) throws SQLException {
125     ChangelogOperation operation = new ChangelogOperation(con, option);
126     operation.deleteById(change.getId());
127   }
128 }