-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial version of neo4j driver adapter #1924
Changes from 1 commit
ee43c3a
79da8a0
a73e56e
7810178
02b36a2
6b39174
6de6d0c
baefd5f
c3422d2
cfba3bd
b9d63a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<!-- | ||
~ Copyright (c) 2024 nosqlbench | ||
~ | ||
~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
~ you may not use this file except in compliance with the License. | ||
~ You may obtain a copy of the License at | ||
~ | ||
~ http://www.apache.org/licenses/LICENSE-2.0 | ||
~ | ||
~ Unless required by applicable law or agreed to in writing, software | ||
~ distributed under the License is distributed on an "AS IS" BASIS, | ||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
~ See the License for the specific language governing permissions and | ||
~ limitations under the License. | ||
--> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>adapter-neo4j</artifactId> | ||
<packaging>jar</packaging> | ||
|
||
<parent> | ||
<artifactId>mvn-defaults</artifactId> | ||
<groupId>io.nosqlbench</groupId> | ||
<version>${revision}</version> | ||
<relativePath>../mvn-defaults</relativePath> | ||
</parent> | ||
|
||
<name>${project.artifactId}</name> | ||
<description> | ||
An nosqlbench adapter driver module for the Neo4J/Aura database. | ||
</description> | ||
|
||
<dependencies> | ||
|
||
<dependency> | ||
<groupId>io.nosqlbench</groupId> | ||
<artifactId>nb-annotations</artifactId> | ||
<version>${revision}</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.nosqlbench</groupId> | ||
<artifactId>adapters-api</artifactId> | ||
<version>${revision}</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.neo4j.driver</groupId> | ||
<artifactId>neo4j-java-driver</artifactId> | ||
<version>5.18.0</version> | ||
</dependency> | ||
|
||
</dependencies> | ||
|
||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (c) 2024 nosqlbench | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.nosqlbench.adapter.neo4j; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import org.neo4j.driver.Record; | ||
import org.neo4j.driver.exceptions.ClientException; | ||
|
||
import java.util.NoSuchElementException; | ||
|
||
|
||
public class Neo4JAdapterUtils { | ||
|
||
/** | ||
* Mask the digits in the given string with '*' | ||
* | ||
* @param unmasked The string to mask | ||
* @return The masked string | ||
*/ | ||
protected static String maskDigits(String unmasked) { | ||
assert StringUtils.isNotBlank(unmasked) && StringUtils.isNotEmpty(unmasked); | ||
int inputLength = unmasked.length(); | ||
StringBuilder masked = new StringBuilder(inputLength); | ||
for (char ch : unmasked.toCharArray()) { | ||
if (Character.isDigit(ch)) { | ||
masked.append("*"); | ||
} else { | ||
masked.append(ch); | ||
} | ||
msmygit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return masked.toString(); | ||
} | ||
|
||
/** | ||
* Reference: | ||
* - https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Value.html#asObject() | ||
*/ | ||
public static Object[] getFieldForAllRecords(Record[] records, String fieldName) { | ||
int n = records.length; | ||
Object[] values = new Object[n]; | ||
int idx; | ||
for (int i = 0; i < n; i++) { | ||
try { | ||
idx = records[i].index(fieldName); | ||
values[i] = records[i].get(idx).asObject(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If any one record does not have the required field, then the call to this method fails for all and exits the loop throwing an exception. Just verifying that this is the desired behavior as opposed to eg setting values[i] for that i to null or something and continuing iteration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, this was the intended behavior for this parsing of the I figured if the user is leveraging this method, they are actually expecting the field from all Should we add a |
||
} | ||
catch (NoSuchElementException e) { | ||
throw e; | ||
} | ||
catch (ClientException e) { | ||
throw e; | ||
} | ||
} | ||
return values; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright (c) 2024 nosqlbench | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.nosqlbench.adapter.neo4j; | ||
|
||
import io.nosqlbench.adapter.neo4j.ops.Neo4JBaseOp; | ||
import io.nosqlbench.adapters.api.activityimpl.OpMapper; | ||
import io.nosqlbench.adapters.api.activityimpl.uniform.BaseDriverAdapter; | ||
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter; | ||
import io.nosqlbench.nb.annotations.Service; | ||
import io.nosqlbench.nb.api.components.core.NBComponent; | ||
import io.nosqlbench.nb.api.config.standard.NBConfigModel; | ||
import io.nosqlbench.nb.api.config.standard.NBConfiguration; | ||
import io.nosqlbench.nb.api.labels.NBLabels; | ||
|
||
import java.util.function.Function; | ||
|
||
|
||
@Service(value = DriverAdapter.class, selector = "neo4j") | ||
public class Neo4JDriverAdapter extends BaseDriverAdapter<Neo4JBaseOp, Neo4JSpace> { | ||
|
||
public Neo4JDriverAdapter(NBComponent parentComponent, NBLabels labels) { | ||
super(parentComponent, labels); | ||
} | ||
|
||
@Override | ||
public OpMapper getOpMapper() { | ||
return new Neo4JOpMapper(this, getSpaceCache()); | ||
} | ||
|
||
@Override | ||
public Function<String, ? extends Neo4JSpace> getSpaceInitializer(NBConfiguration cfg) { | ||
return (s) -> new Neo4JSpace(s, cfg); | ||
} | ||
|
||
@Override | ||
public NBConfigModel getConfigModel() { | ||
return super.getConfigModel().add(Neo4JSpace.getConfigModel()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
* Copyright (c) 2024 nosqlbench | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.nosqlbench.adapter.neo4j; | ||
|
||
import io.nosqlbench.adapter.diag.DriverAdapterLoader; | ||
import io.nosqlbench.nb.annotations.Service; | ||
import io.nosqlbench.nb.api.components.core.NBComponent; | ||
import io.nosqlbench.nb.api.labels.NBLabels; | ||
|
||
@Service(value = DriverAdapterLoader.class, selector = "neo4j") | ||
public class Neo4JDriverAdapterLoader implements DriverAdapterLoader { | ||
@Override | ||
public Neo4JDriverAdapter load(NBComponent parent, NBLabels childLabels) { | ||
return new Neo4JDriverAdapter(parent, childLabels); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright (c) 2024 nosqlbench | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.nosqlbench.adapter.neo4j; | ||
|
||
import io.nosqlbench.adapter.neo4j.opdispensers.*; | ||
import io.nosqlbench.adapter.neo4j.ops.Neo4JBaseOp; | ||
import io.nosqlbench.adapter.neo4j.types.Neo4JOpType; | ||
import io.nosqlbench.adapters.api.activityimpl.OpDispenser; | ||
import io.nosqlbench.adapters.api.activityimpl.OpMapper; | ||
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverSpaceCache; | ||
import io.nosqlbench.adapters.api.templating.ParsedOp; | ||
import io.nosqlbench.engine.api.templating.TypeAndTarget; | ||
|
||
import java.util.function.LongFunction; | ||
|
||
|
||
public class Neo4JOpMapper implements OpMapper<Neo4JBaseOp> { | ||
private final DriverSpaceCache<? extends Neo4JSpace> cache; | ||
private final Neo4JDriverAdapter adapter; | ||
|
||
public Neo4JOpMapper(Neo4JDriverAdapter adapter, DriverSpaceCache<? extends Neo4JSpace> cache) { | ||
this.adapter = adapter; | ||
this.cache = cache; | ||
} | ||
|
||
@Override | ||
public OpDispenser<? extends Neo4JBaseOp> apply(ParsedOp op) { | ||
TypeAndTarget<Neo4JOpType, String> typeAndTarget = op.getTypeAndTarget(Neo4JOpType.class, String.class); | ||
LongFunction<String> spaceNameFunc = op.getAsFunctionOr("space", "default"); | ||
LongFunction<Neo4JSpace> spaceFunc = l -> cache.get(spaceNameFunc.apply(l)); | ||
return switch (typeAndTarget.enumId) { | ||
case autocommit -> new Neo4JAutoCommitOpDispenser( | ||
adapter, op, spaceFunc, typeAndTarget.enumId.getValue() | ||
); | ||
case read_transaction -> new Neo4JReadTxnOpDispenser( | ||
adapter, op, spaceFunc, typeAndTarget.enumId.getValue() | ||
); | ||
case write_transaction -> new Neo4JWriteTxnOpDispenser( | ||
adapter, op, spaceFunc, typeAndTarget.enumId.getValue() | ||
); | ||
}; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright (c) 2024 nosqlbench | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.nosqlbench.adapter.neo4j; | ||
|
||
import io.nosqlbench.nb.api.config.standard.*; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
|
||
import org.neo4j.driver.AuthTokens; | ||
import org.neo4j.driver.Driver; | ||
import org.neo4j.driver.GraphDatabase; | ||
|
||
import java.util.Optional; | ||
|
||
public class Neo4JSpace implements AutoCloseable { | ||
|
||
private final static Logger logger = LogManager.getLogger(Neo4JSpace.class); | ||
private final String space; | ||
private Driver driver; | ||
|
||
public Neo4JSpace(String space, NBConfiguration cfg) { | ||
this.space = space; | ||
this.driver = initializeDriver(cfg); | ||
driver.verifyConnectivity(); | ||
} | ||
|
||
private Driver initializeDriver(NBConfiguration cfg) { | ||
String dbURI = cfg.get("db_uri"); | ||
Optional<String> usernameOpt = cfg.getOptional("username"); | ||
Optional<String> passwordOpt = cfg.getOptional("password"); | ||
String username; | ||
String password; | ||
// user has supplied both username and password | ||
if (usernameOpt.isPresent() && passwordOpt.isPresent()) { | ||
username = usernameOpt.get(); | ||
password = passwordOpt.get(); | ||
logger.info(this.space + ": Creating new Neo4J driver with [" + | ||
"dbURI = " + dbURI + | ||
", username = " + username + | ||
", password = " + Neo4JAdapterUtils.maskDigits(password) + | ||
"]" | ||
); | ||
return GraphDatabase.driver(dbURI, AuthTokens.basic(username, password)); | ||
} | ||
// user has only supplied username | ||
else if (usernameOpt.isPresent()) { | ||
String error = "username is present, but password is not defined."; | ||
logger.error(error); | ||
throw new RuntimeException(error); | ||
} | ||
// user has only supplied password | ||
else if (passwordOpt.isPresent()) { | ||
String error = "password is present, but username is not defined."; | ||
logger.error(error); | ||
throw new RuntimeException(error); | ||
} | ||
// user has supplied neither | ||
else { | ||
logger.info(this.space + ": Creating new Neo4J driver with [" + | ||
"dbURI = " + dbURI + | ||
"]" | ||
); | ||
return GraphDatabase.driver(dbURI); | ||
} | ||
} | ||
|
||
public static NBConfigModel getConfigModel() { | ||
return ConfigModel.of(Neo4JSpace.class) | ||
.add(Param.required("db_uri", String.class)) | ||
.add(Param.optional("username", String.class)) | ||
.add(Param.optional("password", String.class)) | ||
.asReadOnly(); | ||
} | ||
|
||
public Driver getDriver() { | ||
return driver; | ||
} | ||
|
||
@Override | ||
public void close() throws Exception { | ||
if (driver != null){ | ||
driver.close(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit I think this would be a good candidate to be extracted into some common NB utility so that all drivers can utilize it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good. what place do we think if appropriate for this?