diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index 2d92884316..0bfa7fded4 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -46,6 +46,12 @@ It applies all locally resolved migrations to the target database and stores the It returns the last applied version. +That operation does not allow migrations out-of-order by default. +That means if you add version 2 after you already migrated to 5, it will fail with an appropriate error. +You can spot these cases beforehand by validating your chain of migrations (see below). +If you absolutely must however, you can use `withOutOfOrderAllowed` on the config object or the corresponding property in either the Spring Boot starter or the Quarkus extension. +Setting this to true will integrate out-of-order migrations into the chain. + [[usage_common_repair]] === Repair @@ -677,6 +683,13 @@ A configurable delay that will be applied in between applying two migrations. - Type: `java.time.Duration` - Default: `false` +`org.neo4j.migrations.out-of-order`:: +A flag to enable out-of-order migrations. + +- Type: `java.lang.Boolean` +- Default: `false` + + NOTE: Migrations can be disabled by setting `org.neo4j.migrations.enabled` to `false`. diff --git a/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/ChainBuilder.java b/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/ChainBuilder.java index b8bffbc59d..f7256c3aa6 100644 --- a/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/ChainBuilder.java +++ b/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/ChainBuilder.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.TreeMap; import org.neo4j.driver.Record; import org.neo4j.driver.Result; @@ -87,41 +88,50 @@ private Map buildChain0(MigrationContext context, Lis } final String incompleteMigrationsMessage = "More migrations have been applied to the database than locally resolved."; - Map fullMigrationChain = new LinkedHashMap<>( - discoveredMigrations.size() + appliedMigrations.size()); + Map fullMigrationChain = new TreeMap<>(context.getConfig().getVersionComparator()); + boolean outOfOrderAllowed = context.getConfig().isOutOfOrder(); int i = 0; for (Map.Entry entry : appliedMigrations.entrySet()) { MigrationVersion expectedVersion = entry.getKey(); Optional expectedChecksum = entry.getValue().getChecksum(); - Migration newMigration; - try { - newMigration = discoveredMigrations.get(i); - } catch (IndexOutOfBoundsException e) { - if (detailedCauses) { - throw new MigrationsException(incompleteMigrationsMessage, e); + boolean checkNext = true; + while (checkNext) { + checkNext = false; + Migration newMigration; + try { + newMigration = discoveredMigrations.get(i++); + } catch (IndexOutOfBoundsException e) { + if (detailedCauses) { + throw new MigrationsException(incompleteMigrationsMessage, e); + } + throw new MigrationsException(incompleteMigrationsMessage); } - throw new MigrationsException(incompleteMigrationsMessage); - } - if (!newMigration.getVersion().equals(expectedVersion)) { - if (getNumberOfAppliedMigrations(context) > discoveredMigrations.size()) { - throw new MigrationsException(incompleteMigrationsMessage, new IndexOutOfBoundsException()); + if (!newMigration.getVersion().equals(expectedVersion)) { + if (getNumberOfAppliedMigrations(context) > discoveredMigrations.size()) { + throw new MigrationsException(incompleteMigrationsMessage, new IndexOutOfBoundsException()); + } + if (outOfOrderAllowed) { + fullMigrationChain.put(newMigration.getVersion(), DefaultMigrationChainElement.pendingElement(newMigration)); + checkNext = true; + continue; + } else { + throw new MigrationsException("Unexpected migration at index " + (i - 1) + ": " + Migrations.toString(newMigration) + "."); + } } - throw new MigrationsException("Unexpected migration at index " + i + ": " + Migrations.toString(newMigration) + "."); - } - if (newMigration.isRepeatable() != expectedVersion.isRepeatable()) { - throw new MigrationsException("State of " + Migrations.toString(newMigration) + " changed from " + (expectedVersion.isRepeatable() ? "repeatable to non-repeatable" : "non-repeatable to repeatable")); - } + if (newMigration.isRepeatable() != expectedVersion.isRepeatable()) { + throw new MigrationsException("State of " + Migrations.toString(newMigration) + " changed from " + (expectedVersion.isRepeatable() ? "repeatable to non-repeatable" : "non-repeatable to repeatable")); + } - if ((context.getConfig().isValidateOnMigrate() || alwaysVerify) && !(matches(expectedChecksum, newMigration) || expectedVersion.isRepeatable())) { - throw new MigrationsException("Checksum of " + Migrations.toString(newMigration) + " changed!"); - } + if ((context.getConfig().isValidateOnMigrate() || alwaysVerify) && !(matches(expectedChecksum, newMigration) || expectedVersion.isRepeatable())) { + throw new MigrationsException("Checksum of " + Migrations.toString(newMigration) + " changed!"); + } - // This is not a pending migration anymore - fullMigrationChain.put(expectedVersion, entry.getValue()); - ++i; + // This is not a pending migration anymore + fullMigrationChain.put(expectedVersion, entry.getValue()); + } } // All remaining migrations are pending diff --git a/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/Migrations.java b/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/Migrations.java index 735dbabcc2..f568f2b0a8 100644 --- a/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/Migrations.java +++ b/neo4j-migrations-core/src/main/java/ac/simons/neo4j/migrations/core/Migrations.java @@ -51,7 +51,6 @@ import org.neo4j.driver.Values; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.SummaryCounters; import org.neo4j.driver.types.Node; @@ -721,7 +720,9 @@ private void apply0(List migrations) { StopWatch stopWatch = new StopWatch(); for (Migration migration : new IterableMigrations(config, migrations)) { - + if (!chain.isApplied(migration.getVersion().getValue()) && config.getVersionComparator().compare(migration.getVersion(), previousVersion) < 0) { + previousVersion = MigrationVersion.baseline(); + } boolean repeated = false; Supplier logMessage = () -> String.format("Applied migration %s.", toString(migration)); if (previousVersion != MigrationVersion.baseline() && chain.isApplied(migration.getVersion().getValue())) { @@ -766,40 +767,65 @@ private MigrationVersion recordApplication(String neo4jUser, MigrationVersion pr parameters.put("executionTime", executionTime); parameters.put(PROPERTY_MIGRATION_TARGET, migrationTarget.orElse(null)); - TransactionCallback uow; + record ReplacedMigration(long oldRelId, long newMigrationNodeId) { + } + + TransactionCallback> uow; if (repeated) { - uow = t -> t.run( - "MATCH (l:__Neo4jMigration) WHERE l.version = $appliedMigration['version'] AND coalesce(l.migrationTarget,'') = coalesce($migrationTarget,'') WITH l " + uow = t -> { + t.run( + "MATCH (l:__Neo4jMigration) WHERE l.version = $appliedMigration['version'] AND coalesce(l.migrationTarget,'') = coalesce($migrationTarget,'') WITH l " + "CREATE (l) - [:REPEATED {checksum: $appliedMigration['checksum'], at: datetime({timezone: 'UTC'}), in: duration( {milliseconds: $executionTime} ), by: $installedBy, connectedAs: $neo4jUser}] -> (l)", - parameters).consume(); + parameters).consume(); + return Optional.empty(); + }; } else { uow = t -> { - String mergeOrMatchAndMaybeCreate; + String mergePreviousMigration; if (migrationTarget.isPresent()) { - mergeOrMatchAndMaybeCreate = "MERGE (p:__Neo4jMigration {version: $previousVersion, migrationTarget: $migrationTarget}) "; + mergePreviousMigration = "MERGE (p:__Neo4jMigration {version: $previousVersion, migrationTarget: $migrationTarget}) WITH p "; } else { Result result = t.run( "MATCH (p:__Neo4jMigration {version: $previousVersion}) WHERE p.migrationTarget IS NULL RETURN id(p) AS id", Values.parameters("previousVersion", previousVersion.getValue())); if (result.hasNext()) { parameters.put("id", result.single().get("id").asLong()); - mergeOrMatchAndMaybeCreate = "MATCH (p) WHERE id(p) = $id WITH p "; + mergePreviousMigration = "MATCH (p) WHERE id(p) = $id WITH p "; } else { - mergeOrMatchAndMaybeCreate = "CREATE (p:__Neo4jMigration {version: $previousVersion}) "; + mergePreviousMigration = "CREATE (p:__Neo4jMigration {version: $previousVersion}) WITH p "; } } - return t.run( - mergeOrMatchAndMaybeCreate - + "CREATE (c:__Neo4jMigration) SET c = $appliedMigration, c.migrationTarget = $migrationTarget " - + "MERGE (p) - [:MIGRATED_TO {at: datetime({timezone: 'UTC'}), in: duration( {milliseconds: $executionTime} ), by: $installedBy, connectedAs: $neo4jUser}] -> (c)", - parameters) - .consume(); + var createNewMigrationAndPath = """ + OPTIONAL MATCH (p) -[om:MIGRATED_TO]-> (ot) + CREATE (c:__Neo4jMigration) SET c = $appliedMigration, c.migrationTarget = $migrationTarget + MERGE (p) - [:MIGRATED_TO {at: datetime({timezone: 'UTC'}), in: duration( {milliseconds: $executionTime} ), by: $installedBy, connectedAs: $neo4jUser}] -> (c) + RETURN id(c) AS insertedId, id(om) AS oldRelId, properties(om), id(ot) AS oldEndId + """; + + var result = t.run(mergePreviousMigration + " " + createNewMigrationAndPath, parameters).single(); + if (!result.get("oldRelId").isNull()) { + return Optional.of(new ReplacedMigration(result.get("oldRelId").asLong(), result.get("insertedId").asLong())); + } else { + return Optional.empty(); + } }; } try (Session session = context.getSchemaSession()) { - session.executeWrite(uow); + var f = session.executeWrite(uow); + f.ifPresent(replacedMigration -> { + session.executeWriteWithoutResult(t -> { + t.run(""" + MATCH ()-[oldRel]->(oldEnd) + WHERE id(oldRel) = $oldRelId + MATCH (inserted) WHERE id(inserted) = $insertedId + MERGE (inserted) -[r:MIGRATED_TO]-> (oldEnd) + SET r = properties(oldRel) + DELETE (oldRel) + """, Map.of("oldRelId", replacedMigration.oldRelId(), "insertedId", replacedMigration.newMigrationNodeId())).consume(); + }); + }); } return appliedMigration.getVersion(); diff --git a/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/MigrationsIT.java b/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/MigrationsIT.java index 225d429d9a..dc0b7b5de6 100644 --- a/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/MigrationsIT.java +++ b/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/MigrationsIT.java @@ -33,7 +33,9 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.function.Predicate; import java.util.stream.IntStream; +import java.util.stream.StreamSupport; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Nested; @@ -47,6 +49,7 @@ import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.Session; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.types.Node; import org.testcontainers.containers.Neo4jContainer; import ac.simons.neo4j.migrations.core.MigrationChain.ChainBuilderMode; @@ -902,6 +905,212 @@ void shouldCreatedRelationshipIndex() { } } + @Nested + class Ordering { + + static final String FIND_NODES_QUERY = "MATCH (n:OOO) RETURN n ORDER BY n.created_on"; + + @Test + // GH-1213 + void shouldDefaultToOrderAndOrderShouldBeRight() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "005", "010", "015", "020"); + assertCreationOrder("N4", "N1", "N3", "N2"); + } + + @Test + // GH-1213 + void shouldFailOnOutOfOrderByDefault() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/second").build(), driver); + assertThatExceptionOfType(MigrationsException.class).isThrownBy(migrations::apply) + .withMessage("Unexpected migration at index 1: 015 (\"NewSecond\")."); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderInTheBeginningOne() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/third") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "005", "010", "020"); + assertCreationOrder("N1", "N2", "N4"); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderInTheBeginningN() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/third", "classpath:ooo/additional/third") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "001", "005", "009", "010", "020"); + assertCreationOrder("N1", "N2", "N7", "N4", "N8"); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderInTheMiddleOne() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/second") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "015", "020"); + assertCreationOrder("N1", "N2", "N3"); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderInTheMiddleN() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/second", "classpath:ooo/additional/second") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "014", "015", "019", "020"); + assertCreationOrder("N1", "N2", "N5", "N3", "N6"); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderCombined() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base/first", "classpath:ooo/base/second", "classpath:ooo/base/third") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "005", "010", "015", "020"); + assertCreationOrder("N1", "N2", "N4", "N3"); + } + + @Test + // GH-1213 + void shouldNotFailOnOutOfOrderCombinedAll() { + + var migrations = new Migrations(defaultConfigPart().withLocationsToScan("classpath:ooo/base/first").build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "010", "020"); + assertCreationOrder("N1", "N2"); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base", "classpath:ooo/additional", "classpath:ooo/repeatable/orig") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "001", "002", "005", "009", "010", "014", "015", "016", "019", "020"); + assertCreationOrder("N1", "N2", "N7", "N4", "N8", "N5", "N3", "N6"); + assertRepeats(2, 0); + + migrations = new Migrations(defaultConfigPart() + .withLocationsToScan("classpath:ooo/base", "classpath:ooo/additional", "classpath:ooo/repeatable/modified") + .withOutOfOrderAllowed(true) + .build(), driver); + migrations.apply(); + + assertChainOrder(migrations, "001", "002", "005", "009", "010", "014", "015", "016", "019", "020"); + assertCreationOrder("N1", "N2", "N7", "N4", "N8", "N5", "N3", "N6"); + assertRepeats(4, 2); + } + + private static MigrationsConfig.Builder defaultConfigPart() { + return MigrationsConfig.builder() + .withTransactionMode(MigrationsConfig.TransactionMode.PER_MIGRATION); + } + + private static void assertChainOrder(Migrations migrations, String... expectedVersions) { + var chain = migrations.info(); + assertThat(chain.getElements()) + .allSatisfy(c -> { + assertThat(c.getInstalledOn()).isNotEmpty(); + }) + .map(MigrationChain.Element::getVersion) + .containsExactly(expectedVersions); + } + + private void assertCreationOrder(String... labels) { + try (var session = driver.session()) { + var nodes = session.executeRead(tx -> tx.run(FIND_NODES_QUERY).list(r -> r.get("n").asNode())); + assertThat(nodes) + .map(n -> StreamSupport.stream(n.labels().spliterator(), false).filter(Predicate.not("OOO"::equals)).findFirst().orElseThrow()) + .containsExactly(labels); + } + } + + private void assertRepeats(int expectedTotal, int expectedModified) { + try (var session = driver.session()) { + var nodes = session.executeRead(tx -> tx.run("MATCH (n:FromRepeatable) RETURN n").list(r -> r.get("n").asNode())); + int cnt = 0; + int mod = 0; + for (Node node : nodes) { + ++cnt; + for (String l : node.labels()) { + if (l.equals("Mod")) { + ++mod; + } + } + } + assertThat(cnt).isEqualTo(expectedTotal); + assertThat(mod).isEqualTo(expectedModified); + } + } + } + @Nested class Repairing { diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V014__NewSecond.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V014__NewSecond.cypher new file mode 100644 index 0000000000..b7fdd20786 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V014__NewSecond.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N5 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V019__NewSecond.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V019__NewSecond.cypher new file mode 100644 index 0000000000..40f4e3a8f1 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/second/V019__NewSecond.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N6 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V001__NewFirst.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V001__NewFirst.cypher new file mode 100644 index 0000000000..e93489033c --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V001__NewFirst.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N7 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V009__NewFirst.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V009__NewFirst.cypher new file mode 100644 index 0000000000..9614c702f6 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/additional/third/V009__NewFirst.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N8 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V010__First.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V010__First.cypher new file mode 100644 index 0000000000..2ae568ef57 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V010__First.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N1 {created_on: datetime.transaction()}) RETURN m; diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V020__InitialSecond.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V020__InitialSecond.cypher new file mode 100644 index 0000000000..b4700c9956 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/base/first/V020__InitialSecond.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N2 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/base/second/V015__NewSecond.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/base/second/V015__NewSecond.cypher new file mode 100644 index 0000000000..46f0971bb8 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/base/second/V015__NewSecond.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N3 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/base/third/V005__NewFirst.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/base/third/V005__NewFirst.cypher new file mode 100644 index 0000000000..8107f7d5c6 --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/base/third/V005__NewFirst.cypher @@ -0,0 +1 @@ +CREATE (m:OOO:N4 {created_on: datetime.transaction()}) RETURN m; \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R002__na.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R002__na.cypher new file mode 100644 index 0000000000..d16743e10a --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R002__na.cypher @@ -0,0 +1 @@ +CREATE (n:FromRepeatable:Mod); \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R016__na.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R016__na.cypher new file mode 100644 index 0000000000..d16743e10a --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/modified/R016__na.cypher @@ -0,0 +1 @@ +CREATE (n:FromRepeatable:Mod); \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R002__na.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R002__na.cypher new file mode 100644 index 0000000000..b28765efbe --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R002__na.cypher @@ -0,0 +1 @@ +CREATE (n:FromRepeatable); \ No newline at end of file diff --git a/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R016__na.cypher b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R016__na.cypher new file mode 100644 index 0000000000..b28765efbe --- /dev/null +++ b/neo4j-migrations-test-resources/src/main/resources/ooo/repeatable/orig/R016__na.cypher @@ -0,0 +1 @@ +CREATE (n:FromRepeatable); \ No newline at end of file