Skip to content
This repository has been archived by the owner on Feb 3, 2023. It is now read-only.

Commit

Permalink
Add support for Liquigraph XML->Liquibase XML migration
Browse files Browse the repository at this point in the history
Fixes #447
  • Loading branch information
fbiville committed Sep 23, 2021
1 parent 2011ce3 commit 9d3abbc
Show file tree
Hide file tree
Showing 44 changed files with 1,240 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
*/
package org.liquigraph.cli;

import java.util.Arrays;

import org.liquigraph.cli.commands.DryRun;
import org.liquigraph.cli.commands.MigrateDeclaredChangeSets;
import org.liquigraph.cli.commands.Run;
import org.liquigraph.core.api.Liquigraph;
import org.liquigraph.core.api.LiquigraphApi;

import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -39,6 +39,7 @@ public final class LiquigraphCli {
public LiquigraphCli(LiquigraphApi liquigraph) {
this.liquigraph = liquigraph;
this.registry = new LiquigraphCommandRegistry()
.registerCommand("migrate-declared-change-sets", new MigrateDeclaredChangeSets())
.registerCommand("dry-run", new DryRun())
.registerCommand("run", new Run());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import org.liquigraph.core.api.LiquigraphApi;
import org.liquigraph.core.configuration.Configuration;
import org.liquigraph.core.configuration.ConfigurationBuilder;
import org.liquigraph.core.io.xml.ChangelogLoader;
import org.liquigraph.core.io.xml.ClassLoaderChangelogLoader;
import org.liquigraph.core.io.ChangelogLoader;
import org.liquigraph.core.io.ClassLoaderChangelogLoader;

@Parameters(commandDescription = "Simulate the execution of Liquigraph migrations")
public final class DryRun implements LiquigraphCommand {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2014-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.liquigraph.cli.commands;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.liquigraph.cli.commands.delegates.MigrationConfiguration;
import org.liquigraph.cli.io.ClassLoaders;
import org.liquigraph.cli.io.Files;
import org.liquigraph.core.api.LiquigraphApi;
import org.liquigraph.core.io.ChangelogLoader;
import org.liquigraph.core.io.ClassLoaderChangelogLoader;

import java.util.Objects;

@Parameters(commandDescription = "Migrates the given declared change sets to Liquibase XML format")
public final class MigrateDeclaredChangeSets implements LiquigraphCommand {

@ParametersDelegate
private final MigrationConfiguration migrationConfiguration = new MigrationConfiguration();

@Parameter(
names = {"--target-directory", "-d"},
description = "Output directory path into which the Liquibase change sets will be written. " +
"The output file will be named <basename>.liquibase.xml",
required = true
)
private String targetDirectory;

@Override
public void accept(LiquigraphApi liquigraphApi) {
liquigraphApi.migrateDeclaredChangeSets(
migrationConfiguration.getChangelog(),
migrationConfiguration.getExecutionContexts(),
targetDirectory,
getChangelogLoader()
);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MigrateDeclaredChangeSets migrateLiquibase = (MigrateDeclaredChangeSets) o;
return Objects.equals(migrationConfiguration, migrateLiquibase.migrationConfiguration) &&
Objects.equals(targetDirectory, migrateLiquibase.targetDirectory);
}

@Override
public int hashCode() {
return Objects.hash(migrationConfiguration, targetDirectory);
}

private ChangelogLoader getChangelogLoader() {
return new ClassLoaderChangelogLoader(
ClassLoaders.urlClassLoader(
migrationConfiguration.getResourceUrl(),
Files.toUrl(targetDirectory)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
import com.beust.jcommander.Parameter;
import org.liquigraph.cli.io.ClassLoaders;
import org.liquigraph.cli.io.Files;
import org.liquigraph.core.io.xml.ChangelogLoader;
import org.liquigraph.core.io.xml.ClassLoaderChangelogLoader;
import org.liquigraph.core.io.ChangelogLoader;
import org.liquigraph.core.io.ClassLoaderChangelogLoader;

import static java.util.Collections.emptyList;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2014-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.liquigraph.cli;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;

import static org.assertj.core.api.Assertions.assertThat;

// Integration tests that do not require any database connectivity
public class LiquigraphCliOfflineIT {

@Rule
public TemporaryFolder folder = new TemporaryFolder();

@Test
public void migrates_to_Liquibase_change_sets() {
LiquigraphCli.main(new String[]{
"migrate-declared-change-sets",
"--changelog", "changelog.xml",
"--target-directory", folder.getRoot().getPath()
});

assertThat(new File(folder.getRoot(), "changelog.liquibase.xml"))
.hasContent(
"<?xml version=\"1.1\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<databaseChangeLog xmlns=\"http://www.liquibase.org/xml/ns/dbchangelog\" xmlns:ext=\"http://www.liquibase.org/xml/ns/dbchangelog-ext\" xmlns:pro=\"http://www.liquibase.org/xml/ns/pro\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd\">\n" +
" <changeSet author=\"you\" id=\"hello-world\" objectQuotingStrategy=\"LEGACY\">\n" +
" <sql splitStatements=\"true\" stripComments=\"false\">CREATE (n:Sentence {text:'Hello monde!'}) RETURN n</sql>\n" +
" </changeSet>\n" +
" <changeSet author=\"you\" id=\"hello-world-fixed\" objectQuotingStrategy=\"LEGACY\">\n" +
" <sql splitStatements=\"true\" stripComments=\"false\">MATCH (n:Sentence {text:'Hello monde!'}) SET n.text='Hello world!' RETURN n</sql>\n" +
" </changeSet>\n" +
"</databaseChangeLog>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;

import org.junit.After;
Expand All @@ -34,6 +35,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.liquigraph.core.configuration.RunMode.RUN_MODE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

Expand Down Expand Up @@ -109,79 +112,99 @@ public void fails_with_both_version_and_help_flag() {
@Test
public void executes_minimal_migration() {
String uri = "jdbc:neo4j:bolt://example.com";
String masterChangelog = "changelog.xml";
String mainChangelog = "changelog.xml";

cli.execute(new String[] {
"run",
"--graph-db-uri", uri,
"--changelog", masterChangelog
"--changelog", mainChangelog
});

ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
verify(liquigraph).runMigrations(captor.capture());
Configuration configuration = captor.getValue();
assertThat(configuration.masterChangelog()).isEqualTo(masterChangelog);
assertThat(configuration.masterChangelog()).isEqualTo(mainChangelog);
assertThat(configuration.executionMode()).isEqualTo(RUN_MODE);
}

@Test
public void executes_minimal_migration_with_short_options() {
String uri = "jdbc:neo4j:bolt://example.com";
String masterChangelog = "changelog.xml";
String mainChangelog = "changelog.xml";

cli.execute(new String[] {
"run",
"-g", uri,
"-c", masterChangelog
"-c", mainChangelog
});

ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
verify(liquigraph).runMigrations(captor.capture());
Configuration configuration = captor.getValue();
assertThat(configuration.masterChangelog()).isEqualTo(masterChangelog);
assertThat(configuration.masterChangelog()).isEqualTo(mainChangelog);
assertThat(configuration.executionMode()).isEqualTo(RUN_MODE);
}

@Test
public void dry_runs_minimal_migration() {
String uri = "jdbc:neo4j:bolt://example.com";
String masterChangelog = "changelog.xml";
String mainChangelog = "changelog.xml";
File dryRunDirectory = temporaryFolder.getRoot();

cli.execute(new String[] {
"dry-run",
"--dry-run-output-directory", dryRunDirectory.getPath(),
"--graph-db-uri", uri,
"--changelog", masterChangelog
"--changelog", mainChangelog
});

ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
verify(liquigraph).runMigrations(captor.capture());
Configuration configuration = captor.getValue();
assertThat(configuration.masterChangelog()).isEqualTo(masterChangelog);
assertThat(configuration.masterChangelog()).isEqualTo(mainChangelog);
assertThat(configuration.executionMode()).isEqualTo(new DryRunMode(dryRunDirectory.toPath()));
}

@Test
public void dry_runs_minimal_migration_with_short_options() {
String uri = "jdbc:neo4j:bolt://example.com";
String masterChangelog = "changelog.xml";
String mainChangelog = "changelog.xml";
File dryRunDirectory = temporaryFolder.getRoot();

cli.execute(new String[] {
"dry-run",
"-d", dryRunDirectory.getPath(),
"-g", uri,
"-c", masterChangelog
"-c", mainChangelog
});

ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
verify(liquigraph).runMigrations(captor.capture());
Configuration configuration = captor.getValue();
assertThat(configuration.masterChangelog()).isEqualTo(masterChangelog);
assertThat(configuration.masterChangelog()).isEqualTo(mainChangelog);
assertThat(configuration.executionMode()).isEqualTo(new DryRunMode(dryRunDirectory.toPath()));
}

@Test
public void migrates_declared_change_sets_to_Liquibase_format() {
String mainChangelog = "changelog.xml";
File targetDirectory = temporaryFolder.getRoot();

cli.execute(new String[] {
"migrate-declared-change-sets",
"-d", targetDirectory.getPath(),
"-x", "foo,bar",
"-c", mainChangelog
});

verify(liquigraph).migrateDeclaredChangeSets(
eq(mainChangelog),
eq(Arrays.asList("foo", "bar")),
eq(targetDirectory.getPath()),
any()
);
}

private String commandOutput() throws Exception {
return outputStream.toString(StandardCharsets.UTF_8.name());
}
Expand Down
4 changes: 4 additions & 0 deletions liquigraph-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
<artifactId>neo4j-jdbc-driver</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>

<dependency>
<groupId>${project.groupId}</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,36 @@
import org.liquigraph.core.io.ChangelogGraphReader;
import org.liquigraph.core.io.ConditionExecutor;
import org.liquigraph.core.io.ConditionPrinter;
import org.liquigraph.core.io.GraphJdbcConnector;
import org.liquigraph.core.io.xml.ChangelogParser;
import org.liquigraph.core.io.LiquibaseMigrator;
import org.liquigraph.core.io.ChangelogLoader;
import org.liquigraph.core.io.ChangelogParser;
import org.liquigraph.core.io.xml.ChangelogPreprocessor;
import org.liquigraph.core.io.xml.ChangelogXmlParser;
import org.liquigraph.core.io.xml.ImportResolver;
import org.liquigraph.core.io.xml.XmlSchemaValidator;
import org.liquigraph.core.validation.PersistedChangesetValidator;

import java.util.Collection;

/**
* Liquigraph facade in charge of migration execution.
*/
public final class Liquigraph implements LiquigraphApi {

private final MigrationRunner migrationRunner;

private final LiquibaseMigrator liquibaseMigrator;

public Liquigraph() {
ChangelogParser parser = changelogParser(xmlSchemaValidator(), changelogPreprocessor(importResolver()));
migrationRunner = migrationRunner(
changelogParser(xmlSchemaValidator(), changelogPreprocessor(importResolver())),
parser,
changelogGraphReader(),
changelogDiffMaker(),
conditionExecutor(),
conditionPrinter()
);
liquibaseMigrator = new LiquibaseMigrator(parser);
}

private static MigrationRunner migrationRunner(ChangelogParser changelogParser, ChangelogGraphReader changelogGraphReader, ChangelogDiffMaker changelogDiffMaker, ConditionExecutor conditionExecutor, ConditionPrinter conditionPrinter) {
Expand Down Expand Up @@ -75,7 +83,7 @@ private static ChangelogGraphReader changelogGraphReader() {
}

private static ChangelogParser changelogParser(XmlSchemaValidator validator, ChangelogPreprocessor preprocessor) {
return new ChangelogParser(validator, preprocessor);
return new ChangelogXmlParser(validator, preprocessor);
}

private static ChangelogPreprocessor changelogPreprocessor(ImportResolver resolver) {
Expand All @@ -94,4 +102,9 @@ private static XmlSchemaValidator xmlSchemaValidator() {
public void runMigrations(Configuration configuration) {
migrationRunner.runMigrations(configuration);
}

@Override
public void migrateDeclaredChangeSets(String changelog, Collection<String> executionContexts, String targetDirectory, ChangelogLoader changelogLoader) {
liquibaseMigrator.migrate(changelog, executionContexts, targetDirectory, changelogLoader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package org.liquigraph.core.api;

import org.liquigraph.core.configuration.Configuration;
import org.liquigraph.core.io.ChangelogLoader;

import java.util.Collection;

public interface LiquigraphApi {

Expand All @@ -27,4 +30,14 @@ public interface LiquigraphApi {
* @see org.liquigraph.core.configuration.ConfigurationBuilder to create {@link org.liquigraph.core.configuration.Configuration instances}
*/
void runMigrations(Configuration configuration);

/**
* Migrates the provided declared change sets to the Liquibase XML format, optionally filtered by the provided execution contexts
*
* @param changelog configuration of the changelog location
* @param executionContexts optional execution contexts, used to filter change sets
* @param targetDirectory target directory into which the Liquibase change sets will be serialized
* @param changelogLoader changelog loader
*/
void migrateDeclaredChangeSets(String changelog, Collection<String> executionContexts, String targetDirectory, ChangelogLoader changelogLoader);
}
Loading

0 comments on commit 9d3abbc

Please sign in to comment.