Skip to content

Commit

Permalink
Clear default transaction from thread local when the query fails
Browse files Browse the repository at this point in the history
Fixes #393
  • Loading branch information
nmervaillie authored and frant-hartm committed Sep 20, 2017
1 parent 1bf4f9c commit a3b17c2
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.neo4j.ogm.config.ObjectMapperFactory;
import org.neo4j.ogm.drivers.bolt.response.GraphModelResponse;
import org.neo4j.ogm.drivers.bolt.response.GraphRowModelResponse;
Expand All @@ -35,12 +38,16 @@
import org.neo4j.ogm.model.GraphRowListModel;
import org.neo4j.ogm.model.RestModel;
import org.neo4j.ogm.model.RowModel;
import org.neo4j.ogm.request.*;
import org.neo4j.ogm.request.DefaultRequest;
import org.neo4j.ogm.request.GraphModelRequest;
import org.neo4j.ogm.request.GraphRowListModelRequest;
import org.neo4j.ogm.request.Request;
import org.neo4j.ogm.request.RestModelRequest;
import org.neo4j.ogm.request.RowModelRequest;
import org.neo4j.ogm.request.Statement;
import org.neo4j.ogm.response.EmptyResponse;
import org.neo4j.ogm.response.Response;
import org.neo4j.ogm.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author vince
Expand Down Expand Up @@ -85,15 +92,14 @@ public Response<RowModel> execute(DefaultRequest query) {
String[] columns = null;
for (Statement statement : query.getStatements()) {
StatementResult result = executeRequest(statement);
if (columns == null) {
List<String> columnSet = result.keys();
columns = columnSet.toArray(new String[columnSet.size()]);
}
RowModelResponse rowModelResponse = new RowModelResponse(result, transactionManager);
RowModel model;
while ((model = rowModelResponse.next()) != null) {
rowmodels.add(model);
}
if (columns == null) {
columns = rowModelResponse.columns();
}
result.consume();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.neo4j.ogm.drivers.bolt.transaction.BoltTransaction;
import org.neo4j.ogm.exception.CypherException;
import org.neo4j.ogm.response.Response;
import org.neo4j.ogm.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Luanne Misquitta
Expand Down Expand Up @@ -69,13 +70,25 @@ public void close() {

@Override
public String[] columns() {
if (result.hasNext()) {
Record record = result.peek();
if (record != null) {
Set<String> columns = result.peek().asMap().keySet();
return columns.toArray(new String[columns.size()]);
try {
if (result.hasNext()) {
Record record = result.peek();
if (record != null) {
Set<String> columns = result.peek().asMap().keySet();
return columns.toArray(new String[columns.size()]);
}
}
} catch (ClientException ce) {
// exception may occur if records has not been fetched yet
// should we catch other things than ClientException ?
BoltTransaction tx = (BoltTransaction) transactionManager.getCurrentTransaction();
if (tx != null) {
tx.rollback();
}
LOGGER.debug("Error executing Cypher: {}, {}", ce.code(), ce.getMessage());
throw new CypherException("Error executing Cypher", ce, ce.code(), ce.getMessage());
}

return new String[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.context.MappedRelationship;
import org.neo4j.ogm.context.MappingContext;
import org.neo4j.ogm.context.TransientRelationship;
import org.neo4j.ogm.cypher.compiler.CompileContext;
import org.neo4j.ogm.cypher.compiler.Compiler;
import org.neo4j.ogm.exception.CypherException;
import org.neo4j.ogm.metadata.ClassInfo;
import org.neo4j.ogm.metadata.FieldInfo;
import org.neo4j.ogm.model.RowModel;
Expand All @@ -34,8 +38,6 @@
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.transaction.AbstractTransaction;
import org.neo4j.ogm.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Plans request execution and processes the response.
Expand Down Expand Up @@ -250,42 +252,53 @@ private void executeSave(CompileContext context, boolean transactionRequired) {
newTransaction = true;
}

//If there are statements that depend on new nodes i.e. relationships created between new nodes,
//we must create the new nodes first, and then use their node IDs when creating relationships between them
if (compiler.hasStatementsDependentOnNewNodes()) {

DefaultRequest createNodesRowRequest = new DefaultRequest();
createNodesRowRequest.setStatements(compiler.createNodesStatements());
try {
//If there are statements that depend on new nodes i.e. relationships created between new nodes,
//we must create the new nodes first, and then use their node IDs when creating relationships between them
if (compiler.hasStatementsDependentOnNewNodes()) {

// execute the statements to create new nodes. The ids will be returned
// and will be used in subsequent statements that refer to these new nodes.
try (Response<RowModel> response = session.requestHandler().execute(createNodesRowRequest)) {
registerEntityIds(context, response, entityReferenceMappings, relReferenceMappings);
}
DefaultRequest createNodesRowRequest = new DefaultRequest();
createNodesRowRequest.setStatements(compiler.createNodesStatements());

List<Statement> statements = new ArrayList<>();
statements.addAll(compiler.createRelationshipsStatements());
statements.addAll(compiler.updateNodesStatements());
statements.addAll(compiler.updateRelationshipStatements());
statements.addAll(compiler.deleteRelationshipStatements());
statements.addAll(compiler.deleteRelationshipEntityStatements());
// execute the statements to create new nodes. The ids will be returned
// and will be used in subsequent statements that refer to these new nodes.
try (Response<RowModel> response = session.requestHandler().execute(createNodesRowRequest)) {
registerEntityIds(context, response, entityReferenceMappings, relReferenceMappings);
}

DefaultRequest defaultRequest = new DefaultRequest();
defaultRequest.setStatements(statements);
List<Statement> statements = new ArrayList<>();
statements.addAll(compiler.createRelationshipsStatements());
statements.addAll(compiler.updateNodesStatements());
statements.addAll(compiler.updateRelationshipStatements());
statements.addAll(compiler.deleteRelationshipStatements());
statements.addAll(compiler.deleteRelationshipEntityStatements());

try (Response<RowModel> response = session.requestHandler().execute(defaultRequest)) {
registerEntityIds(context, response, entityReferenceMappings, relReferenceMappings);
registerNewRelIds(response, relReferenceMappings);
}
} else { // only update / delete statements
List<Statement> statements = compiler.getAllStatements();
if (statements.size() > 0) {
DefaultRequest defaultRequest = new DefaultRequest();
defaultRequest.setStatements(statements);

try (Response<RowModel> response = session.requestHandler().execute(defaultRequest)) {
registerEntityIds(context, response, entityReferenceMappings, relReferenceMappings);
registerNewRelIds(response, relReferenceMappings);
}
} else { // only update / delete statements
List<Statement> statements = compiler.getAllStatements();
if (statements.size() > 0) {
DefaultRequest defaultRequest = new DefaultRequest();
defaultRequest.setStatements(statements);
try (Response<RowModel> response = session.requestHandler().execute(defaultRequest)) {
registerEntityIds(context, response, entityReferenceMappings, relReferenceMappings);
registerNewRelIds(response, relReferenceMappings);
}
}
}
} catch (CypherException e) {
// all tx management logic should be here in case of error. At the moment it is
// split in various parts of the drivers. Needs to be refactored in next major version.
// This is intended to fix #393. Replacing previous workaround to make HTTP work
if (transactionRequired && newTransaction) {
tx.rollback();
tx.close();
throw e;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*/

package org.neo4j.ogm.persistence.transaction;

import java.io.IOException;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.neo4j.ogm.domain.simple.User;
import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.ogm.testutil.MultiDriverTestClass;

import static java.util.Collections.emptyMap;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

/**
* Test of behaviour of the Session for default transactions (transactions without explicit handling)
*
* @author Frantisek Hartman
*/
public class DefaultTransactionTest extends MultiDriverTestClass {

private static final Logger logger = LoggerFactory.getLogger(DefaultTransactionTest.class);

private static SessionFactory sessionFactory;
private Session session;

@BeforeClass
public static void setUpClass() throws Exception {
sessionFactory = new SessionFactory(driver, "org.neo4j.ogm.domain.simple");
}

@Before
public void init() throws IOException {
session = sessionFactory.openSession();
Result result = session.query("CREATE CONSTRAINT ON (u:User) ASSERT u.name IS UNIQUE", emptyMap());
}

@After
public void tearDown() throws Exception {
session.query("DROP CONSTRAINT ON (u:User) ASSERT u.name IS UNIQUE", emptyMap());
}

@Test
public void shouldBeAbleToUseSessionAfterDefaultTransactionFails() throws Exception {

User u1 = new User("frantisek");
session.save(u1);
session.clear();

try {
session.save(new User("frantisek"));
fail("Constraint violation should have make the second save fail");
} catch (Exception ex) {
logger.info("Caught exception", ex);
}

User loaded = session.load(User.class, u1.getId());
assertThat(loaded).isNotNull();
}
}

0 comments on commit a3b17c2

Please sign in to comment.