Skip to content

Commit

Permalink
WIP snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
fbiville committed Feb 26, 2024
1 parent 0636770 commit 9f0ded9
Show file tree
Hide file tree
Showing 17 changed files with 620 additions and 29 deletions.
47 changes: 36 additions & 11 deletions src/main/java/liquibase/ext/neo4j/database/Neo4jDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import liquibase.executor.ExecutorService;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;

import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -57,7 +59,7 @@ protected String getDefaultDatabaseProductName() {

@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return conn.getDatabaseProductName().equals("Neo4j");
return conn.getDatabaseProductName().startsWith("Neo4j");
}

@Override
Expand Down Expand Up @@ -90,14 +92,33 @@ public boolean supportsTablespaces() {

@Override
public boolean supportsCatalogs() {
return neo4jVersion.startsWith("4") || isV5OrLater();
return isGreaterOrEqualThanMajor(4);
}

@Override
public boolean supportsSequences() {
return false;
}

@Override
public boolean supportsSchemas() {
return false;
}

@Override
public boolean supports(Class<? extends DatabaseObject> object) {
if (Catalog.class.isAssignableFrom(object)) {
return isGreaterOrEqualThanMajor(4);
}
return object.getPackage().getName().startsWith("liquibase.ext.neo4j");
}

@Override
public String getDefaultCatalogName() {
return "neo4j";
}


@Override
public boolean isCaseSensitive() {
return true;
Expand All @@ -109,7 +130,7 @@ public void createIndex(String name, String label, String property) throws Datab
createIndexForNeo4j3(label, property);
} else if (neo4jVersion.startsWith("4")) {
createIndexForNeo4j4(name, label, property);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
createIndexForNeo4j5(name, label, property);
} else {
throw new DatabaseException(String.format(
Expand All @@ -127,7 +148,7 @@ public void createUniqueConstraint(String name, String label, String property) t
createUniqueConstraintForNeo4j3(label, property);
} else if (neo4jVersion.startsWith("4")) {
createUniqueConstraintForNeo4j4(name, label, property);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
createUniqueConstraintForNeo4j5(name, label, property);
} else {
throw new DatabaseException(String.format(
Expand All @@ -149,7 +170,7 @@ public void createNodeKeyConstraint(String name, String label, String firstPrope
createNodeKeyConstraintForNeo4j3(label, properties);
} else if (neo4jVersion.startsWith("4")) {
createNodeKeyConstraintForNeo4j4(name, label, properties);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
createNodeKeyConstraintForNeo4j5(name, label, properties);
} else {
throw new DatabaseException(String.format(
Expand All @@ -167,7 +188,7 @@ public void dropIndex(String name, String label, String property) throws Databas
dropIndexForNeo4j3(label, property);
} else if (neo4jVersion.startsWith("4")) {
dropIndexForNeo4j4(name);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
dropIndexForNeo4j5(name);
} else {
throw new DatabaseException(String.format(
Expand All @@ -185,7 +206,7 @@ public void dropUniqueConstraint(String name, String label, String property) thr
dropUniqueConstraintForNeo4j3(label, property);
} else if (neo4jVersion.startsWith("4")) {
dropConstraintForNeo4j4(name);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
dropConstraintForNeo4j5(name);
} else {
throw new DatabaseException(String.format(
Expand All @@ -207,7 +228,7 @@ public void dropNodeKeyConstraint(String name, String label, String firstPropert
dropNodeKeyConstraintForNeo4j3(label, properties);
} else if (neo4jVersion.startsWith("4")) {
dropConstraintForNeo4j4(name);
} else if (isV5OrLater()) {
} else if (isGreaterOrEqualThanMajor(5)) {
dropConstraintForNeo4j5(name);
} else {
throw new DatabaseException(String.format(
Expand All @@ -228,7 +249,11 @@ public void execute(SqlStatement statement) throws LiquibaseException {
}

public boolean supportsCallInTransactions() {
return neo4jVersion.startsWith("4.4") || isV5OrLater();
return is44OrLater();
}

public boolean is44OrLater() {
return neo4jVersion.startsWith("4.4") || isGreaterOrEqualThanMajor(5);
}

public String getNeo4jVersion() {
Expand Down Expand Up @@ -476,7 +501,7 @@ private void rollbackOnError(LiquibaseException le) {
throw convertToRuntimeException(le);
}

private boolean isV5OrLater() {
return Integer.parseInt(neo4jVersion.substring(0, 1), 10) >= 5;
private boolean isGreaterOrEqualThanMajor(int major) {
return Integer.parseInt(neo4jVersion.substring(0, 1), 10) >= major;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
Expand All @@ -38,6 +39,8 @@
import static liquibase.ext.neo4j.database.jdbc.SupportedJdbcUrl.normalizeUri;

class Neo4jConnection implements Connection, DatabaseMetaData {
private static final String SERVER_VERSION_QUERY =
"CALL dbms.components() YIELD name, edition, versions WHERE name = \"Neo4j Kernel\" RETURN edition, versions[0] AS version LIMIT 1";

private final String uri;
private final Driver driver;
Expand All @@ -46,6 +49,8 @@ class Neo4jConnection implements Connection, DatabaseMetaData {
private Transaction transaction;
private boolean autocommit = true;
private boolean closed;
private String neo4jVersion;
private String neo4jEdition;

public Neo4jConnection(String url, Properties info) {
this(url, info, new DriverConfigSupplier(QueryStringParser.parseQueryString(url.replaceFirst("jdbc:neo4j:", "")), info));
Expand Down Expand Up @@ -154,13 +159,15 @@ public boolean nullsAreSortedAtEnd() {
}

@Override
public String getDatabaseProductName() {
return "Neo4j";
public String getDatabaseProductName() throws SQLException {
readNeo4jVersionAndEdition();
return String.format("Neo4j (%s Edition)", neo4jEdition.equals("enterprise") ? "Enterprise" : "Community");
}

@Override
public String getDatabaseProductVersion() {
return null;
public String getDatabaseProductVersion() throws SQLException {
readNeo4jVersionAndEdition();
return neo4jVersion;
}

@Override
Expand Down Expand Up @@ -901,13 +908,25 @@ public int getResultSetHoldability() {
}

@Override
public int getDatabaseMajorVersion() {
return 0;
public int getDatabaseMajorVersion() throws SQLException {
readNeo4jVersionAndEdition();
String[] versionComponents = neo4jVersion.split("\\.");
if (versionComponents.length <= 1) {
throw new SQLException(String.format("Unrecognized Neo4j version string: %s", neo4jVersion));
}
String major = versionComponents[0];
return Integer.parseInt(major, 10);
}

@Override
public int getDatabaseMinorVersion() {
return 0;
public int getDatabaseMinorVersion() throws SQLException {
readNeo4jVersionAndEdition();
String[] versionComponents = neo4jVersion.split("\\.");
if (versionComponents.length <= 1) {
throw new SQLException(String.format("Unrecognized Neo4j version string: %s", neo4jVersion));
}
String minor = versionComponents[1];
return Integer.parseInt(minor, 10);
}

@Override
Expand Down Expand Up @@ -986,8 +1005,17 @@ public void setCatalog(String catalog) {
}

@Override
public String getCatalog() {
return null;
public String getCatalog() throws SQLException {
try (ResultSet resultSet = this.createStatement().executeQuery("CALL db.info() YIELD name RETURN name")) {
if (!resultSet.next()) {
throw new SQLException("Could not retrieve current database name (catalog): expected 1 row, got none");
}
String databaseName = resultSet.getString("name");
if (resultSet.next()) {
throw new SQLException("Could not retrieve current database name (catalog): expected 1 row, got at least 2");
}
return databaseName;
}
}

@Override
Expand Down Expand Up @@ -1250,10 +1278,10 @@ public Transaction getOrBeginTransaction() {
}

// visible for testing

final Transaction getTransaction() {
return transaction;
}

final Session openSession() {
return driver.session(this.sessionConfig);
}
Expand All @@ -1280,4 +1308,18 @@ private static void ensureResultSetConcurrency(int resultSetConcurrency) throws
throw new SQLFeatureNotSupportedException("only CONCUR_READ_ONLY is supported");
}
}

private void readNeo4jVersionAndEdition() throws SQLException {
if (this.neo4jVersion != null) {
return;
}
try (ResultSet results = this.createStatement().executeQuery(SERVER_VERSION_QUERY)) {
if (!results.next()) {
throw new SQLException("Could not retrieve Neo4j version and edition");
}
results.next();
this.neo4jVersion = results.getString("version");
this.neo4jEdition = results.getString("edition").toLowerCase(Locale.ENGLISH);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package liquibase.ext.neo4j.snapshot;

import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.ext.neo4j.database.Neo4jDatabase;
import liquibase.ext.neo4j.structure.Label;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
import liquibase.snapshot.SnapshotGenerator;
import liquibase.snapshot.SnapshotGeneratorChain;
import liquibase.snapshot.jvm.CatalogSnapshotGenerator;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;

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

public class LabelSnapshotGeneratorNeo4j 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_DEFAULT;
}
if (Catalog.class.isAssignableFrom(objectType)) {
return PRIORITY_ADDITIONAL;
}
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(Label.class)) {
return chain.snapshot(example, snapshot);
}
if (example instanceof Label) {
return example;
}
if (!(example instanceof Catalog)) {
return chain.snapshot(example, snapshot);
}
Catalog catalog = (Catalog) example;
Neo4jDatabase neo4j = (Neo4jDatabase) snapshot.getDatabase();
retrieveLabels(neo4j, catalog)
.forEach(catalog::addDatabaseObject);
return example;
}

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

@Override
@SuppressWarnings("unchecked")
public Class<? extends SnapshotGenerator>[] replaces() {
return new Class[] {CatalogSnapshotGenerator.class};
}

private static List<Label> retrieveLabels(Neo4jDatabase database, Catalog catalog) throws DatabaseException {
try {
return database.run(new RawSqlStatement("CALL db.labels() YIELD label RETURN label"))
.stream()
.map(row -> new Label(catalog, (String) row.get("label")))
.collect(Collectors.toList());
} catch (LiquibaseException e) {
throw new DatabaseException("Could not retrieve node labels during label snapshot", e);
}
}
}
Loading

0 comments on commit 9f0ded9

Please sign in to comment.