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
cypher
XML 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:
valueBoolean
for boolean valuesvalueDate
for date/time valuesvalueNumeric
for numeric valuesvalue
for 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.
<?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
:
fragment
specifies the pattern to match the nodes againstoutputVariable
specifies the Cypher variable name defined infragment
that 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.
<?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.
<?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
:
fragment
specifies the pattern to match the relationships againstoutputVariable
specifies the Cypher variable name defined infragment
that 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.
<?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.
<?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
:
fragment
specifies the pattern to match the relationships againstoutputVariable
specifies the Cypher variable name defined infragment
that 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.
<?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.
<?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.
<?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.
<?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).