1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.migration.commands;
17
18 import java.io.IOException;
19 import java.io.Reader;
20 import java.math.BigDecimal;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.StringTokenizer;
27
28 import org.apache.ibatis.migration.Change;
29 import org.apache.ibatis.migration.MigrationException;
30 import org.apache.ibatis.migration.hook.MigrationHook;
31 import org.apache.ibatis.migration.hook.ScriptHookContext;
32 import org.apache.ibatis.migration.operations.DatabaseOperation;
33 import org.apache.ibatis.migration.operations.StatusOperation;
34 import org.apache.ibatis.migration.options.SelectedOptions;
35
36 public final class ScriptCommand extends BaseCommand {
37
38 public ScriptCommand(SelectedOptions options) {
39 super(options);
40 }
41
42 @Override
43 public void execute(String... sparams) {
44 try {
45 if (sparams == null || sparams.length < 1 || sparams[0] == null) {
46 throw new MigrationException("The script command requires a range of versions from v1 - v2.");
47 }
48 StringTokenizer parser = new StringTokenizer(sparams[0]);
49 int tokenCount = parser.countTokens();
50 boolean scriptPending = false;
51 boolean scriptPendingUndo = false;
52
53 String firstToken = parser.nextToken();
54
55 if (tokenCount == 1 && firstToken.equals("pending")) {
56 scriptPending = true;
57 } else if (tokenCount == 1 && firstToken.equals("pending_undo")) {
58 scriptPendingUndo = true;
59 } else if (!scriptPending && !scriptPendingUndo && tokenCount != 2) {
60 throw new MigrationException("The script command requires a range of versions from v1 - v2.");
61 }
62
63 BigDecimal v1 = scriptPending || scriptPendingUndo ? null : new BigDecimal(firstToken);
64 BigDecimal v2 = scriptPending || scriptPendingUndo ? null : new BigDecimal(parser.nextToken());
65
66 boolean undo;
67 undo = scriptPendingUndo;
68 if (!scriptPending && !scriptPendingUndo) {
69 int comparison = v1.compareTo(v2);
70 if (comparison == 0) {
71 throw new MigrationException(
72 "The script command requires two different versions. Use 0 to include the first version.");
73 }
74 undo = comparison > 0;
75 }
76
77 Map<String, Object> hookBindings = new HashMap<>();
78 MigrationHook hook = createScriptHook();
79 List<Change> migrations = scriptPending || scriptPendingUndo ? new StatusOperation()
80 .operate(getConnectionProvider(), getMigrationLoader(), getDatabaseOperationOption(), null).getCurrentStatus()
81 : getMigrationLoader().getMigrations();
82 Collections.sort(migrations);
83 if (undo) {
84 Collections.reverse(migrations);
85 }
86 int count = 0;
87 for (int i = 0; i < migrations.size(); i++) {
88 Change change = migrations.get(i);
89 if (shouldRun(change, v1, v2, scriptPending || scriptPendingUndo)) {
90 if (count == 0 && hook != null) {
91 hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(null, undo));
92 hook.before(hookBindings);
93 printStream.println();
94 }
95 if (hook != null) {
96 hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(new Change(change), undo));
97 hook.beforeEach(hookBindings);
98 printStream.println();
99 }
100 printStream.println("-- " + change.getFilename());
101 try (Reader migrationReader = getMigrationLoader().getScriptReader(change, undo)) {
102 char[] cbuf = new char[1024];
103 int l;
104 while ((l = migrationReader.read(cbuf)) > -1) {
105 printStream.print(l == cbuf.length ? cbuf : Arrays.copyOf(cbuf, l));
106 }
107 }
108 count++;
109 printStream.println();
110 printStream.println();
111 if (!undo) {
112 printStream.println(generateVersionInsert(change));
113 } else if (i + 1 < migrations.size() || !DESC_CREATE_CHANGELOG.equals(change.getDescription())) {
114 printStream.println(generateVersionDelete(change));
115 }
116 printStream.println();
117 if (hook != null) {
118 hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(new Change(change), undo));
119 hook.afterEach(hookBindings);
120 printStream.println();
121 }
122 }
123 }
124 if (count > 0 && hook != null) {
125 hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(null, undo));
126 hook.after(hookBindings);
127 printStream.println();
128 }
129 } catch (IOException e) {
130 throw new MigrationException("Error generating script. Cause: " + e, e);
131 }
132 }
133
134 private String generateVersionInsert(Change change) {
135 return "INSERT INTO " + changelogTable() + " (ID, APPLIED_AT, DESCRIPTION) " + "VALUES (" + change.getId() + ", '"
136 + DatabaseOperation.generateAppliedTimeStampAsString() + "', '" + change.getDescription().replace('\'', ' ')
137 + "')" + getDelimiter();
138 }
139
140 private String generateVersionDelete(Change change) {
141 return "DELETE FROM " + changelogTable() + " WHERE ID = " + change.getId() + getDelimiter();
142 }
143
144 private boolean shouldRun(Change change, BigDecimal v1, BigDecimal v2, boolean pendingOnly) {
145 if (pendingOnly) {
146 return change.getAppliedTimestamp() == null;
147 }
148 BigDecimal id = change.getId();
149 if (v1.compareTo(v2) > 0) {
150 return id.compareTo(v2) > 0 && id.compareTo(v1) <= 0;
151 }
152 return id.compareTo(v1) > 0 && id.compareTo(v2) <= 0;
153 }
154
155
156 private String getDelimiter() {
157 StringBuilder delimiter = new StringBuilder();
158 if (environment().isFullLineDelimiter()) {
159 delimiter.append('\n');
160 }
161 delimiter.append(environment().getDelimiter());
162 return delimiter.toString();
163 }
164
165 private MigrationHook createScriptHook() {
166 String before = environment().getHookBeforeScript();
167 String beforeEach = environment().getHookBeforeEachScript();
168 String afterEach = environment().getHookAfterEachScript();
169 String after = environment().getHookAfterScript();
170 if (before == null && beforeEach == null && afterEach == null && after == null) {
171 return null;
172 }
173 return createFileMigrationHook(before, beforeEach, afterEach, after);
174 }
175 }