Skip to content

Commit

Permalink
Implement support for node constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
fbiville committed Jan 24, 2025
1 parent b75f823 commit 0542de0
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class KernelVersion implements Comparable<KernelVersion> {
public static final KernelVersion V4_3_0 = new KernelVersion(4, 3, 0);
public static final KernelVersion V4_4_0 = new KernelVersion(4, 4, 0);
public static final KernelVersion V5_0_0 = new KernelVersion(5, 0, 0);
public static final KernelVersion V5_11_0 = new KernelVersion(5, 11, 0);
public static final KernelVersion V5_24_0 = new KernelVersion(5, 24, 0);
public static final KernelVersion V5_26_0 = new KernelVersion(5, 26, 0);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package liquibase.ext.neo4j.snapshot;

import liquibase.Scope;
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.ext.neo4j.database.KernelVersion;
import liquibase.ext.neo4j.database.Neo4jDatabase;
import liquibase.ext.neo4j.structure.EntityType;
import liquibase.ext.neo4j.structure.Label;
import liquibase.ext.neo4j.structure.NodeConstraint;
import liquibase.logging.Logger;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
import liquibase.snapshot.SnapshotGenerator;
import liquibase.snapshot.SnapshotGeneratorChain;
import liquibase.statement.core.RawParameterizedSqlStatement;
import liquibase.structure.DatabaseObject;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class NodeConstraintSnapshotGeneratorNeo4j implements SnapshotGenerator {

@Override
public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
if (!(database instanceof Neo4jDatabase)) {
return PRIORITY_NONE;
}
if (Label.class.isAssignableFrom(objectType)) {
return PRIORITY_ADDITIONAL;
}
if (NodeConstraint.class.isAssignableFrom(objectType)) {
return PRIORITY_DEFAULT;
}
return PRIORITY_NONE;
}

@Override
public <T extends DatabaseObject> T snapshot(T example, DatabaseSnapshot snapshot, SnapshotGeneratorChain chain) throws DatabaseException, InvalidExampleException {
Database database = snapshot.getDatabase();
if (!(database instanceof Neo4jDatabase)) {
return chain.snapshot(example, snapshot);
}
if (!snapshot.getSnapshotControl().shouldInclude(NodeConstraint.class)) {
return chain.snapshot(example, snapshot);
}
if (example instanceof NodeConstraint) {
return example;
}
if (!(example instanceof Label)) {
return chain.snapshot(example, snapshot);
}
Neo4jDatabase neo4j = (Neo4jDatabase) database;
KernelVersion version = neo4j.getKernelVersion();
if (version.compareTo(KernelVersion.V4_4_0) < 0) {
Logger log = Scope.getCurrentScope().getLog(getClass());
log.warning(String.format("Ignoring snapshot request as Neo4j version is too old (%s): expected at least 4.4",
version));
return chain.snapshot(example, snapshot);
}

Label label = (Label) example;
List<NodeConstraint> indices = retrieveConstraints(neo4j, label);
indices.forEach(label::addConstraint);
return example;
}

@Override
@SuppressWarnings("unchecked")
public Class<? extends DatabaseObject>[] addsTo() {
return new Class[]{Label.class};
}

@Override
public Class<? extends SnapshotGenerator>[] replaces() {
return null;
}

private static List<NodeConstraint> retrieveConstraints(Neo4jDatabase neo4j, Label label) throws DatabaseException {
String cypher = "SHOW CONSTRAINTS YIELD name, type, entityType, labelsOrTypes, properties, options " +
"WHERE entityType = $1 AND $2 IN labelsOrTypes " +
"RETURN type, name, labelsOrTypes AS labels, properties, options " +
"ORDER BY type, name ASC";
try {
return neo4j.run(new RawParameterizedSqlStatement(cypher,
EntityType.NODE.name(),
label.getName()
))
.stream()
.map(row -> mapConstraint(label, row))
.collect(Collectors.toList());
} catch (LiquibaseException e) {
throw new DatabaseException("Could not retrieve BTREE node indexes", e);
}
}

@SuppressWarnings("unchecked")
private static NodeConstraint mapConstraint(Label label, Map<String, ?> row) {
Map<String, Object> options = (Map<String, Object>) row.get("options");
return new NodeConstraint(
label,
(String) row.get("type"),
(String) row.get("name"),
((List<String>) row.get("labels")),
(List<String>) row.get("properties"),
(String) (options == null ? null : options.get("indexProvider")),
(Map<String, Object>) (options == null ? null : options.get("indexConfig")));
}

}
10 changes: 8 additions & 2 deletions src/main/java/liquibase/ext/neo4j/structure/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ public String getDatabase() {
return database;
}

public void addIndex(NodeIndex nodeIndex) {
public void addIndex(NodeIndex index) {
Set<DatabaseObject> objects = getAttribute("indices", new LinkedHashSet<>());
objects.add(nodeIndex);
objects.add(index);
setAttribute("indices", objects);
}

public void addConstraint(NodeConstraint constraint) {
Set<DatabaseObject> objects = getAttribute("constraints", new LinkedHashSet<>());
objects.add(constraint);
setAttribute("constraints", objects);
}

@Override
public Catalog getCatalog() {
return catalog;
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/liquibase/ext/neo4j/structure/NodeConstraint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package liquibase.ext.neo4j.structure;

import liquibase.structure.AbstractDatabaseObject;
import liquibase.structure.CatalogLevelObject;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Schema;

import java.util.List;
import java.util.Map;

public class NodeConstraint extends AbstractDatabaseObject implements CatalogLevelObject {

private Label label;
private String name;

public NodeConstraint() {
}

public NodeConstraint(Label label, String type, String name, List<String> labels, List<String> properties, String indexProvider, Map<String, Object> indexConfig) {
this.label = label;
this.name = name;
this.setAttribute("type", type);
this.setAttribute("labels", labels);
this.setAttribute("properties", properties);
this.setAttribute("indexProvider", indexProvider);
this.setAttribute("indexConfig", indexConfig);
}

@Override
public DatabaseObject[] getContainingObjects() {
return null;
}

@Override
public DatabaseObject setName(String name) {
this.name = name;
return this;
}

@Override
public String getName() {
return name;
}

@Override
public Catalog getCatalog() {
return label.getCatalog();
}

@Override
public Schema getSchema() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
liquibase.ext.neo4j.snapshot.LabelSnapshotGeneratorNeo4j
liquibase.ext.neo4j.snapshot.TypeSnapshotGeneratorNeo4j
liquibase.ext.neo4j.snapshot.NodeIndexSnapshotGeneratorNeo4j
liquibase.ext.neo4j.snapshot.NodeConstraintSnapshotGeneratorNeo4j
liquibase.ext.neo4j.snapshot.TypeSnapshotGeneratorNeo4j
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
liquibase.ext.neo4j.structure.Label
liquibase.ext.neo4j.structure.NodeIndex
liquibase.ext.neo4j.structure.NodeConstraint
liquibase.ext.neo4j.structure.Type
Loading

0 comments on commit 0542de0

Please sign in to comment.