/** SQLite-specific table. */
public class SQLiteTable extends Table {
  private static final Log LOG = LogFactory.getLog(SQLiteTable.class);

  /**
   * Creates a new SQLite table.
   *
   * @param jdbcTemplate The Jdbc Template for communicating with the DB.
   * @param dbSupport The database-specific support.
   * @param schema The schema this table lives in.
   * @param name The name of the table.
   */
  public SQLiteTable(JdbcTemplate jdbcTemplate, DbSupport dbSupport, Schema schema, String name) {
    super(jdbcTemplate, dbSupport, schema, name);
  }

  @Override
  protected void doDrop() throws SQLException {
    jdbcTemplate.execute("DROP TABLE " + dbSupport.quote(schema.getName(), name));
  }

  @Override
  protected boolean doExists() throws SQLException {
    return jdbcTemplate.queryForInt(
            "SELECT count(tbl_name) FROM "
                + dbSupport.quote(schema.getName())
                + ".sqlite_master WHERE type='table' AND tbl_name='"
                + name
                + "'")
        > 0;
  }

  @Override
  protected void doLock() throws SQLException {
    LOG.debug(
        "Unable to lock "
            + this
            + " as SQLite does not support locking. No concurrent migration supported.");
  }
}
Exemple #2
0
/** Main workflow for cleaning the database. */
public class DbClean {
  private static final Log LOG = LogFactory.getLog(DbClean.class);

  /** The connection to use. */
  private final Connection connection;

  /** The metadata table. */
  private final MetaDataTable metaDataTable;

  /** The schemas to clean. */
  private final Schema[] schemas;

  /**
   * The list of callbacks that fire before or after the clean task is executed. You can add as many
   * callbacks as you want. These should be set on the Flyway class by the end user as Flyway will
   * set them automatically for you here.
   */
  private final FlywayCallback[] callbacks;

  /**
   * Whether to disable clean.
   *
   * <p>This is especially useful for production environments where running clean can be quite a
   * career limiting move.
   */
  private boolean cleanDisabled;

  /** The DB support for the connection. */
  private final DbSupport dbSupport;

  /**
   * Creates a new database cleaner.
   *
   * @param connection The connection to use.
   * @param dbSupport The DB support for the connection.
   * @param metaDataTable The metadata table.
   * @param schemas The schemas to clean.
   * @param callbacks The list of callbacks that fire before or after the clean task is executed.
   * @param cleanDisabled Whether to disable clean.
   */
  public DbClean(
      Connection connection,
      DbSupport dbSupport,
      MetaDataTable metaDataTable,
      Schema[] schemas,
      FlywayCallback[] callbacks,
      boolean cleanDisabled) {
    this.connection = connection;
    this.dbSupport = dbSupport;
    this.metaDataTable = metaDataTable;
    this.schemas = schemas;
    this.callbacks = callbacks;
    this.cleanDisabled = cleanDisabled;
  }

  /**
   * Cleans the schemas of all objects.
   *
   * @throws FlywayException when clean failed.
   */
  public void clean() throws FlywayException {
    if (cleanDisabled) {
      throw new FlywayException(
          "Unable to execute clean as it has been disabled with the \"flyway.cleanDisabled\" property.");
    }
    try {
      for (final FlywayCallback callback : callbacks) {
        new TransactionTemplate(connection)
            .execute(
                new TransactionCallback<Object>() {
                  @Override
                  public Object doInTransaction() throws SQLException {
                    dbSupport.changeCurrentSchemaTo(schemas[0]);
                    callback.beforeClean(connection);
                    return null;
                  }
                });
      }

      dbSupport.changeCurrentSchemaTo(schemas[0]);
      boolean dropSchemas = false;
      try {
        dropSchemas = metaDataTable.hasSchemasMarker();
      } catch (Exception e) {
        LOG.error("Error while checking whether the schemas should be dropped", e);
      }

      for (Schema schema : schemas) {
        if (!schema.exists()) {
          LOG.warn("Unable to clean unknown schema: " + schema);
          continue;
        }

        if (dropSchemas) {
          dropSchema(schema);
        } else {
          cleanSchema(schema);
        }
      }

      for (final FlywayCallback callback : callbacks) {
        new TransactionTemplate(connection)
            .execute(
                new TransactionCallback<Object>() {
                  @Override
                  public Object doInTransaction() throws SQLException {
                    dbSupport.changeCurrentSchemaTo(schemas[0]);
                    callback.afterClean(connection);
                    return null;
                  }
                });
      }
    } finally {
      dbSupport.restoreCurrentSchema();
    }
  }

