Add migration to merge duplicate Locations

- having duplicate Location nodes in the production database blocks us
  from adding a unique constraint, so that Locations are not created
which have the same id.
This commit is contained in:
mattwr18 2020-01-22 13:19:38 +01:00
parent a86b26a756
commit 561889c530
2 changed files with 91 additions and 7 deletions

View File

@ -35,7 +35,7 @@ class Store {
const session = driver.session()
const readTxResultPromise = session.readTransaction(async txc => {
const result = await txc.run(
'MATCH (migration:Migration) RETURN migration {.*} ORDER BY migration.timestamp DESC',
'MATCH (migration:Migration) RETURN migration {.*} ORDER BY migration.createdAt DESC',
)
return result.records.map(r => r.get('migration'))
})
@ -64,12 +64,19 @@ class Store {
const { migrations } = set
const writeTxResultPromise = session.writeTransaction(txc => {
return Promise.all(
migrations.map(migration => {
const { title, description, timestamp } = migration
const properties = { title, description, timestamp }
return txc.run('CREATE (migration:Migration) SET migration += $properties', {
properties,
})
migrations.map(async migration => {
const { title, description } = migration
const properties = { title, description }
const migrationResult = await txc.run(
`
MERGE (migration:Migration { title: $properties.title })
ON CREATE SET migration += $properties,
migration.createdAt = toString(datetime())
RETURN migration
`,
{ properties },
)
return migrationResult
}),
)
})

View File

@ -0,0 +1,77 @@
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
import { getDriver } from '../neo4j'
export const description = `
This migration merges duplicate :Location nodes. It became
necessary after we realized that we had not set up constraints for Location.id in production.
`
export function up(next) {
const driver = getDriver()
const rxSession = driver.rxSession()
rxSession
.beginTransaction()
.pipe(
flatMap(transaction =>
concat(
transaction
.run(
`
MATCH (location:Location)
RETURN location {.id}
`,
)
.records()
.pipe(
map(record => {
const { id: locationIds } = record.get('location')
return { locationIds }
}),
mergeMap(({ locationIds }) => {
return transaction
.run(
`
MATCH(location:Location {id: $locationIds}), (location2:Location {id: $locationIds})
WHERE location.id = location2.id AND id(location) < id(location2)
CALL apoc.refactor.mergeNodes([location, location2], { properties: 'combine', mergeRels: true }) YIELD node as updatedLocation
RETURN location {.*},updatedLocation {.*}
`,
{ locationIds },
)
.records()
.pipe(
map(record => ({
location: record.get('location'),
updatedLocation: record.get('updatedLocation'),
})),
)
}),
),
transaction.commit(),
).pipe(catchError(error => transaction.rollback().pipe(throwError(error)))),
),
)
.subscribe({
next: ({ updatedLocation, location }) =>
// eslint-disable-next-line no-console
console.log(`
Merged:
=============================
locationId: ${location.id}
updatedLocation: ${location.id} => ${updatedLocation.id}
=============================
`),
complete: () => {
// eslint-disable-next-line no-console
console.log('Merging of duplicate locations completed')
next()
},
error: error => {
next(new Error(error), null)
},
})
}
export function down(next) {
next(new Error('Irreversible migration'))
}