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.Collections;
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 UpOperation extends DatabaseOperation {
36    private final Integer steps;
37  
38    public UpOperation() {
39      this.steps = null;
40    }
41  
42    public UpOperation(Integer steps) {
43      this.steps = steps;
44      if (steps != null && steps.intValue() < 1) {
45        throw new IllegalArgumentException("step must be positive number or null.");
46      }
47    }
48  
49    public UpOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
50        DatabaseOperationOption option, PrintStream printStream) {
51      return operate(connectionProvider, migrationsLoader, option, printStream, null);
52    }
53  
54    public UpOperation operate(ConnectionProvider connectionProvider, MigrationLoader migrationsLoader,
55        DatabaseOperationOption option, PrintStream printStream, MigrationHook hook) {
56      try (Connection con = connectionProvider.getConnection()) {
57        if (option == null) {
58          option = new DatabaseOperationOption();
59        }
60  
61        List<Change> changesInDb = Collections.emptyList();
62        if (changelogExists(con, option)) {
63          changesInDb = getChangelog(con, option);
64        }
65  
66        List<Change> migrations = migrationsLoader.getMigrations();
67        Collections.sort(migrations);
68        String skippedOrMissing = checkSkippedOrMissing(changesInDb, migrations);
69        int stepCount = 0;
70  
71        Map<String, Object> hookBindings = new HashMap<>();
72        ScriptRunner runner = getScriptRunner(con, option, printStream);
73        try {
74          for (Change change : migrations) {
75            if (changesInDb.isEmpty() || change.compareTo(changesInDb.get(changesInDb.size() - 1)) > 0) {
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("Applying: " + change.getFilename(), 80));
86              try (Reader scriptReader = migrationsLoader.getScriptReader(change, false)) {
87                runner.runScript(scriptReader);
88              }
89              insertChangelog(change, con, option);
90              println(printStream);
91              if (hook != null) {
92                hookBindings.put(MigrationHook.HOOK_CONTEXT,
93                    new HookContext(connectionProvider, runner, new Change(change)));
94                hook.afterEach(hookBindings);
95              }
96              stepCount++;
97              if (steps != null && stepCount >= steps) {
98                break;
99              }
100           }
101         }
102         if (stepCount > 0 && hook != null) {
103           hookBindings.put(MigrationHook.HOOK_CONTEXT, new HookContext(connectionProvider, runner, null));
104           hook.after(hookBindings);
105         }
106         println(printStream, skippedOrMissing);
107         return this;
108       } catch (Exception e) {
109         try (Reader onAbortScriptReader = migrationsLoader.getOnAbortReader()) {
110           if (onAbortScriptReader != null) {
111             println(printStream);
112             println(printStream, Util.horizontalLine("Executing onabort.sql script.", 80));
113             runner.runScript(onAbortScriptReader);
114             println(printStream);
115           }
116         }
117         throw e;
118       }
119     } catch (Throwable e) {
120       while (e instanceof MigrationException && e.getCause() != null) {
121         e = e.getCause();
122       }
123       throw new MigrationException("Error executing command.  Cause: " + e, e);
124     }
125   }
126 }