  /**
   * Drops this schema.
   *
   * @param schema The schema to drop.
   * @throws FlywayException when the drop failed.
   */
  private void dropSchema(final Schema schema) {
    LOG.debug("Dropping schema " + schema + " ...");
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    new TransactionTemplate(connection)
        .execute(
            new TransactionCallback<Void>() {
              public Void doInTransaction() {
                schema.drop();
                return null;
              }
            });
    stopWatch.stop();
    LOG.info(
        String.format(
            "Dropped schema %s (execution time %s)",
            schema, TimeFormat.format(stopWatch.getTotalTimeMillis())));
  }

  /**
   * Cleans this schema of all objects.
   *
   * @param schema The schema to clean.
   * @throws FlywayException when clean failed.
   */
  private void cleanSchema(final Schema schema) {
    LOG.debug("Cleaning schema " + schema + " ...");
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    new TransactionTemplate(connection)
        .execute(
            new TransactionCallback<Void>() {
              public Void doInTransaction() {
                schema.clean();
                return null;
              }
            });
    stopWatch.stop();
    LOG.info(
        String.format(
            "Cleaned schema %s (execution time %s)",
            schema, TimeFormat.format(stopWatch.getTotalTimeMillis())));
  }
}
 /**
  * Initializes the logging.
  *
  * @param level The minimum level to log at.
  */
 private static void initLogging(final Level level) {
   LogFactory.setLogCreator(new ConsoleLogCreator(level));
   LOG = LogFactory.getLog(Migrator.class);
 }
/** Test to demonstrate the migration functionality using Phoenix. */
@Category(DbCategory.Phoenix.class)
public class PhoenixMigrationMediumTest extends MigrationTestCase {
  private static final Log LOG = LogFactory.getLog(PhoenixMigrationMediumTest.class);

  protected static HBaseTestingUtility testUtility = null;
  protected static DriverDataSource dataSource = null;

  @Override
  protected String getBasedir() {
    return "migration/dbsupport/phoenix/sql/sql";
  }

  @Override
  protected String getMigrationDir() {
    return "migration/dbsupport/phoenix/sql";
  }

  @Override
  protected String getQuoteLocation() {
    return "migration/dbsupport/phoenix/sql/quote";
  }

  @Override
  protected String getFutureFailedLocation() {
    return "migration/dbsupport/phoenix/sql/future_failed";
  }

  @Override
  protected String getValidateLocation() {
    return "migration/dbsupport/phoenix/sql/validate";
  }

  @Override
  protected String getSemiColonLocation() {
    return "migration/dbsupport/phoenix/sql/semicolon";
  }

  @Override
  protected String getCommentLocation() {
    return "migration/dbsupport/phoenix/sql/comment";
  }

  @BeforeClass
  public static void beforeClassSetUp() throws Exception {
    // Startup HBase in-memory cluster
    LOG.info("Starting mini-cluster");
    testUtility = new HBaseTestingUtility();
    testUtility.startMiniCluster();

    // Set up Phoenix schema
    String server = testUtility.getConfiguration().get("hbase.zookeeper.quorum");
    String port = testUtility.getConfiguration().get("hbase.zookeeper.property.clientPort");
    String zkServer = server + ":" + port;

    dataSource =
        new DriverDataSource(
            Thread.currentThread().getContextClassLoader(),
            null,
            "jdbc:phoenix:" + zkServer,
            "",
            "");
  }

  @Override
  protected DataSource createDataSource(Properties customProperties) throws Exception {
    return dataSource;
  }

  @After
  @Override
  public void tearDown() throws Exception {
    // Don't close the connection after each test
  }

  @AfterClass
  public static void afterClassTearDown() throws Exception {
    LOG.info("Shutting down mini-cluster");
    dataSource.close();
    testUtility.shutdownMiniCluster();
  }

  @Override
  public void createTestTable() throws SQLException {
    jdbcTemplate.execute(
        "CREATE TABLE t1 (\n" + "  name VARCHAR(25) NOT NULL PRIMARY KEY\n" + "  )");
  }

  // The default schema doesn't exist until something has been
  // created in the schema
  @Test
  @Override
  public void schemaExists() throws SQLException {
    assertFalse(dbSupport.getOriginalSchema().exists());
    assertFalse(dbSupport.getSchema("InVaLidScHeMa").exists());

    jdbcTemplate.execute(
        "CREATE TABLE tc1 (\n" + "  name VARCHAR(25) NOT NULL PRIMARY KEY\n" + "  )");

    assertTrue(dbSupport.getOriginalSchema().exists());
  }

  // Custom query, Phoenix has a LIKE with newline issue:
  // https://issues.apache.org/jira/browse/PHOENIX-1351
  @Test
  @Override
  public void semicolonWithinStringLiteral() throws Exception {
    flyway.setLocations("migration/dbsupport/phoenix/sql/semicolon");
    flyway.migrate();

    assertEquals("1.1", flyway.info().current().getVersion().toString());
    assertEquals("Populate table", flyway.info().current().getDescription());
    assertEquals(
        "Mr. Semicolon+Linebreak;\nanother line",
        jdbcTemplate.queryForString(
            "SELECT name FROM test_user ORDER BY LENGTH(NAME) DESC LIMIT 1"));
  }

