1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 }