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