Skip to content

Commit 9660b14

Browse files
committed
Fixes neo4j-contrib/neo4j-apoc-procedures#2826: The apoc.import.csv/graphml procedures create empty wrong empty nodes with UNIQUE constraint violation (#3006)
1 parent 26b30f8 commit 9660b14

File tree

5 files changed

+72
-4
lines changed

5 files changed

+72
-4
lines changed

common/src/main/java/apoc/export/util/BatchTransaction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public void manualCommit(boolean log) {
4141
doCommit(log);
4242
}
4343

44+
public void rollback() {
45+
tx.rollback();
46+
}
47+
4448
private void doCommit(boolean log) {
4549
tx.commit();
4650
tx.close();
@@ -56,7 +60,6 @@ private Transaction beginTx() {
5660
@Override
5761
public void close() {
5862
if (tx!=null) {
59-
tx.commit();
6063
tx.close();
6164
if (reporter!=null) reporter.progress("finish after " + count + " row(s) ");
6265
}

core/src/main/java/apoc/export/csv/CsvEntityLoader.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public void loadNodes(final Object fileName, final List<String> labels, final Gr
7878

7979
final String[] loadCsvCompatibleHeader = fields.stream().map(f -> f.getName()).toArray(String[]::new);
8080
int lineNo = 0;
81-
try (BatchTransaction btx = new BatchTransaction(db, clc.getBatchSize(), reporter)) {
81+
BatchTransaction btx = new BatchTransaction(db, clc.getBatchSize(), reporter);
82+
try {
8283
for (String[] line : csv.readAll()) {
8384
lineNo++;
8485

@@ -138,6 +139,12 @@ public void loadNodes(final Object fileName, final List<String> labels, final Gr
138139
}
139140
reporter.update(1, 0, props++);
140141
}
142+
btx.commit();
143+
} catch (RuntimeException e) {
144+
btx.rollback();
145+
throw e;
146+
} finally {
147+
btx.close();
141148
}
142149
}
143150
}
@@ -183,7 +190,8 @@ public void loadRelationships(
183190
final String[] loadCsvCompatibleHeader = fields.stream().map(f -> f.getName()).toArray(String[]::new);
184191

185192
int lineNo = 0;
186-
try (BatchTransaction btx = new BatchTransaction(db, clc.getBatchSize(), reporter)) {
193+
BatchTransaction btx = new BatchTransaction(db, clc.getBatchSize(), reporter);
194+
try {
187195
for (String[] line : csv.readAll()) {
188196
lineNo++;
189197

@@ -225,6 +233,12 @@ public void loadRelationships(
225233
}
226234
reporter.update(0, 1, props);
227235
}
236+
btx.commit();
237+
} catch (RuntimeException e) {
238+
btx.rollback();
239+
throw e;
240+
} finally {
241+
btx.close();
228242
}
229243
}
230244
}

core/src/main/java/apoc/export/graphml/XmlGraphMLReader.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ public long parseXML(Reader input) throws XMLStreamException {
210210
Map<String, Key> nodeKeys = new HashMap<>();
211211
Map<String, Key> relKeys = new HashMap<>();
212212
int count = 0;
213-
try (BatchTransaction tx = new BatchTransaction(db, batchSize * 10, reporter)) {
213+
BatchTransaction tx = new BatchTransaction(db, batchSize * 10, reporter);
214+
try {
214215

215216
while (reader.hasNext()) {
216217
XMLEvent event = (XMLEvent) reader.next();
@@ -288,6 +289,12 @@ public long parseXML(Reader input) throws XMLStreamException {
288289
}
289290
}
290291
}
292+
tx.commit();
293+
} catch (Exception e) {
294+
tx.rollback();
295+
throw e;
296+
} finally {
297+
tx.close();
291298
}
292299
return count;
293300
}

core/src/test/java/apoc/export/csv/ImportCsvTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,28 @@ private void testSkipLine(long skipLine, int nodes) {
218218
db.executeTransactionally("MATCH (n:SkipLine) DETACH DELETE n");
219219
}
220220

221+
@Test
222+
public void issue2826WithImportCsv() {
223+
db.executeTransactionally("CREATE (n:Person {name: 'John'})");
224+
db.executeTransactionally("CREATE CONSTRAINT unique_person ON (n:Person) ASSERT n.name IS UNIQUE");
225+
try {
226+
TestUtil.testCall(db,
227+
"CALL apoc.import.csv([{fileName: $file, labels: ['Person']}], [], $config)",
228+
map("file", "file:/id.csv", "config", map("delimiter", '|')),
229+
(r) -> fail());
230+
} catch (RuntimeException e) {
231+
String expected = "Failed to invoke procedure `apoc.import.csv`: " +
232+
"Caused by: IndexEntryConflictException{propertyValues=( String(\"John\") ), addedNodeId=-1, existingNodeId=0}";
233+
assertEquals(expected, e.getMessage());
234+
}
235+
236+
// should return only 1 node due to constraint exception
237+
TestUtil.testCall(db, "MATCH (n:Person) RETURN properties(n) AS props",
238+
r -> assertEquals(Map.of("name", "John"), r.get("props")));
239+
240+
db.executeTransactionally("DROP CONSTRAINT unique_person");
241+
}
242+
221243
@Test
222244
public void testNodesAndRelsWithMultiTypes() {
223245
TestUtil.testCall(db,

core/src/test/java/apoc/export/graphml/ExportGraphMLTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import static org.junit.Assert.assertNotNull;
5252
import static org.junit.Assert.assertNull;
5353
import static org.junit.Assert.assertTrue;
54+
import static org.junit.Assert.fail;
5455
import static org.junit.Assume.assumeFalse;
5556
import static org.neo4j.configuration.GraphDatabaseSettings.TransactionStateMemoryAllocation.OFF_HEAP;
5657
import static org.neo4j.configuration.SettingValueParsers.BYTES;
@@ -342,7 +343,28 @@ public void testImportGraphMLWithEdgeWithoutDataKeys() throws Exception {
342343

343344
TestUtil.testCall(db, "MATCH ()-[c:RELATED]->() RETURN COUNT(c) AS c", null, (r) -> assertEquals(1L, r.get("c")));
344345
}
346+
347+
@Test
348+
public void issue2797WithImportGraphMl() {
349+
db.executeTransactionally("CREATE (n:FOO {name: 'foo'})");
350+
db.executeTransactionally("CREATE CONSTRAINT unique_foo ON (n:FOO) ASSERT n.name IS UNIQUE");
351+
try {
352+
TestUtil.testCall(db,
353+
"CALL apoc.import.graphml($file, {readLabels:true})",
354+
map("file", new File(directory, "importNodeEdges.graphml").getAbsolutePath()),
355+
(r) -> fail());
356+
} catch (Exception e) {
357+
String expected = "Failed to invoke procedure `apoc.import.graphml`: " +
358+
"Caused by: IndexEntryConflictException{propertyValues=( String(\"foo\") ), addedNodeId=-1, existingNodeId=3}";
359+
assertEquals(expected, e.getMessage());
360+
}
361+
362+
// should return only 1 node due to constraint exception
363+
TestUtil.testCall(db, "MATCH (n:FOO) RETURN properties(n) AS props",
364+
r -> assertEquals(Map.of("name", "foo"), r.get("props")));
345365

366+
db.executeTransactionally("DROP CONSTRAINT unique_foo");
367+
}
346368

347369
@Test
348370
public void testImportGraphMLWithoutCharactersDataKeys() throws Exception {

0 commit comments

Comments
 (0)