diff --git a/adapter-neo4j/pom.xml b/adapter-neo4j/pom.xml
new file mode 100644
index 000000000..94bbdee00
--- /dev/null
+++ b/adapter-neo4j/pom.xml
@@ -0,0 +1,58 @@
+
+
+
+ 4.0.0
+
+ adapter-neo4j
+ jar
+
+
+ mvn-defaults
+ io.nosqlbench
+ ${revision}
+ ../mvn-defaults
+
+
+ ${project.artifactId}
+
+ An nosqlbench adapter driver module for the Neo4J/Aura database.
+
+
+
+
+
+ io.nosqlbench
+ nb-annotations
+ ${revision}
+ compile
+
+
+ io.nosqlbench
+ adapters-api
+ ${revision}
+ compile
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+ 5.18.0
+
+
+
+
+
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JAdapterUtils.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JAdapterUtils.java
new file mode 100644
index 000000000..ed60e8d64
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JAdapterUtils.java
@@ -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);
+ }
+ }
+ 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();
+ }
+ catch (NoSuchElementException e) {
+ throw e;
+ }
+ catch (ClientException e) {
+ throw e;
+ }
+ }
+ return values;
+ }
+}
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapter.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapter.java
new file mode 100644
index 000000000..833fb8630
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapter.java
@@ -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 {
+
+ public Neo4JDriverAdapter(NBComponent parentComponent, NBLabels labels) {
+ super(parentComponent, labels);
+ }
+
+ @Override
+ public OpMapper getOpMapper() {
+ return new Neo4JOpMapper(this, getSpaceCache());
+ }
+
+ @Override
+ public Function getSpaceInitializer(NBConfiguration cfg) {
+ return (s) -> new Neo4JSpace(s, cfg);
+ }
+
+ @Override
+ public NBConfigModel getConfigModel() {
+ return super.getConfigModel().add(Neo4JSpace.getConfigModel());
+ }
+}
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapterLoader.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapterLoader.java
new file mode 100644
index 000000000..53f9098b0
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JDriverAdapterLoader.java
@@ -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);
+ }
+}
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JOpMapper.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JOpMapper.java
new file mode 100644
index 000000000..918d52f4d
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JOpMapper.java
@@ -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 {
+ 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 typeAndTarget = op.getTypeAndTarget(Neo4JOpType.class, String.class);
+ LongFunction spaceNameFunc = op.getAsFunctionOr("space", "default");
+ LongFunction 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()
+ );
+ };
+ }
+}
+
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JSpace.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JSpace.java
new file mode 100644
index 000000000..82e06a473
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/Neo4JSpace.java
@@ -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 usernameOpt = cfg.getOptional("username");
+ Optional 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();
+ }
+ }
+}
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JAutoCommitOpDispenser.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JAutoCommitOpDispenser.java
new file mode 100644
index 000000000..f3899245e
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JAutoCommitOpDispenser.java
@@ -0,0 +1,42 @@
+/*
+ * 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.opdispensers;
+
+import io.nosqlbench.adapter.neo4j.Neo4JDriverAdapter;
+import io.nosqlbench.adapter.neo4j.ops.Neo4JAutoCommitOp;
+import io.nosqlbench.adapter.neo4j.Neo4JSpace;
+import io.nosqlbench.adapters.api.templating.ParsedOp;
+
+import org.neo4j.driver.async.AsyncSession;
+
+import java.util.function.LongFunction;
+
+
+public class Neo4JAutoCommitOpDispenser extends Neo4JBaseOpDispenser {
+
+ public Neo4JAutoCommitOpDispenser(Neo4JDriverAdapter adapter, ParsedOp op, LongFunction spaceFunc, String requiredTemplateKey) {
+ super(adapter, op, spaceFunc, requiredTemplateKey);
+ }
+
+ @Override
+ public LongFunction createOpFunc() {
+ return l -> new Neo4JAutoCommitOp(
+ spaceFunc.apply(l).getDriver().session(AsyncSession.class),
+ queryFunc.apply(l)
+ );
+ }
+}
diff --git a/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JBaseOpDispenser.java b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JBaseOpDispenser.java
new file mode 100644
index 000000000..ce852c886
--- /dev/null
+++ b/adapter-neo4j/src/main/java/io/nosqlbench/adapter/neo4j/opdispensers/Neo4JBaseOpDispenser.java
@@ -0,0 +1,67 @@
+/*
+ * 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.opdispensers;
+
+import io.nosqlbench.adapter.neo4j.Neo4JDriverAdapter;
+import io.nosqlbench.adapter.neo4j.ops.Neo4JBaseOp;
+import io.nosqlbench.adapter.neo4j.Neo4JSpace;
+import io.nosqlbench.adapters.api.activityimpl.BaseOpDispenser;
+import io.nosqlbench.adapters.api.templating.ParsedOp;
+
+import org.neo4j.driver.Query;
+
+import java.util.Collections;
+import java.util.function.LongFunction;
+import java.util.Map;
+
+
+public abstract class Neo4JBaseOpDispenser extends BaseOpDispenser {
+ protected final LongFunction spaceFunc;
+ protected final LongFunction cypherFunc;
+ protected final LongFunction queryFunc;
+ protected final LongFunction