  // Needs its own file for syntax, and a custom sql migration prefix to prevent other unit tests
  // from failing
  @Test
  public void validateClean() throws Exception {
    flyway.setLocations(getValidateLocation());
    flyway.migrate();

    assertEquals("1", flyway.info().current().getVersion().toString());

    flyway.setValidateOnMigrate(true);
    flyway.setCleanOnValidationError(true);
    flyway.setSqlMigrationPrefix("PhoenixCheckValidate");
    assertEquals(1, flyway.migrate());
  }

  // Phoenix doesn't support setting an explicit schema
  @Ignore
  @Override
  public void setCurrentSchema() throws Exception {}
}
/** FileSystem scanner. */
public class FileSystemScanner {
  private static final Log LOG = LogFactory.getLog(FileSystemScanner.class);

  /**
   * Scans the FileSystem for resources under the specified location, starting with the specified
   * prefix and ending with the specified suffix.
   *
   * @param location The location in the filesystem to start searching. Subdirectories are also
   *     searched.
   * @param prefix The prefix of the resource names to match.
   * @param suffix The suffix of the resource names to match.
   * @return The resources that were found.
   * @throws java.io.IOException when the location could not be scanned.
   */
  public Resource[] scanForResources(Location location, String prefix, String suffix)
      throws IOException {
    String path = location.getPath();
    LOG.debug(
        "Scanning for filesystem resources at '"
            + path
            + "' (Prefix: '"
            + prefix
            + "', Suffix: '"
            + suffix
            + "')");

    File dir = new File(path);
    if (!dir.isDirectory() || !dir.canRead()) {
      LOG.warn("Unable to resolve location filesystem:" + path);
      return new Resource[0];
    }

    Set<Resource> resources = new TreeSet<Resource>();

    Set<String> resourceNames = findResourceNames(path, prefix, suffix);
    for (String resourceName : resourceNames) {
      resources.add(new FileSystemResource(resourceName));
      LOG.debug("Found filesystem resource: " + resourceName);
    }

    return resources.toArray(new Resource[resources.size()]);
  }

  /**
   * Finds the resources names present at this location and below on the classpath starting with
   * this prefix and ending with this suffix.
   *
   * @param path The path on the classpath to scan.
   * @param prefix The filename prefix to match.
   * @param suffix The filename suffix to match.
   * @return The resource names.
   * @throws java.io.IOException when scanning this location failed.
   */
  private Set<String> findResourceNames(String path, String prefix, String suffix)
      throws IOException {
    Set<String> resourceNames = findResourceNamesFromFileSystem(path, new File(path));
    return filterResourceNames(resourceNames, prefix, suffix);
  }

  /**
   * Finds all the resource names contained in this file system folder.
   *
   * @param scanRootLocation The root location of the scan on disk.
   * @param folder The folder to look for resources under on disk.
   * @return The resource names;
   * @throws IOException when the folder could not be read.
   */
  @SuppressWarnings("ConstantConditions")
  private Set<String> findResourceNamesFromFileSystem(String scanRootLocation, File folder)
      throws IOException {
    LOG.debug(
        "Scanning for resources in path: " + folder.getPath() + " (" + scanRootLocation + ")");

    Set<String> resourceNames = new TreeSet<String>();

    File[] files = folder.listFiles();
    for (File file : files) {
      if (file.canRead()) {
        if (file.isDirectory()) {
          resourceNames.addAll(findResourceNamesFromFileSystem(scanRootLocation, file));
        } else {
          resourceNames.add(file.getPath());
        }
      }
    }

    return resourceNames;
  }

  /**
   * Filters this list of resource names to only include the ones whose filename matches this prefix
   * and this suffix.
   *
   * @param resourceNames The names to filter.
   * @param prefix The prefix to match.
   * @param suffix The suffix to match.
   * @return The filtered names set.
   */
  private Set<String> filterResourceNames(Set<String> resourceNames, String prefix, String suffix) {
    Set<String> filteredResourceNames = new TreeSet<String>();
    for (String resourceName : resourceNames) {
      String fileName = resourceName.substring(resourceName.lastIndexOf(File.separator) + 1);
      if (fileName.startsWith(prefix)
          && fileName.endsWith(suffix)
          && (fileName.length() > (prefix + suffix).length())) {
        filteredResourceNames.add(resourceName);
      } else {
        LOG.debug("Filtering out resource: " + resourceName + " (filename: " + fileName + ")");
      }
    }
    return filteredResourceNames;
  }
}