ScriptCommand.java

  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.commands;

  17. import java.io.IOException;
  18. import java.io.Reader;
  19. import java.math.BigDecimal;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.StringTokenizer;

  26. import org.apache.ibatis.migration.Change;
  27. import org.apache.ibatis.migration.MigrationException;
  28. import org.apache.ibatis.migration.hook.MigrationHook;
  29. import org.apache.ibatis.migration.hook.ScriptHookContext;
  30. import org.apache.ibatis.migration.operations.DatabaseOperation;
  31. import org.apache.ibatis.migration.operations.StatusOperation;
  32. import org.apache.ibatis.migration.options.SelectedOptions;

  33. public final class ScriptCommand extends BaseCommand {

  34.   public ScriptCommand(SelectedOptions options) {
  35.     super(options);
  36.   }

  37.   @Override
  38.   public void execute(String... sparams) {
  39.     try {
  40.       if (sparams == null || sparams.length < 1 || sparams[0] == null) {
  41.         throw new MigrationException("The script command requires a range of versions from v1 - v2.");
  42.       }
  43.       StringTokenizer parser = new StringTokenizer(sparams[0]);
  44.       int tokenCount = parser.countTokens();
  45.       boolean scriptPending = false;
  46.       boolean scriptPendingUndo = false;

  47.       String firstToken = parser.nextToken();

  48.       if (tokenCount == 1 && firstToken.equals("pending")) {
  49.         scriptPending = true;
  50.       } else if (tokenCount == 1 && firstToken.equals("pending_undo")) {
  51.         scriptPendingUndo = true;
  52.       } else if (!scriptPending && !scriptPendingUndo && tokenCount != 2) {
  53.         throw new MigrationException("The script command requires a range of versions from v1 - v2.");
  54.       }

  55.       BigDecimal v1 = scriptPending || scriptPendingUndo ? null : new BigDecimal(firstToken);
  56.       BigDecimal v2 = scriptPending || scriptPendingUndo ? null : new BigDecimal(parser.nextToken());

  57.       boolean undo;
  58.       undo = scriptPendingUndo;
  59.       if (!scriptPending && !scriptPendingUndo) {
  60.         int comparison = v1.compareTo(v2);
  61.         if (comparison == 0) {
  62.           throw new MigrationException(
  63.               "The script command requires two different versions. Use 0 to include the first version.");
  64.         }
  65.         undo = comparison > 0;
  66.       }

  67.       Map<String, Object> hookBindings = new HashMap<>();
  68.       MigrationHook hook = createScriptHook();
  69.       List<Change> migrations = scriptPending || scriptPendingUndo ? new StatusOperation()
  70.           .operate(getConnectionProvider(), getMigrationLoader(), getDatabaseOperationOption(), null).getCurrentStatus()
  71.           : getMigrationLoader().getMigrations();
  72.       Collections.sort(migrations);
  73.       if (undo) {
  74.         Collections.reverse(migrations);
  75.       }
  76.       int count = 0;
  77.       for (int i = 0; i < migrations.size(); i++) {
  78.         Change change = migrations.get(i);
  79.         if (shouldRun(change, v1, v2, scriptPending || scriptPendingUndo)) {
  80.           if (count == 0 && hook != null) {
  81.             hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(null, undo));
  82.             hook.before(hookBindings);
  83.             printStream.println();
  84.           }
  85.           if (hook != null) {
  86.             hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(new Change(change), undo));
  87.             hook.beforeEach(hookBindings);
  88.             printStream.println();
  89.           }
  90.           printStream.println("-- " + change.getFilename());
  91.           try (Reader migrationReader = getMigrationLoader().getScriptReader(change, undo)) {
  92.             char[] cbuf = new char[1024];
  93.             int l;
  94.             while ((l = migrationReader.read(cbuf)) > -1) {
  95.               printStream.print(l == cbuf.length ? cbuf : Arrays.copyOf(cbuf, l));
  96.             }
  97.           }
  98.           count++;
  99.           printStream.println();
  100.           printStream.println();
  101.           if (!undo) {
  102.             printStream.println(generateVersionInsert(change));
  103.           } else if (i + 1 < migrations.size() || !DESC_CREATE_CHANGELOG.equals(change.getDescription())) {
  104.             printStream.println(generateVersionDelete(change));
  105.           }
  106.           printStream.println();
  107.           if (hook != null) {
  108.             hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(new Change(change), undo));
  109.             hook.afterEach(hookBindings);
  110.             printStream.println();
  111.           }
  112.         }
  113.       }
  114.       if (count > 0 && hook != null) {
  115.         hookBindings.put(MigrationHook.HOOK_CONTEXT, new ScriptHookContext(null, undo));
  116.         hook.after(hookBindings);
  117.         printStream.println();
  118.       }
  119.     } catch (IOException e) {
  120.       throw new MigrationException("Error generating script. Cause: " + e, e);
  121.     }
  122.   }

  123.   private String generateVersionInsert(Change change) {
  124.     return "INSERT INTO " + changelogTable() + " (ID, APPLIED_AT, DESCRIPTION) " + "VALUES (" + change.getId() + ", '"
  125.         + DatabaseOperation.generateAppliedTimeStampAsString() + "', '" + change.getDescription().replace('\'', ' ')
  126.         + "')" + getDelimiter();
  127.   }

  128.   private String generateVersionDelete(Change change) {
  129.     return "DELETE FROM " + changelogTable() + " WHERE ID = " + change.getId() + getDelimiter();
  130.   }

  131.   private boolean shouldRun(Change change, BigDecimal v1, BigDecimal v2, boolean pendingOnly) {
  132.     if (pendingOnly) {
  133.       return change.getAppliedTimestamp() == null;
  134.     }
  135.     BigDecimal id = change.getId();
  136.     if (v1.compareTo(v2) > 0) {
  137.       return id.compareTo(v2) > 0 && id.compareTo(v1) <= 0;
  138.     }
  139.     return id.compareTo(v1) > 0 && id.compareTo(v2) <= 0;
  140.   }

  141.   // Issue 699
  142.   private String getDelimiter() {
  143.     StringBuilder delimiter = new StringBuilder();
  144.     if (environment().isFullLineDelimiter()) {
  145.       delimiter.append('\n');
  146.     }
  147.     delimiter.append(environment().getDelimiter());
  148.     return delimiter.toString();
  149.   }

  150.   private MigrationHook createScriptHook() {
  151.     String before = environment().getHookBeforeScript();
  152.     String beforeEach = environment().getHookBeforeEachScript();
  153.     String afterEach = environment().getHookAfterEachScript();
  154.     String after = environment().getHookAfterScript();
  155.     if (before == null && beforeEach == null && afterEach == null && after == null) {
  156.       return null;
  157.     }
  158.     return createFileMigrationHook(before, beforeEach, afterEach, after);
  159.   }
  160. }