Features#
Cypher Change Log Format#
[Required plugin version : 4.9.0.1]
The extension supports polyglot change logs (XML, SQL, YAML ...). The SQL format has been aliased to the more idiomatic Cypher format.
Cypher file names must end with .cypher.
Cypher change logs must start with the comment --liquibase formatted cypher.
Here is an example of a supported Cypher file:
-- liquibase formatted cypher
-- changeset fbiville:my-movie-init
CREATE (:Movie {title: 'My Life', genre: 'Comedy'});
Cypher files that are part of a folder inclusion must only contain a single Cypher query and no comment directive.
The extension supports change log file and folder inclusion.
Cypher and Rollback Changes#
[Required plugin version (Cypher alias) : 4.7.1.1]
The built-in SQL
and rollback changes are supported.
The SQL change is also aliased to cypher.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
</changeSet>
<changeSet id="translate" author="fbiville">
<neo4j:cypher>MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comédie'</neo4j:cypher>
<rollback>MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comedy'</rollback>
</changeSet>
</databaseChangeLog>
Warning
- The
cypherXML tag needs to be prepended with the corresponding extension namespace prefix. - If the query contains XML special characters such as
<or>, make sure to surround the query content with<![CDATA[at the beginning and]]>at the end.
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
}
]
}
},
{
"changeSet": {
"id": "translate",
"author": "fbiville",
"changes": [
{
"cypher": "MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comédie'"
}
],
"rollback": [
{
"cypher": "MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comedy'"
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- changeSet:
id: translate
author: fbiville
changes:
- cypher: 'MATCH (m:Movie {title: ''My Life''}) SET m.genre = ''Comédie'''
rollback:
- cypher: 'MATCH (m:Movie {title: ''My Life''}) SET m.genre = ''Comedy'''
-- liquibase formatted cypher
-- changeset fbiville:my-movie-init
CREATE (:Movie {title: 'My Life', genre: 'Comedy'});
-- changeset fbiville:translate
MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comédie'
-- rollback MATCH (m:Movie {title: 'My Life'}) SET m.genre = 'Comedy'
Neo4j Preconditions#
Learn more about preconditions in the Liquibase documentation, especially the failure and error handling section.
Built-in precondition support#
The extension has been successfully tested with the following built-in precondition since the very first release of the plugin:
dbms(targetingNeo4j)sqlCheck(aliased ascypherCheck, see below)
Others may work but have not been tested.
Version check#
[Required plugin version : 4.9.0]
The version precondition asserts the runtime Neo4j version starts with the specified string.
It can be combined with other preconditions with the standard boolean operators.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-neo4j-44-deployment" author="fbiville">
<preConditions onFail="CONTINUE">
<neo4j:version matches="4.4"/>
</preConditions>
<neo4j:cypher>CREATE (:Neo4j {neo4j44: true})</neo4j:cypher>
</changeSet>
<changeSet id="my-neo4j-non44-deployment" author="fbiville">
<preConditions onFail="CONTINUE">
<not>
<neo4j:version matches="4.4"/>
</not>
</preConditions>
<neo4j:cypher>CREATE (:Neo4j {neo4j44: false})</neo4j:cypher>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-neo4j-44-deployment",
"author": "fbiville",
"preConditions": [
{
"onFail": "CONTINUE"
},
{
"version": {
"matches": "4.4"
}
}
],
"changes": [
{
"cypher": "CREATE (:Neo4j {neo4j44: true})"
}
]
}
},
{
"changeSet": {
"id": "my-neo4j-non44-deployment",
"author": "fbiville",
"preConditions": [
{
"onFail": "CONTINUE"
},
{
"not": {
"version": {
"matches": "4.4"
}
}
}
],
"changes": [
{
"cypher": "CREATE (:Neo4j {neo4j44: false})"
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-neo4j-44-deployment
author: fbiville
preConditions:
- onFail: 'CONTINUE'
- version:
matches: '4.4'
changes:
- cypher: 'CREATE (:Neo4j {neo4j44: true})'
- changeSet:
id: my-neo4j-non44-deployment
author: fbiville
preConditions:
- onFail: 'CONTINUE'
- not:
- version:
matches: '4.4'
changes:
- cypher: 'CREATE (:Neo4j {neo4j44: false})'
Edition check#
[Required plugin version : 4.9.0]
The edition check asserts whether the target Neo4j deployment is the Community Edition or Enterprise Edition.
It can be combined with other preconditions with the standard boolean operators.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-neo4j-ee-deployment" author="fbiville">
<preConditions onFail="CONTINUE">
<neo4j:edition enterprise="true"/>
</preConditions>
<neo4j:cypher>CREATE (:Neo4j {enterprise: true})</neo4j:cypher>
</changeSet>
<changeSet id="my-neo4j-ce-deployment" author="fbiville">
<preConditions onFail="CONTINUE">
<neo4j:edition enterprise="false"/>
</preConditions>
<neo4j:cypher>CREATE (:Neo4j {enterprise: false})</neo4j:cypher>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-neo4j-ee-deployment",
"author": "fbiville",
"preConditions": [
{
"onFail": "CONTINUE"
},
{
"edition": {
"enterprise": true
}
}
],
"changes": [
{
"cypher": "CREATE (:Neo4j {enterprise: true})"
}
]
}
},
{
"changeSet": {
"id": "my-neo4j-ce-deployment",
"author": "fbiville",
"preConditions": [
{
"onFail": "CONTINUE"
},
{
"edition": {
"enterprise": false
}
}
],
"changes": [
{
"cypher": "CREATE (:Neo4j {enterprise: false})"
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-neo4j-ee-deployment
author: fbiville
preConditions:
- edition:
enterprise: true
- onFail: 'CONTINUE'
changes:
- cypher: 'CREATE (:Neo4j {enterprise: true})'
- changeSet:
id: my-neo4j-ce-deployment
author: fbiville
preConditions:
- edition:
enterprise: false
- onFail: 'CONTINUE'
changes:
- cypher: 'CREATE (:Neo4j {enterprise: false})'
Cypher check alias#
[Required plugin version : 4.9.0]
The cypherCheck aliases the existing sqlCheck precondition.
Cypher formatted change log files can only use sqlCheck at the moment.
cypherCheck can be combined with other preconditions with the standard boolean operators.
Warning
Before version 4.21.1.2, JSON and YAML change logs had to specify a sql attribute instead of cypher.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-neo4j-deployment" author="fbiville">
<preConditions onFail="CONTINUE">
<neo4j:cypherCheck expectedResult="0">MATCH (n:Neo4j) RETURN count(n)</neo4j:cypherCheck>
</preConditions>
<neo4j:cypher>CREATE (:Neo4j)</neo4j:cypher>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-neo4j-deployment",
"author": "fbiville",
"preConditions": [
{
"onFail": "CONTINUE"
},
{
"cypherCheck": {
"expectedResult": "0",
"cypher": "MATCH (n:Neo4j) RETURN count(n)"
}
}
],
"changes": [
{
"cypher": {
"cypher": "CREATE (:Neo4j)"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-neo4j-deployment
author: fbiville
preConditions:
- onFail: 'CONTINUE'
- cypherCheck:
expectedResult: '0'
cypher: MATCH (n:Neo4j) RETURN count(n)
changes:
- cypher:
cypher: CREATE (:Neo4j)
Insert Change#
[Required plugin version : 4.21.1]
The change allows to define the creation of individual nodes, with a single label specified with labelName.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet id="insert-node" author="fbiville">
<neo4j:insert labelName="Person">
<column name="id" value="8987212b-a6ff-48a1-901f-8c4b39bd6d9e" type="uuid"/>
<column name="age" valueNumeric="30" type="integer"/>
<column name="first_name" value="Florent"/>
<column name="last_name" value="Biville"/>
<column name="local_date" valueDate="2022-12-25" type="date"/>
<column name="local_time" valueDate="22:23:24" type="date"/>
<column name="local_date_time" valueDate="2018-02-01T12:13:14" type="date"/>
<column name="zoned_date_time" valueDate="2020-07-12T22:23:24+02:00" type="date"/>
<column name="polite" valueBoolean="true" type="boolean"/>
<column name="picture" value="DLxmEfVUC9CAmjiNyVphWw==" type="blob"/>
<column name="bio"
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nisi tellus, elementum id mi vitae, faucibus lacinia purus. Integer nec velit sit amet velit tincidunt ultrices eu eu massa. Vestibulum in libero vel neque interdum blandit in non libero. Aenean iaculis, erat ac molestie laoreet, risus ex faucibus odio, a fermentum turpis elit eget ex. Donec volutpat bibendum enim pretium pulvinar. Proin rutrum neque dui, a suscipit tellus semper suscipit. Praesent lobortis ut lorem vitae volutpat. Pellentesque a lorem eu lacus faucibus facilisis nec sed metus. Aenean lacinia luctus ultricies. Pellentesque cursus justo non iaculis tristique. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Duis tempor nisi ut turpis bibendum facilisis. Donec aliquet porttitor lacus, non rhoncus lectus laoreet et."
type="clob"/>
</neo4j:insert>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "insert-node",
"author": "fbiville",
"changes": [
{
"insert": {
"columns": [
{
"column": {
"name": "id",
"type": "uuid",
"value": "8987212b-a6ff-48a1-901f-8c4b39bd6d9e"
}
},
{
"column": {
"name": "age",
"type": "integer",
"valueNumeric": 30
}
},
{
"column": {
"name": "first_name",
"value": "Florent"
}
},
{
"column": {
"name": "last_name",
"value": "Biville"
}
},
{
"column": {
"name": "local_date",
"type": "date",
"valueDate": "2022-12-25"
}
},
{
"column": {
"name": "local_time",
"type": "date",
"valueDate": "22:23:24"
}
},
{
"column": {
"name": "local_date_time",
"type": "date",
"valueDate": "2018-02-01T12:13:14"
}
},
{
"column": {
"name": "zoned_date_time",
"type": "date",
"valueDate": "2020-07-12T22:23:24+02:00"
}
},
{
"column": {
"name": "polite",
"type": "boolean",
"valueBoolean": true
}
},
{
"column": {
"name": "picture",
"type": "blob",
"value": "DLxmEfVUC9CAmjiNyVphWw=="
}
},
{
"column": {
"name": "bio",
"type": "clob",
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nisi tellus, elementum id mi vitae, faucibus lacinia purus. Integer nec velit sit amet velit tincidunt ultrices eu eu massa. Vestibulum in libero vel neque interdum blandit in non libero. Aenean iaculis, erat ac molestie laoreet, risus ex faucibus odio, a fermentum turpis elit eget ex. Donec volutpat bibendum enim pretium pulvinar. Proin rutrum neque dui, a suscipit tellus semper suscipit. Praesent lobortis ut lorem vitae volutpat. Pellentesque a lorem eu lacus faucibus facilisis nec sed metus. Aenean lacinia luctus ultricies. Pellentesque cursus justo non iaculis tristique. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Duis tempor nisi ut turpis bibendum facilisis. Donec aliquet porttitor lacus, non rhoncus lectus laoreet et."
}
}
],
"labelName": "Person"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: insert-node
author: fbiville
changes:
- insert:
columns:
- column:
name: id
type: uuid
value: 8987212b-a6ff-48a1-901f-8c4b39bd6d9e
- column:
name: age
type: integer
valueNumeric: !!float '30'
- column:
name: first_name
value: Florent
- column:
name: last_name
value: Biville
- column:
name: local_date
type: date
valueDate: '2022-12-25'
- column:
name: local_time
type: date
valueDate: '22:23:24'
- column:
name: local_date_time
type: date
valueDate: '2018-02-01T12:13:14'
- column:
name: zoned_date_time
type: date
valueDate: '2020-07-12T22:23:24+02:00'
- column:
name: polite
type: boolean
valueBoolean: true
- column:
name: picture
type: blob
value: DLxmEfVUC9CAmjiNyVphWw==
- column:
name: bio
type: clob
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean
nisi tellus, elementum id mi vitae, faucibus lacinia purus. Integer
nec velit sit amet velit tincidunt ultrices eu eu massa. Vestibulum
in libero vel neque interdum blandit in non libero. Aenean iaculis,
erat ac molestie laoreet, risus ex faucibus odio, a fermentum turpis
elit eget ex. Donec volutpat bibendum enim pretium pulvinar. Proin rutrum
neque dui, a suscipit tellus semper suscipit. Praesent lobortis ut lorem
vitae volutpat. Pellentesque a lorem eu lacus faucibus facilisis nec
sed metus. Aenean lacinia luctus ultricies. Pellentesque cursus justo
non iaculis tristique. Vestibulum ante ipsum primis in faucibus orci
luctus et ultrices posuere cubilia curae; Duis tempor nisi ut turpis
bibendum facilisis. Donec aliquet porttitor lacus, non rhoncus lectus
laoreet et.
labelName: Person
Please refer to the Load Data documentation for the supported value types for each column.
Load Data#
[Required Liquibase core version : 4.11.0] [Required plugin version : 4.16.1.1]
Assuming the following (S)CSV data.scsv file:
name;age;some_date;ignored;uuid;is_polite;blob
Florent;30.5;2022-12-25;ignored;8d1208fc-f401-496c-9cb8-483fef121234;false;DLxmEfVUC9CAmjiNyVphWw==
Andrea;32;2020-07-12T22:23:24+02:00;ignored!;1bc59ddb-8d4d-41d0-9c9a-34e837de5678;true;NULL
Nathan;34;2018-02-01T12:13:14;ignored!;123e4567-e89b-12d3-a456-426614174000;true;NULL
Robert;36;22:23:24;ignored!;9986a49a-0cce-4982-b491-b8177fd0ef81;true;NULL
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="customer-import" author="asanturbano">
<loadData
file="e2e/load-data/data.scsv"
separator=";"
tableName="CsvPerson">
<column name="first_name" header="name" type="string"/>
<column name="wisdom_index" header="age" type="numeric"/>
<column name="some_date" index="2" type="date"/>
<column name="_" header="ignored" type="skip"/>
<column name="uuid" header="uuid" type="uuid"/>
<column name="polite" header="is_polite" type="boolean"/>
<column name="picture" header="blob" type="blob"/>
</loadData>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "customer-import",
"author": "asanturbano",
"changes": [
{
"loadData": {
"columns": [
{
"column": {
"header": "name",
"name": "first_name",
"type": "string"
}
},
{
"column": {
"header": "age",
"name": "wisdom_index",
"type": "numeric"
}
},
{
"column": {
"index": 2,
"name": "some_date",
"type": "date"
}
},
{
"column": {
"header": "ignored",
"name": "_",
"type": "skip"
}
},
{
"column": {
"header": "uuid",
"name": "uuid",
"type": "uuid"
}
},
{
"column": {
"header": "is_polite",
"name": "polite",
"type": "boolean"
}
},
{
"column": {
"header": "blob",
"name": "picture",
"type": "blob"
}
}
],
"file": "e2e/load-data/data.scsv",
"separator": ";",
"tableName": "CsvPerson"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: customer-import
author: asanturbano
changes:
- loadData:
columns:
- column:
header: name
name: first_name
type: string
- column:
header: age
name: wisdom_index
type: numeric
- column:
index: 2
name: some_date
type: date
- column:
header: ignored
name: _
type: skip
- column:
header: uuid
name: uuid
type: uuid
- column:
header: is_polite
name: polite
type: boolean
- column:
header: blob
name: picture
type: blob
file: e2e/load-data/data.scsv
separator: ;
tableName: CsvPerson
The general documentation of this change is available here.
The table below details how each supported data type is mapped to its Neo4j counterpart:
| Load Data Type | Liquibase Java Type | Example Value | Resulting Neo4j Java Type |
|---|---|---|---|
BLOB |
String |
DLxmEfVUC9CAmjiNyVphWw== (base64-encoded) |
byte[] |
BOOLEAN |
Boolean |
true or false |
Boolean |
CLOB |
String |
String |
|
DATE |
java.sql.Timestamp |
2018-02-01T12:13:14 |
java.time.LocalDateTime |
DATE |
java.sql.Date |
2018-02-01 |
java.time.LocalDate |
DATE |
java.sql.Time |
12:13:14 |
java.time.LocalTime |
DATE |
liquibase.statement.DatabaseFunction |
2018-02-01T12:13:14+02:00 |
java.time.ZonedDateTime |
NUMERIC |
liquibase.change.ColumnConfig.ValueNumeric |
42 or 42.0 |
Long or Double |
STRING |
String |
"a string" |
String |
UUID |
String |
1bc59ddb-8d4d-41d0-9c9a-34e837de5678 |
String |
SKIP is also supported: the value will be ignored.
Warning
BLOB files (see issue), CLOB files (see issue), SEQUENCE, COMPUTED, OTHER and UNKNOWN
load data types are currently unsupported.
Make sure to use the right valueXxx attribute:
valueBooleanfor boolean valuesvalueDatefor date/time valuesvalueNumericfor numeric valuesvaluefor everything else
Graph refactorings#
Node Merge#
[Required plugin version : 4.13.0]
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Horror'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Documentary'})</neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:mergeNodes fragment="(m:Movie {title: 'My Life'}) WITH m ORDER BY m.genre ASC" outputVariable="m">
<neo4j:propertyPolicy nameMatcher=".*" mergeStrategy="KEEP_FIRST"/>
</neo4j:mergeNodes>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Horror'})"
},
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Documentary'})"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"mergeNodes": {
"fragment": "(m:Movie {title: 'My Life'}) WITH m ORDER BY m.genre ASC",
"outputVariable": "m",
"propertyPolicies": [
{
"propertyPolicy": {
"mergeStrategy": "KEEP_FIRST",
"nameMatcher": ".*"
}
}
]
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Horror''})'
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Documentary''})'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- mergeNodes:
fragment: '(m:Movie {title: ''My Life''}) WITH m ORDER BY m.genre ASC'
outputVariable: m
propertyPolicies:
- propertyPolicy:
mergeStrategy: 'KEEP_FIRST'
nameMatcher: .*
Specify a Cypher query fragment, which defines the nodes to match for the merge operation. If fewer than two nodes are matched, the merge is a no-op.
Specify the variable in that fragment that refer to the matched nodes. This is reused to create the internal merge Cypher queries to execute.
Finally, make sure to define the merge policy for each persisted property. The extension goes through every unique property name, selects the first matching merge policy, in declaration order. If at least one property name does not match a policy, the merge fails and is canceled. Once the policy is matched for the property name, one of the following operations happens:
KEEP_FIRST: the first defined property value for that name is keptKEEP_LAST: the last defined property value for that name is keptKEEP_ALL: all defined property values are aggregated into an array (even if only a single value is found)
Note
"first" and "last" are defined by the ordering of the specified Cypher query fragment. It is strongly advised to
explicitly order the matched nodes with the ORDER BY clause like in the example.
Node Property Extraction#
[Required plugin version : 4.17.2]
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Movie {title: 'My Project', genre: 'Comedy'})</neo4j:cypher>
</changeSet>
<changeSet id="genre-extraction" author="marouane">
<neo4j:extractProperty property="genre" fromNodes="(m:Movie) WITH m ORDER BY id(m) ASC" nodesNamed="m">
<neo4j:toNodes withLabel="Genre" withProperty="genre">
<neo4j:linkedFromSource withType="HAS_GENRE" withDirection="OUTGOING" />
</neo4j:toNodes>
</neo4j:extractProperty>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Movie {title: 'My Project', genre: 'Comedy'})"
}
]
}
},
{
"changeSet": {
"id": "genre-extraction",
"author": "marouane",
"changes": [
{
"extractProperty": {
"fromNodes": "(m:Movie) WITH m ORDER BY id(m) ASC",
"nodesNamed": "m",
"property": "genre",
"toNodes": {
"withLabel": "Genre",
"withProperty": "genre",
"linkedFromSource": {
"withDirection": "OUTGOING",
"withType": "HAS_GENRE"
}
}
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Movie {title: ''My Project'', genre: ''Comedy''})'
- changeSet:
id: genre-extraction
author: marouane
changes:
- extractProperty:
fromNodes: '(m:Movie) WITH m ORDER BY id(m) ASC'
nodesNamed: 'm'
property: 'genre'
toNodes:
withLabel: 'Genre'
withProperty: 'genre'
linkedFromSource:
withDirection: 'OUTGOING'
withType: 'HAS_GENRE'
The node property extraction refactoring allows to extract node properties into their own nodes.
As for the node merge refactoring, the nodes to extract properties from are specified as a Cypher
fragment
(fromNodes attribute) and the variable name (nodesNamed attribute) bound to these nodes.
The property name to extract is specified with the property attribute.
The source nodes matched by the Cypher fragment will have their property removed.
That property will be set on the extracted nodes, with the name described by the withProperty attribute.
The extracted nodes' label is defined with the withLabel attribute.
Set the merge property to true to avoid duplicates with potentially existing nodes with the same label and property.
The default behavior is to create extracted nodes every time.
Optionally, the extracted nodes can be linked with the source nodes.
In that case, a type and a direction need to be specified with respectively the withType and withDirection
attributes.
Note
The relation direction is from the perspective of the source nodes.
In the example, OUTGOING means the relationship starts from the source node and goes out to the extracted node.
Conversely, INCOMING would mean the relationship comes in the source node from the extracted node.
It is also possible to avoid relationship duplicates by setting the corresponding merge
attribute to true. The default is to always create relationships.
Warning
merge=false on nodes with merge=true on relationships will trigger a validation warning.
Indeed, creating extracted nodes imply that new relationships will be created as well.
Setting merge=true on relationships in that case incur an unnecessary execution penalty.
Node Label Rename#
[Required plugin version : 4.25.0.1]
The label rename refactoring allows to rename one label to another, matching all or some of its nodes, in a single transaction or in batches.
As illustrated below, the main attributes of the refactoring are:
from: value of the existing labelto: value of the new label, replacing the existing one
Global Rename#
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Book {title: 'My Life', genre: 'Autobiography'})</neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameLabel from="Movie" to="Film" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Book {title: 'My Life', genre: 'Autobiography'})"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameLabel": {
"from": "Movie",
"to": "Film"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Book {title: ''My Life'', genre: ''Autobiography''})'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameLabel:
from: 'Movie'
to: 'Film'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Book {title: 'My Life', genre: 'Autobiography'})</neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameLabel from="Movie" to="Film" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Book {title: 'My Life', genre: 'Autobiography'})"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameLabel": {
"from": "Movie",
"to": "Film",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Book {title: ''My Life'', genre: ''Autobiography''})'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameLabel:
from: 'Movie'
to: 'Film'
enableBatchImport: true
batchSize: 1
Partial Rename#
The following attributes can also be set, in order to match only a subset of the nodes with the label specified in from:
fragmentspecifies the pattern to match the nodes againstoutputVariablespecifies the Cypher variable name defined infragmentthat denotes the targeted nodes
Note
The nodes that are going to be renamed sit at the intersection of what is defined in fragment and the nodes whose
label is specified by from.
In other words, if none of the nodes defined in fragment carry the label defined in from, the rename
is not going to modify any of those.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Movie {title: 'My Birthday', genre: 'Musical'})</neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameLabel from="Movie" to="Film" fragment="(m:Movie {title: 'My Birthday'})" outputVariable="m" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Movie {title: 'My Birthday', genre: 'Musical'})"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameLabel": {
"from": "Movie",
"to": "Film",
"fragment": "(m:Movie {title: 'My Birthday'})",
"outputVariable": "m"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Movie {title: ''My Birthday'', genre: ''Musical''})'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameLabel:
from: 'Movie'
to: 'Film'
fragment: '(m:Movie {title: ''My Birthday''})'
outputVariable: 'm'
Since this operation can potentially affect a lot of nodes, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even run out of memory.
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher>CREATE (:Movie {title: 'My Life', genre: 'Comedy'})</neo4j:cypher>
<neo4j:cypher>CREATE (:Movie {title: 'My Birthday', genre: 'Musical'})</neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameLabel from="Movie" to="Film" fragment="(m:Movie {title: 'My Birthday'})" outputVariable="m" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {title: 'My Life', genre: 'Comedy'})"
},
{
"cypher": "CREATE (:Movie {title: 'My Birthday', genre: 'Musical'})"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameLabel": {
"from": "Movie",
"to": "Film",
"fragment": "(m:Movie {title: 'My Birthday'})",
"outputVariable": "m",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {title: ''My Life'', genre: ''Comedy''})'
- cypher: 'CREATE (:Movie {title: ''My Birthday'', genre: ''Musical''})'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameLabel:
from: 'Movie'
to: 'Film'
fragment: '(m:Movie {title: ''My Birthday''})'
outputVariable: 'm'
enableBatchImport: true
batchSize: 1
Relationship Type Rename#
[Required plugin version : 4.25.0.1]
The type rename refactoring allows to rename one type to another, matching all or some of its relationships, in a single transaction or in batches.
As illustrated below, the main attributes of the refactoring are:
from: value of the existing typeto: value of the new type, replacing the existing one
Global Rename#
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameType from="SEEN_BY" to="VIEWED_BY" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameType": {
"from": "SEEN_BY",
"to": "VIEWED_BY"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''now''}]->(:Person)'
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameType:
from: 'SEEN_BY'
to: 'VIEWED_BY'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameType from="SEEN_BY" to="VIEWED_BY" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameType": {
"from": "SEEN_BY",
"to": "VIEWED_BY",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''now''}]->(:Person)'
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameType:
from: 'SEEN_BY'
to: 'VIEWED_BY'
enableBatchImport: true
batchSize: 1
Partial Rename#
The following attributes can also be set, in order to match only a subset of the relationships with the type specified in from:
fragmentspecifies the pattern to match the relationships againstoutputVariablespecifies the Cypher variable name defined infragmentthat denotes the targeted relationships
Note
The relationships that are going to be renamed sit at the intersection of what is defined in fragment and the
relationships whose type is specified by from.
In other words, if none of the relationships defined in fragment have the type defined in from, the rename
is not going to modify any of those.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameType from="SEEN_BY" to="VIEWED_BY" fragment="(:Person)<-[r:SEEN_BY]-()" outputVariable="r" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameType": {
"from": "SEEN_BY",
"to": "VIEWED_BY",
"fragment": "(:Person)<-[r:SEEN_BY]-()",
"outputVariable": "r"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''now''}]->(:Person)'
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameType:
from: 'SEEN_BY'
to: 'VIEWED_BY'
fragment: '(:Person)<-[r:SEEN_BY]-()'
outputVariable: 'r'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameType from="SEEN_BY" to="VIEWED_BY" fragment="(:Person)<-[r:SEEN_BY]-()" outputVariable="r" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'now'}]->(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:SEEN_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameType": {
"from": "SEEN_BY",
"to": "VIEWED_BY",
"fragment": "(:Person)<-[r:SEEN_BY]-()",
"outputVariable": "r",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''now''}]->(:Person)'
- cypher: 'CREATE (:Movie)-[:SEEN_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameType:
from: 'SEEN_BY'
to: 'VIEWED_BY'
fragment: '(:Person)<-[r:SEEN_BY]-()'
outputVariable: 'r'
enableBatchImport: true
batchSize: 1
Relationship Direction Inversion#
[Required plugin version : 4.25.1.1]
The direction inversion refactoring allows to flip the start and end node of relationships with the specified type, matching all or some of them, in a single transaction or in batches.
As illustrated below, the main attributes of the refactoring are:
type: type of the relationship(s) to invert
Global Inversion#
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)<-[:VIEWED_BY {date: 'yesterday'}]-(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:invertDirection type="VIEWED_BY" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)"
},
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'yesterday'}]-(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"invertDirection": {
"type": "VIEWED_BY"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''now''}]-(:Person)'
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''yesterday''}]-(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- invertDirection:
type: 'VIEWED_BY'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)<-[:VIEWED_BY {date: 'yesterday'}]-(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:invertDirection type="VIEWED_BY" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)"
},
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'yesterday'}]-(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"invertDirection": {
"type": "VIEWED_BY",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''now''}]-(:Person)'
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''yesterday''}]-(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- invertDirection:
type: 'VIEWED_BY'
enableBatchImport: true
batchSize: 1
Partial Inversion#
The following attributes can also be set, in order to match only a subset of the relationships with the type specified in type:
fragmentspecifies the pattern to match the relationships againstoutputVariablespecifies the Cypher variable name defined infragmentthat denotes the targeted relationships
Note
The relationships that are going to be inverted sit at the intersection of what is defined in fragment and the
relationships whose type is specified by type.
In other words, if none of the relationships defined in fragment have the type defined in type, the inversion
is not going to modify any of those.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:VIEWED_BY {date: 'now'}]->(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:VIEWED_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:invertDirection type="VIEWED_BY" fragment="(:Person)-[r:VIEWED_BY]->()" outputVariable="r" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:VIEWED_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"invertDirection": {
"type": "VIEWED_BY",
"fragment": "(:Person)-[r:VIEWED_BY]->()",
"outputVariable": "r"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''now''}]-(:Person)'
- cypher: 'CREATE (:Movie)-[:VIEWED_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- invertDirection:
type: 'VIEWED_BY'
fragment: '(:Person)-[r:VIEWED_BY]->()'
outputVariable: 'r'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)]]></neo4j:cypher>
<neo4j:cypher><![CDATA[CREATE (:Movie)-[:VIEWED_BY {date: 'yesterday'}]->(:Dog)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:invertDirection type="VIEWED_BY" fragment="(:Person)-[r:VIEWED_BY]->()" outputVariable="r" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie)<-[:VIEWED_BY {date: 'now'}]-(:Person)"
},
{
"cypher": "CREATE (:Movie)-[:VIEWED_BY {date: 'yesterday'}]->(:Dog)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"invertDirection": {
"type": "VIEWED_BY",
"fragment": "(:Person)-[r:VIEWED_BY]->()",
"outputVariable": "r",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie)<-[:VIEWED_BY {date: ''now''}]-(:Person)'
- cypher: 'CREATE (:Movie)-[:VIEWED_BY {date: ''yesterday''}]->(:Dog)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- invertDirection:
type: 'VIEWED_BY'
fragment: '(:Person)-[r:VIEWED_BY]->()'
outputVariable: 'r'
enableBatchImport: true
batchSize: 1
Property Rename#
[Required plugin version : 4.25.1.1]
The property rename refactoring allows to rename a property of all enclosing entities, or only nodes or relationships.
As illustrated below, the main attributes of the refactoring are:
from: name of the existing propertyto: new name of the property, replacing the existing oneentityType: type of the enclosing entity to match. One of:ALL(default),NODE,RELATIONSHIP.
Global Rename#
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameProperty from="calendar_date" to="date" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameProperty from="calendar_date" to="date" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
enableBatchImport: true
batchSize: 1
Node-only Property Rename#
When setting the entityType attribute to NODE, only the matching properties of nodes will be renamed:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameProperty from="calendar_date" to="date" entityType="NODE" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date",
"entityType": "NODE"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
entityType: 'NODE'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameProperty from="calendar_date" to="date" entityType="NODE" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date",
"entityType": "NODE",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
entityType: 'NODE'
enableBatchImport: true
batchSize: 1
Relationship-only Property Rename#
When setting the entityType attribute to RELATIONSHIP, only the matching properties of relationships will be renamed:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville">
<neo4j:renameProperty from="calendar_date" to="date" entityType="RELATIONSHIP" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date",
"entityType": "RELATIONSHIP"
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
entityType: 'RELATIONSHIP'
Since this operation can potentially affect a lot of data, running the change in a single transaction may be infeasible since the transaction would likely run either too slow, or even out of memory.
To prevent this, enableBatchImport must be set to true.
Since it relies on CALL {} IN TRANSACTIONS under the hood, the enclosing change set's runInTransaction must also be set to false.
This results in the change being executed in batches.
Warning
This setting only works if the target Neo4j instance supports CALL {} IN TRANSACTIONS (version 4.4 and later).
If not, the Neo4j plugin will run the change in a single, autocommit transaction.
Make sure to read about the consequences of changing runInTransaction.
The batchSize attribute controls how many transactions run.
If the attribute is not set, the batch size is defined on the Neo4j server side.
The error policy of inner transactions,
introduced in Neo4j 5.7, is configurable with the batchErrorPolicy attribute, and accepts the following values:
- CONTINUE
- BREAK
- FAIL
Inner transactions can also be configured to run in parallel since Neo4j 5.21.
The boolean concurrent attribute controls that behavior. Concurrency is disabled by default.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:neo4j="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init-oops" author="fbiville">
<neo4j:cypher><![CDATA[CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)]]></neo4j:cypher>
</changeSet>
<changeSet id="my-movie-init-fixed" author="fbiville" runInTransaction="false">
<neo4j:renameProperty from="calendar_date" to="date" entityType="RELATIONSHIP" enableBatchImport="true" batchSize="1" />
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init-oops",
"author": "fbiville",
"changes": [
{
"cypher": "CREATE (:Movie {calendar_date: 'today'})-[:SEEN_BY {calendar_date: 'now'}]->(:Person)"
}
]
}
},
{
"changeSet": {
"id": "my-movie-init-fixed",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"renameProperty": {
"from": "calendar_date",
"to": "date",
"entityType": "RELATIONSHIP",
"enableBatchImport": true,
"batchSize": 1
}
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init-oops
author: fbiville
changes:
- cypher: 'CREATE (:Movie {calendar_date: ''today''})-[:SEEN_BY {calendar_date: ''now''}]->(:Person)'
- changeSet:
id: my-movie-init-fixed
author: fbiville
runInTransaction: false
changes:
- renameProperty:
from: 'calendar_date'
to: 'date'
entityType: 'RELATIONSHIP'
enableBatchImport: true
batchSize: 1
Change Set's runInTransaction#
The default value of runInTransaction is true. This means that all changes of a given change set run in a single,
explicit transaction.
This is the right default and should be changed only if you need any of the following two Cypher constructs:
- [since Neo4j 4.4]
CALL {} IN TRANSACTIONS - [until Neo4j 4.4]
PERIODIC COMMIT
Indeed, using those constructs without disabling runInTransaction fails with a similar error message:
A query with 'CALL { ... } IN TRANSACTIONS' can only be executed in an implicit transaction, but tried to execute in an explicit transaction.
Setting runInTransaction to false on a change set means that all its changes are going to run in their own
auto-commit (or implicit) transaction.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="my-movie-init" author="fbiville" runInTransaction="false">
<sql>CALL { CREATE (:Movie {title: 'My Life', genre: 'Comedy'}) } IN TRANSACTIONS</sql>
</changeSet>
</databaseChangeLog>
{
"databaseChangeLog": [
{
"changeSet": {
"id": "my-movie-init",
"author": "fbiville",
"runInTransaction": false,
"changes": [
{
"cypher": "CALL { CREATE (:Movie {title: 'My Life', genre: 'Comedy'}) } IN TRANSACTIONS"
}
]
}
}
]
}
databaseChangeLog:
- changeSet:
id: my-movie-init
author: fbiville
runInTransaction: false
changes:
- cypher: 'CALL { CREATE (:Movie {title: ''My Life'', genre: ''Comedy''}) } IN TRANSACTIONS'
-- liquibase formatted sql
-- changeset fbiville:my-movie-init runInTransaction:false
CALL { CREATE (:Movie {title: 'My Life', genre: 'Comedy'}) } IN TRANSACTIONS
History Consistency#
runInTransaction is a sharp tool and can lead to unintended consequences.
If any of the changes of the enclosing change set fails, the change set is not going to be stored in the history graph.
Re-running this change set results in all changes being run again, even the ones that successfully ran before.
In situations where runInTransactions="false" cannot be avoided, make sure the affected change set's queries are
idempotent.
Defining constraints and using Cypher's MERGE instead
of CREATE usually helps.
Neo4j Isolation Level Refresher#
CALL {} IN TRANSACTIONS and PERIODIC COMMIT spawn a new transaction for each batch.
Since Neo4j's default isolation level is 'read-committed',
these new transactions can read data modified by previous transactions, whether they originate from the same statement
or from a completely different one.
Let us illustrate this with a simple example.
Assume the data is initialized with:
CREATE (:Person {name: 'Alejandro'})
CREATE (:Person {name: 'Filipe'})
CREATE (:Person {name: 'Florent'})
CREATE (:Person {name: 'Marouane'})
CREATE (:Person {name: 'Nathan'})
Person with a name property.
Running MATCH (p:Person) CALL { WITH p DELETE p } IN TRANSACTIONS OF 2 ROWS may have different outcomes.
This query deletes all nodes with the Person label in batches of 2.
The execution may succeed and run with 3 batches if the data was not concurrently altered by other transactions.
It may need more batches if concurrent transactions create more Person nodes.
It may need fewer batches if concurrent transactions delete Person nodes.
The CALL {} IN TRANSACTIONS may also fail if concurrent transactions create a relationship to any of the above nodes,
since DELETE assumes disconnected nodes (DETACH DELETE deletes nodes AND their relationships).