Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
781ec8b
test: create query scenarion to cypher
otaviojava May 18, 2025
d285402
feat: update cypher code
otaviojava May 18, 2025
6c0646f
feat: update neo4j with new method
otaviojava May 18, 2025
470130a
feat: update code with the implementation
otaviojava May 18, 2025
5357e24
feat: update query
otaviojava May 18, 2025
3cdfc27
feat: update neo4j database manager
otaviojava May 18, 2025
9a164e5
feat: update documentation on neo4jdatabase
otaviojava May 18, 2025
1a4704d
feat: include new method on neo4j template
otaviojava May 18, 2025
9c684f8
feat: update converter
otaviojava May 18, 2025
f147b48
feat: create implementation to neo4j template
otaviojava May 18, 2025
2a6fcea
test: update template integration
otaviojava May 18, 2025
ab09a1c
test: generate a magazine repository
otaviojava May 18, 2025
f6317f3
test: update repository integration
otaviojava May 18, 2025
ef222c2
feat: initial creation for the annotation
otaviojava May 18, 2025
18be278
docs: update Gremlin annotation
otaviojava May 18, 2025
a03fc11
feat: update tinkerpop
otaviojava May 18, 2025
48c5495
docs: update documentation tinkeropp
otaviojava May 18, 2025
8b11cd6
feat: udpate graph spi
otaviojava May 18, 2025
ddf5764
feat: update to extension
otaviojava May 18, 2025
3703814
feat: update method neo4jrepository
otaviojava May 18, 2025
5f4478d
feat: update tinkper
otaviojava May 18, 2025
0575d5f
feat: update paramconverter
otaviojava May 18, 2025
3279503
feat: update repository
otaviojava May 18, 2025
aa5e8a3
feat: update tinkperop test
otaviojava May 18, 2025
8704ed0
feat: generate implementation on tinkerpop template
otaviojava May 18, 2025
f785bf7
feat: remove condition on tinkerpop
otaviojava May 18, 2025
b719e33
feat: put parameter at condition
otaviojava May 18, 2025
34a9de6
feat: update tinkerpop repository
otaviojava May 18, 2025
a46b76c
feat: update tinkerpop repository bean
otaviojava May 18, 2025
873f604
feat: update custom test
otaviojava May 18, 2025
c9b57ae
feat: generate music store at neo4j
otaviojava May 18, 2025
47cd358
test: include sample using graph
otaviojava May 18, 2025
a1dbb39
feat: include configuration
otaviojava May 18, 2025
fb4fe82
test: update test scenarions
otaviojava May 18, 2025
7ab6db9
feat: update collection on repository
otaviojava May 19, 2025
bfe558f
feat: update to population
otaviojava May 19, 2025
f215843
test: create population
otaviojava May 19, 2025
d0c4363
test: include population on repository
otaviojava May 19, 2025
4339fe4
test: update scenarion on population
otaviojava May 19, 2025
a5e0ada
test: define human id as object
otaviojava May 19, 2025
0f142c3
test: update builder on human to keep long as reference instead of pr…
otaviojava May 19, 2025
5635cb6
test: generate new test on tinkerpop
otaviojava May 19, 2025
1c13ec9
test: update population on repository
otaviojava May 19, 2025
f085b13
style: remove imports
otaviojava May 19, 2025
e752228
style: remove imporst at default tinkerpop tempalte
otaviojava May 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.Values;
import org.neo4j.driver.types.TypeSystem;

import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -164,22 +165,50 @@ public long count(String entity) {
}

