Custom 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:count-movies runAlways:true
MATCH (m:Movie) WITH COUNT(m) AS count MERGE (c:Count) SET c.value = count
--rollback MATCH (c:Count) DETACH DELETE c
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
.
Warning
When using XML change logs, the cypher
tag needs to be prepended with the corresponding extension namespace prefix.
In the following example, the prefix is neo4j
:
<?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-latest.xsd">
<changeSet id="my-movie-init" author="fbiville">
<neo4j:cypher>MERGE (:Movie {title: 'My Life'})</neo4j:cypher>
<rollback>MATCH (m:Movie {title: 'My Life'}) DETACH DELETE m</rollback>
</changeSet>
Neo4j preconditions#
[Required plugin version | 4.9.0]
The extension defines three Neo4j-specific preconditions:
version
which asserts the expected Neo4j versionedition
which asserts the expected Neo4j edition (Community or Enterprise)cypherCheck
which aliases the existingsqlCheck
precondition
It also supports some built-in preconditions:
dbms
(restricting toneo4j
), which is supported since the very first release of the plugin
Warning
When using XML change logs, the cypherCheck
, version
and edition
tags need to be prepended with the corresponding extension namespace prefix.
In the following example, the prefix is neo4j
:
<?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-latest.xsd">
<changeSet id="some-change-set" author="some-author">
<preConditions onFail="CONTINUE">
<and>
<neo4j:cypherCheck expectedResult="0">
MATCH (n)
WHERE NONE(label IN LABELS(n) WHERE label STARTS WITH '__Liquibase')
RETURN COUNT(n)
</neo4j:cypherCheck>
<or>
<neo4j:edition enterprise="true" />
<neo4j:version matches="4.4" />
</or>
</and>
</preConditions>
<!-- [...] -->
</changeSet>
All boolean operators are supported.
Graph refactorings#
Node Merge#
[Required plugin version | 4.13.0]
<neo4j:mergeNodes fragment="(m:Movie {title: 'My Life'}) WITH m ORDER BY id(m) ASC" outputVariable="m">
<neo4j:propertyPolicy nameMatcher="name" mergeStrategy="KEEP_FIRST"/>
<neo4j:propertyPolicy nameMatcher="par.*" mergeStrategy="KEEP_LAST"/>
<neo4j:propertyPolicy nameMatcher=".*" mergeStrategy="KEEP_ALL"/>
</neo4j:mergeNodes>
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]
<neo4j:extractProperty property="genre"
fromNodes="(m:Movie) WITH m ORDER BY id(m) ASC"
nodesNamed="m">
<neo4j:toNodes withLabel="Genre" withProperty="genre" merge="true" />
</neo4j:extractProperty>
<neo4j:extractProperty property="genre"
fromNodes="(m:Movie) WITH m ORDER BY id(m) ASC"
nodesNamed="m">
<neo4j:toNodes withLabel="Genre" withProperty="genre" merge="true">
<neo4j:linkedFromSource withType="HAS_GENRE"
withDirection="OUTGOING"
merge="true" />
</neo4j:toNodes>
</neo4j:extractProperty>
{
"extractProperty": {
"property": "genre",
"fromNodes": "(m:Movie) WITH m ORDER BY id(m) ASC",
"nodesNamed": "m",
"toNodes": {
"withLabel": "Genre",
"withProperty": "genre",
"merge": true
}
}
}
{
"extractProperty": {
"property": "genre",
"fromNodes": "(m:Movie) WITH m ORDER BY id(m) ASC",
"nodesNamed": "m",
"toNodes": {
"withLabel": "Genre",
"withProperty": "genre",
"merge": true,
"extractedNodes": {
"linkedFromSource": {
"extractedRelationships": {
"withDirection": "OUTGOING",
"withType": "HAS_GENRE",
"merge": true
}
}
}
}
}
}
- extractProperty:
fromNodes: (m:Movie) WITH m ORDER BY id(m) ASC
nodesNamed: m
property: genre
toNodes:
withLabel: Genre
withProperty: genre
merge: true
- extractProperty:
fromNodes: (m:Movie) WITH m ORDER BY id(m) ASC
nodesNamed: m
property: genre
toNodes:
withLabel: Genre
withProperty: genre
merge: true
extractedNodes:
linkedFromSource:
extractedRelationships:
withType: HAS_GENRE
withDirection: !!liquibase.ext.neo4j.change.refactoring.RelationshipDirection 'OUTGOING'
merge: true
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 extracting nodes imply that new relationships will be created as well.
Setting merge=true
on relationships in that case incur an unnecessary execution penalty.