@Override
public Stream<CommunicationEntity> executeQuery(String cypher, Map<String, Object> parameters) {
public Stream<CommunicationEntity> cypher(String cypher, Map<String, Object> parameters) {
Objects.requireNonNull(cypher, "Cypher query is required");
Objects.requireNonNull(parameters, "Parameters map is required");

try (Transaction tx = session.beginTransaction()) {
Stream<CommunicationEntity> result = tx.run(cypher, Values.parameters(flattenMap(parameters)))
.list(record -> extractEntity("QueryResult", record, false))
.stream();
var result = tx.run(cypher, Values.parameters(flattenMap(parameters)));

List<CommunicationEntity> entities = result
.stream()
.map(record -> record.keys().stream()
.map(key -> {
var value = record.get(key);
if (value.hasType(TypeSystem.getDefault().NODE())) {
return extractEntity(key, record, false);
} else if (value.hasType(TypeSystem.getDefault().RELATIONSHIP())) {
var rel = value.asRelationship();
List<Element> elements = new ArrayList<>();
rel.asMap().forEach((k, v) -> elements.add(Element.of(k, v)));
elements.add(Element.of(ID, rel.elementId()));
elements.add(Element.of("start", rel.startNodeElementId()));
elements.add(Element.of("end", rel.endNodeElementId()));
return CommunicationEntity.of(key, elements);
}
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null)
)
.filter(Objects::nonNull)
.toList();
LOGGER.fine("Executed Cypher query: " + cypher);
tx.commit();
return result;
return entities.stream();
} catch (Exception e) {
throw new CommunicationException("Error executing Cypher query", e);
}
}

@Override
public Stream<CommunicationEntity> cypher(String cypher) {
return cypher(cypher, Collections.emptyMap());
}

@Override
public Stream<CommunicationEntity> traverse(String startNodeId, String label, int depth) {
Objects.requireNonNull(startNodeId, "Start node ID is required");
Expand Down Expand Up @@ -376,22 +405,45 @@ var record = result.hasNext() ? result.next() : null;
return entitiesResult;
}

private CommunicationEntity extractEntity(String entityName, org.neo4j.driver.Record record, boolean isFullNode) {
private CommunicationEntity extractEntity(String alias, org.neo4j.driver.Record record, boolean isFullNode) {
List<Element> elements = new ArrayList<>();

for (String key : record.keys()) {
var value = record.get(key);

if (value.hasType(org.neo4j.driver.types.TypeSystem.getDefault().NODE())) {
if (value.hasType(TypeSystem.getDefault().NODE())) {
var node = value.asNode();
node.asMap().forEach((k, v) -> elements.add(Element.of(k, v))); // Extract properties

node.asMap().forEach((k, v) -> elements.add(Element.of(k, v)));

elements.add(Element.of(ID, node.elementId()));
} else {
String fieldName = key.contains(".") ? key.substring(key.indexOf('.') + 1) : key;
elements.add(Element.of(fieldName, value.asObject()));
elements.add(Element.of("_alias", key));

var label = node.labels().iterator().hasNext()
? node.labels().iterator().next()
: key;

return CommunicationEntity.of(label, elements);
}

if (value.hasType(TypeSystem.getDefault().RELATIONSHIP())) {
var rel = value.asRelationship();

rel.asMap().forEach((k, v) -> elements.add(Element.of(k, v)));

elements.add(Element.of(ID, rel.elementId()));
elements.add(Element.of("start", rel.startNodeElementId()));
elements.add(Element.of("end", rel.endNodeElementId()));
elements.add(Element.of("_alias", key));

return CommunicationEntity.of(rel.type(), elements);
}

String fieldName = key.contains(".") ? key.substring(key.indexOf('.') + 1) : key;
elements.add(Element.of(fieldName, value.asObject()));
}

return CommunicationEntity.of(entityName, elements);
// No node or relationship found: use alias as fallback
return CommunicationEntity.of(alias, elements);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,16 @@ public interface Neo4JDatabaseManager extends GraphDatabaseManager {
* @return a stream of {@link CommunicationEntity} representing the query result.
* @throws NullPointerException if {@code cypher} or {@code parameters} is null.
*/
Stream<CommunicationEntity> executeQuery(String cypher, Map<String, Object> parameters);
Stream<CommunicationEntity> cypher(String cypher, Map<String, Object> parameters);

/**
* Executes a custom Cypher query without parameters and returns a stream of {@link CommunicationEntity}.
*
* @param cypher the Cypher query to execute.
* @return a stream of {@link CommunicationEntity} representing the query result.
* @throws NullPointerException if {@code cypher} is null.
*/
Stream<CommunicationEntity> cypher(String cypher);

/**
* Traverses the graph starting from a node and follows the specified label type up to a given depth.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ class DefaultNeo4JTemplate extends AbstractGraphTemplate implements Neo4JTemplat
public <T> Stream<T> cypher(String cypher, Map<String, Object> parameters) {
Objects.requireNonNull(cypher, "cypher is required");
Objects.requireNonNull(parameters, "parameters is required");
return manager.get().executeQuery(cypher, parameters)
return manager.get().cypher(cypher, parameters)
.map(e -> (T) converter.toEntity(e));
}

@SuppressWarnings("unchecked")
@Override
public <T> Stream<T> cypher(String cypher) {
Objects.requireNonNull(cypher, "cypher is required");
return manager.get().cypher(cypher)
.map(e -> (T) converter.toEntity(e));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ protected Neo4JTemplate template() {
return template;
}

@SuppressWarnings("unchecked")
@Override
public Object invoke(Object instance, Method method, Object[] args) throws Throwable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*
*/
public interface Neo4JTemplate extends GraphTemplate {

/**
* Executes a Cypher query and returns a stream of results mapped to the given entity type.
*
Expand All @@ -41,6 +42,16 @@ public interface Neo4JTemplate extends GraphTemplate {
*/
<T> Stream<T> cypher(String cypher, Map<String, Object> parameters);

/**
* Executes a Cypher query and returns a stream of results mapped to the given entity type.
*
* @param cypher The Cypher query string.
* @param <T> The entity type representing nodes or relationships within the graph database.
* @return A stream of entities representing the query result.
* @throws NullPointerException if {@code cypher} is null.
*/
<T> Stream<T> cypher(String cypher);

/**
* Traverses relationships from a given start node up to a specified depth.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,11 @@ void shouldExecuteCustomQuery() {
entityManager.insert(entity);

String cypher = "MATCH (e:person) RETURN e";
var result = entityManager.executeQuery(cypher, new HashMap<>()).toList();
var result = entityManager.cypher(cypher, new HashMap<>()).toList();

SoftAssertions.assertSoftly(softly -> {
softly.assertThat(result).isNotEmpty();
softly.assertThat(result.get(0).name()).isEqualTo(COLLECTION_NAME);
softly.assertThat(result.get(0).find("name")).isPresent();
softly.assertThat(result.get(0).find("city")).isPresent();
softly.assertThat(result.get(0).find("_id")).isPresent(); // Ensuring _id exists
Expand Down Expand Up @@ -524,7 +525,7 @@ void shouldCreateEdge() {
"id2", person2Id
);

var result = entityManager.executeQuery(cypher, parameters).toList();
var result = entityManager.cypher(cypher, parameters).toList();
SoftAssertions.assertSoftly(softly -> softly.assertThat(result).isNotEmpty());

entityManager.remove(person1, "FRIEND", person2);
Expand All @@ -546,7 +547,7 @@ void shouldRemoveEdge() {
String cypher = "MATCH (p1:person { _id: $_id1 })-[r:FRIEND]-(p2:person { _id: $_id2 }) RETURN r";
Map<String, Object> parameters = Map.of("_id1", startNodeId, "_id2", targetNodeId);

var result = entityManager.executeQuery(cypher, parameters).toList();
var result = entityManager.cypher(cypher, parameters).toList();
SoftAssertions.assertSoftly(softly -> softly.assertThat(result).isEmpty());
}

Expand All @@ -564,7 +565,7 @@ void shouldDeleteEdgeById() {
String cypher = "MATCH ()-[r]-() WHERE elementId(r) = $id RETURN r";
Map<String, Object> parameters = Map.of("id", edgeId);

var result = entityManager.executeQuery(cypher, parameters).toList();
var result = entityManager.cypher(cypher, parameters).toList();
SoftAssertions.assertSoftly(softly -> softly.assertThat(result).isEmpty());
}

Expand Down Expand Up @@ -598,7 +599,7 @@ void shouldCreateEdgeWithProperties() {
"WHERE elementId(r) = $edgeId RETURN r";
Map<String, Object> parameters = Map.of("edgeId", edge.id());

var result = entityManager.executeQuery(cypher, parameters).toList();
var result = entityManager.cypher(cypher, parameters).toList();
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(result).isNotEmpty();
softly.assertThat(edge.properties()).containsEntry("since", 2019);
Expand All @@ -623,7 +624,7 @@ private void removeAllEdges() {
String cypher = "MATCH ()-[r]-() DELETE r";

try {
entityManager.executeQuery(cypher, new HashMap<>()).toList();
entityManager.cypher(cypher, new HashMap<>()).toList();
} catch (Exception e) {
throw new RuntimeException("Failed to remove edges before node deletion", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private void removeAllEdges() {

try {
var entityManager = DatabaseContainer.INSTANCE.get("neo4j");
entityManager.executeQuery(cypher, new HashMap<>()).toList();
entityManager.cypher(cypher, new HashMap<>()).toList();
} catch (Exception e) {
throw new RuntimeException("Failed to remove edges before node deletion", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@

package org.eclipse.jnosql.databases.neo4j.integration;

import jakarta.data.repository.Param;
import jakarta.data.repository.Repository;
import org.eclipse.jnosql.databases.neo4j.mapping.Cypher;
import org.eclipse.jnosql.databases.neo4j.mapping.Neo4JRepository;

import java.util.List;

@Repository
public interface MagazineRepository extends Neo4JRepository<Magazine, String> {

@Cypher("MATCH (m:Magazine) RETURN m")
List<Magazine> findAllByCypher();

@Cypher("MATCH (m:Magazine{title: $title}) RETURN m")
List<Magazine> findByTitle(@Param("title") String title);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@


import jakarta.inject.Inject;
import org.assertj.core.api.SoftAssertions;
import org.eclipse.jnosql.databases.neo4j.communication.DatabaseContainer;
import org.eclipse.jnosql.databases.neo4j.communication.Neo4JConfigurations;
import org.eclipse.jnosql.databases.neo4j.mapping.Neo4JExtension;
Expand All @@ -28,6 +29,7 @@
import org.jboss.weld.junit5.auto.AddExtensions;
import org.jboss.weld.junit5.auto.AddPackages;
import org.jboss.weld.junit5.auto.EnableAutoWeld;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;

Expand All @@ -53,6 +55,11 @@ public class RepositoryIntegrationTest {
@Inject
private MagazineRepository repository;

@BeforeEach
void beforeEach() {
repository.deleteAll();
}

@Test
void shouldSave() {
Magazine magazine = new Magazine(null, "Effective Java", 1);
Expand All @@ -61,4 +68,30 @@ void shouldSave() {

}

@Test
void shouldFindAll() {
for (int index = 0; index < 5; index++) {
Magazine magazine = repository.save(new Magazine(null, "Effective Java", index));
assertThat(magazine).isNotNull();
}
var result = repository.findAllByCypher();
SoftAssertions.assertSoftly(soft -> {
assertThat(result).isNotNull();
assertThat(result).hasSize(5);
});
}

@Test
void shouldFindByName() {
for (int index = 0; index < 5; index++) {
Magazine magazine = repository.save(new Magazine(null, "Effective Java", index));
assertThat(magazine).isNotNull();
}
var result = repository.findByTitle("Effective Java");
SoftAssertions.assertSoftly(soft -> {
assertThat(result).isNotNull();
assertThat(result).hasSize(5);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.jnosql.databases.neo4j.integration;

import jakarta.inject.Inject;
import org.assertj.core.api.SoftAssertions;
import org.eclipse.jnosql.databases.neo4j.communication.DatabaseContainer;
import org.eclipse.jnosql.databases.neo4j.communication.Neo4JConfigurations;
import org.eclipse.jnosql.databases.neo4j.mapping.Neo4JTemplate;
Expand All @@ -30,6 +31,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;

import java.util.Map;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -110,4 +112,32 @@ void shouldDeleteAll(){
template.delete(Magazine.class).execute();
assertThat(template.select(Magazine.class).result()).isEmpty();
}

@Test
void shouldFindUsingCypher() {
for (int index = 0; index < 5; index++) {
Magazine magazine = template.insert(new Magazine(null, "Effective Java", index));
assertThat(magazine).isNotNull();
}
var result = template.cypher("MATCH (m:Magazine) RETURN m").toList();
SoftAssertions.assertSoftly(soft -> {
assertThat(result).isNotNull();
assertThat(result).hasSize(5);
});
}

@Test
void shouldFindUsingCypherParameter() {
for (int index = 0; index < 5; index++) {
Magazine magazine = template.insert(new Magazine(null, "Effective Java", index));
assertThat(magazine).isNotNull();
}

Map<String, Object> parameters = Map.of("title", "Effective Java");
var result = template.cypher("MATCH (m:Magazine{title: $title}) RETURN m", parameters).toList();
SoftAssertions.assertSoftly(soft -> {
assertThat(result).isNotNull();
assertThat(result).hasSize(5);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void shouldCypher() {
Map<String, Object> parameters = Collections.emptyMap();
CommunicationEntity entity = CommunicationEntity.of("Music");
entity.add(Element.of("name", "Ada"));
when(manager.executeQuery(cypher, parameters)).thenReturn(Stream.of(entity));
when(manager.cypher(cypher, parameters)).thenReturn(Stream.of(entity));

Stream<Music> result = template.cypher(cypher, parameters);
assertNotNull(result);
Expand Down
Loading