diff --git a/common/src/main/java/apoc/load/LoadJsonUtils.java b/common/src/main/java/apoc/load/LoadJsonUtils.java index 34a536144..8aebbc064 100644 --- a/common/src/main/java/apoc/load/LoadJsonUtils.java +++ b/common/src/main/java/apoc/load/LoadJsonUtils.java @@ -9,22 +9,31 @@ import java.util.Map; import java.util.stream.Stream; import org.neo4j.procedure.Name; +import org.neo4j.procedure.TerminationGuard; public class LoadJsonUtils { - public static Stream loadJsonStream(@Name("urlOrKeyOrBinary") Object urlOrKeyOrBinary, @Name("headers") Map headers, @Name("payload") String payload, String path, boolean failOnError, String compressionAlgo, List pathOptions) { + public static Stream loadJsonStream(@Name("urlOrKeyOrBinary") Object urlOrKeyOrBinary, @Name("headers") Map headers, @Name("payload") String payload, String path, boolean failOnError, String compressionAlgo, List pathOptions, TerminationGuard terminationGuard) { if (urlOrKeyOrBinary instanceof String) { headers = null != headers ? headers : new HashMap<>(); headers.putAll(Util.extractCredentialsIfNeeded((String) urlOrKeyOrBinary, failOnError)); } Stream stream = JsonUtil.loadJson(urlOrKeyOrBinary,headers,payload, path, failOnError, compressionAlgo, pathOptions); return stream.flatMap((value) -> { + if (terminationGuard != null) { + terminationGuard.check(); + } if (value instanceof Map) { return Stream.of(new MapResult((Map) value)); } if (value instanceof List) { if (((List)value).isEmpty()) return Stream.empty(); if (((List) value).get(0) instanceof Map) - return ((List) value).stream().map((v) -> new MapResult((Map) v)); + return ((List) value).stream().map((v) -> { + if (terminationGuard != null) { + terminationGuard.check(); + } + return new MapResult((Map) v); + }); return Stream.of(new MapResult(Collections.singletonMap("result",value))); } if(!failOnError) diff --git a/common/src/main/java/apoc/util/JsonUtil.java b/common/src/main/java/apoc/util/JsonUtil.java index 1574bc23a..9c7ea2041 100755 --- a/common/src/main/java/apoc/util/JsonUtil.java +++ b/common/src/main/java/apoc/util/JsonUtil.java @@ -3,6 +3,7 @@ import apoc.export.util.DurationValueSerializer; import apoc.export.util.PointSerializer; import apoc.export.util.TemporalSerializer; +import apoc.result.ProgressInfo; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; diff --git a/common/src/main/resources/movies.cypher b/common/src/main/resources/movies.cypher index a44fee01d..13843db33 100644 --- a/common/src/main/resources/movies.cypher +++ b/common/src/main/resources/movies.cypher @@ -1,11 +1,11 @@ CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'}) -CREATE (Keanu:Person {name:'Keanu Reeves', born:1964}) -CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967}) -CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961}) -CREATE (Hugo:Person {name:'Hugo Weaving', born:1960}) -CREATE (AndyW:Person {name:'Andy Wachowski', born:1967}) -CREATE (LanaW:Person {name:'Lana Wachowski', born:1965}) -CREATE (JoelS:Person {name:'Joel Silver', born:1952}) +CREATE (Keanu:Person:Other {name:'Keanu Reeves', id: randomUUID(), born:1964}) +CREATE (Carrie:Person:Other {name:'Carrie-Anne Moss', id: randomUUID(), born:1967}) +CREATE (Laurence:Person:Other {name:'Laurence Fishburne', id: randomUUID(), born:1961}) +CREATE (Hugo:Person:Other {name:'Hugo Weaving', id: randomUUID(), id: randomUUID(), born:1960}) +CREATE (AndyW:Person:Other {name:'Andy Wachowski', id: randomUUID(), born:1967}) +CREATE (LanaW:Person:Other {name:'Lana Wachowski', id: randomUUID(), born:1965}) +CREATE (JoelS:Person:Other {name:'Joel Silver', id: randomUUID(), born:1952}) CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix), (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix), @@ -15,7 +15,7 @@ CREATE (LanaW)-[:DIRECTED]->(TheMatrix), (JoelS)-[:PRODUCED]->(TheMatrix) -CREATE (Emil:Person {name:"Emil Eifrem", born:1978}) +CREATE (Emil:Person:Other {name:"Emil Eifrem", id: randomUUID(), born:1978}) CREATE (Emil)-[:ACTED_IN {roles:["Emil"]}]->(TheMatrix) CREATE (TheMatrixReloaded:Movie {title:'The Matrix Reloaded', released:2003, tagline:'Free your mind'}) @@ -39,9 +39,9 @@ CREATE (JoelS)-[:PRODUCED]->(TheMatrixRevolutions) CREATE (TheDevilsAdvocate:Movie {title:"The Devil's Advocate", released:1997, tagline:'Evil has its winning ways'}) -CREATE (Charlize:Person {name:'Charlize Theron', born:1975}) -CREATE (Al:Person {name:'Al Pacino', born:1940}) -CREATE (Taylor:Person {name:'Taylor Hackford', born:1944}) +CREATE (Charlize:Person:Other {name:'Charlize Theron', id: randomUUID(), born:1975}) +CREATE (Al:Person:Other {name:'Al Pacino', id: randomUUID(), born:1940}) +CREATE (Taylor:Person:Other {name:'Taylor Hackford', id: randomUUID(), born:1944}) CREATE (Keanu)-[:ACTED_IN {roles:['Kevin Lomax']}]->(TheDevilsAdvocate), (Charlize)-[:ACTED_IN {roles:['Mary Ann Lomax']}]->(TheDevilsAdvocate), @@ -49,19 +49,19 @@ CREATE (Taylor)-[:DIRECTED]->(TheDevilsAdvocate) CREATE (AFewGoodMen:Movie {title:"A Few Good Men", released:1992, tagline:"In the heart of the nation's capital, in a courthouse of the U.S. government, one man will stop at nothing to keep his honor, and one will stop at nothing to find the truth."}) -CREATE (TomC:Person {name:'Tom Cruise', born:1962}) -CREATE (JackN:Person {name:'Jack Nicholson', born:1937}) -CREATE (DemiM:Person {name:'Demi Moore', born:1962}) -CREATE (KevinB:Person {name:'Kevin Bacon', born:1958}) -CREATE (KieferS:Person {name:'Kiefer Sutherland', born:1966}) -CREATE (NoahW:Person {name:'Noah Wyle', born:1971}) -CREATE (CubaG:Person {name:'Cuba Gooding Jr.', born:1968}) -CREATE (KevinP:Person {name:'Kevin Pollak', born:1957}) -CREATE (JTW:Person {name:'J.T. Walsh', born:1943}) -CREATE (JamesM:Person {name:'James Marshall', born:1967}) -CREATE (ChristopherG:Person {name:'Christopher Guest', born:1948}) -CREATE (RobR:Person {name:'Rob Reiner', born:1947}) -CREATE (AaronS:Person {name:'Aaron Sorkin', born:1961}) +CREATE (TomC:Person:Other {name:'Tom Cruise', id: randomUUID(), born:1962}) +CREATE (JackN:Person:Other {name:'Jack Nicholson', id: randomUUID(), born:1937}) +CREATE (DemiM:Person:Other {name:'Demi Moore', id: randomUUID(), born:1962}) +CREATE (KevinB:Person:Other {name:'Kevin Bacon', id: randomUUID(), born:1958}) +CREATE (KieferS:Person:Other {name:'Kiefer Sutherland', id: randomUUID(), born:1966}) +CREATE (NoahW:Person:Other {name:'Noah Wyle', id: randomUUID(), born:1971}) +CREATE (CubaG:Person:Other {name:'Cuba Gooding Jr.', id: randomUUID(), born:1968}) +CREATE (KevinP:Person:Other {name:'Kevin Pollak', id: randomUUID(), born:1957}) +CREATE (JTW:Person:Other {name:'J.T. Walsh', id: randomUUID(), born:1943}) +CREATE (JamesM:Person:Other {name:'James Marshall', id: randomUUID(), born:1967}) +CREATE (ChristopherG:Person:Other {name:'Christopher Guest', id: randomUUID(), born:1948}) +CREATE (RobR:Person:Other {name:'Rob Reiner', id: randomUUID(), born:1947}) +CREATE (AaronS:Person:Other {name:'Aaron Sorkin', id: randomUUID(), born:1961}) CREATE (TomC)-[:ACTED_IN {roles:['Lt. Daniel Kaffee']}]->(AFewGoodMen), (JackN)-[:ACTED_IN {roles:['Col. Nathan R. Jessup']}]->(AFewGoodMen), @@ -79,13 +79,13 @@ CREATE (AaronS)-[:WROTE]->(AFewGoodMen) CREATE (TopGun:Movie {title:"Top Gun", released:1986, tagline:'I feel the need, the need for speed.'}) -CREATE (KellyM:Person {name:'Kelly McGillis', born:1957}) -CREATE (ValK:Person {name:'Val Kilmer', born:1959}) -CREATE (AnthonyE:Person {name:'Anthony Edwards', born:1962}) -CREATE (TomS:Person {name:'Tom Skerritt', born:1933}) -CREATE (MegR:Person {name:'Meg Ryan', born:1961}) -CREATE (TonyS:Person {name:'Tony Scott', born:1944}) -CREATE (JimC:Person {name:'Jim Cash', born:1941}) +CREATE (KellyM:Person:Other {name:'Kelly McGillis', id: randomUUID(), born:1957}) +CREATE (ValK:Person:Other {name:'Val Kilmer', id: randomUUID(), born:1959}) +CREATE (AnthonyE:Person:Other {name:'Anthony Edwards', id: randomUUID(), born:1962}) +CREATE (TomS:Person:Other {name:'Tom Skerritt', id: randomUUID(), born:1933}) +CREATE (MegR:Person:Other {name:'Meg Ryan', id: randomUUID(), born:1961}) +CREATE (TonyS:Person:Other {name:'Tony Scott', id: randomUUID(), born:1944}) +CREATE (JimC:Person:Other {name:'Jim Cash', id: randomUUID(), born:1941}) CREATE (TomC)-[:ACTED_IN {roles:['Maverick']}]->(TopGun), (KellyM)-[:ACTED_IN {roles:['Charlie']}]->(TopGun), @@ -97,14 +97,14 @@ CREATE (JimC)-[:WROTE]->(TopGun) CREATE (JerryMaguire:Movie {title:'Jerry Maguire', released:2000, tagline:'The rest of his life begins now.'}) -CREATE (ReneeZ:Person {name:'Renee Zellweger', born:1969}) -CREATE (KellyP:Person {name:'Kelly Preston', born:1962}) -CREATE (JerryO:Person {name:"Jerry O'Connell", born:1974}) -CREATE (JayM:Person {name:'Jay Mohr', born:1970}) -CREATE (BonnieH:Person {name:'Bonnie Hunt', born:1961}) -CREATE (ReginaK:Person {name:'Regina King', born:1971}) -CREATE (JonathanL:Person {name:'Jonathan Lipnicki', born:1996}) -CREATE (CameronC:Person {name:'Cameron Crowe', born:1957}) +CREATE (ReneeZ:Person:Other {name:'Renee Zellweger', id: randomUUID(), born:1969}) +CREATE (KellyP:Person:Other {name:'Kelly Preston', id: randomUUID(), born:1962}) +CREATE (JerryO:Person:Other {name:"Jerry O'Connell", id: randomUUID(), born:1974}) +CREATE (JayM:Person:Other {name:'Jay Mohr', id: randomUUID(), born:1970}) +CREATE (BonnieH:Person:Other {name:'Bonnie Hunt', id: randomUUID(), born:1961}) +CREATE (ReginaK:Person:Other {name:'Regina King', id: randomUUID(), born:1971}) +CREATE (JonathanL:Person:Other {name:'Jonathan Lipnicki', id: randomUUID(), born:1996}) +CREATE (CameronC:Person:Other {name:'Cameron Crowe', id: randomUUID(), born:1957}) CREATE (TomC)-[:ACTED_IN {roles:['Jerry Maguire']}]->(JerryMaguire), (CubaG)-[:ACTED_IN {roles:['Rod Tidwell']}]->(JerryMaguire), @@ -120,11 +120,11 @@ CREATE (CameronC)-[:WROTE]->(JerryMaguire) CREATE (StandByMe:Movie {title:"Stand By Me", released:1986, tagline:"For some, it's the last real taste of innocence, and the first real taste of life. But for everyone, it's the time that memories are made of."}) -CREATE (RiverP:Person {name:'River Phoenix', born:1970}) -CREATE (CoreyF:Person {name:'Corey Feldman', born:1971}) -CREATE (WilW:Person {name:'Wil Wheaton', born:1972}) -CREATE (JohnC:Person {name:'John Cusack', born:1966}) -CREATE (MarshallB:Person {name:'Marshall Bell', born:1942}) +CREATE (RiverP:Person:Other {name:'River Phoenix', id: randomUUID(), born:1970}) +CREATE (CoreyF:Person:Other {name:'Corey Feldman', id: randomUUID(), born:1971}) +CREATE (WilW:Person:Other {name:'Wil Wheaton', id: randomUUID(), born:1972}) +CREATE (JohnC:Person:Other {name:'John Cusack', id: randomUUID(), born:1966}) +CREATE (MarshallB:Person:Other {name:'Marshall Bell', id: randomUUID(), born:1942}) CREATE (WilW)-[:ACTED_IN {roles:['Gordie Lachance']}]->(StandByMe), (RiverP)-[:ACTED_IN {roles:['Chris Chambers']}]->(StandByMe), @@ -136,9 +136,9 @@ CREATE (RobR)-[:DIRECTED]->(StandByMe) CREATE (AsGoodAsItGets:Movie {title:'As Good as It Gets', released:1997, tagline:'A comedy from the heart that goes for the throat.'}) -CREATE (HelenH:Person {name:'Helen Hunt', born:1963}) -CREATE (GregK:Person {name:'Greg Kinnear', born:1963}) -CREATE (JamesB:Person {name:'James L. Brooks', born:1940}) +CREATE (HelenH:Person:Other {name:'Helen Hunt', id: randomUUID(), born:1963}) +CREATE (GregK:Person:Other {name:'Greg Kinnear', id: randomUUID(), born:1963}) +CREATE (JamesB:Person:Other {name:'James L. Brooks', id: randomUUID(), born:1940}) CREATE (JackN)-[:ACTED_IN {roles:['Melvin Udall']}]->(AsGoodAsItGets), (HelenH)-[:ACTED_IN {roles:['Carol Connelly']}]->(AsGoodAsItGets), @@ -147,11 +147,11 @@ CREATE (JamesB)-[:DIRECTED]->(AsGoodAsItGets) CREATE (WhatDreamsMayCome:Movie {title:'What Dreams May Come', released:1998, tagline:'After life there is more. The end is just the beginning.'}) -CREATE (AnnabellaS:Person {name:'Annabella Sciorra', born:1960}) -CREATE (MaxS:Person {name:'Max von Sydow', born:1929}) -CREATE (WernerH:Person {name:'Werner Herzog', born:1942}) -CREATE (Robin:Person {name:'Robin Williams', born:1951}) -CREATE (VincentW:Person {name:'Vincent Ward', born:1956}) +CREATE (AnnabellaS:Person:Other {name:'Annabella Sciorra', id: randomUUID(), born:1960}) +CREATE (MaxS:Person:Other {name:'Max von Sydow', id: randomUUID(), born:1929}) +CREATE (WernerH:Person:Other {name:'Werner Herzog', id: randomUUID(), born:1942}) +CREATE (Robin:Person:Other {name:'Robin Williams', id: randomUUID(), born:1951}) +CREATE (VincentW:Person:Other {name:'Vincent Ward', id: randomUUID(), born:1956}) CREATE (Robin)-[:ACTED_IN {roles:['Chris Nielsen']}]->(WhatDreamsMayCome), (CubaG)-[:ACTED_IN {roles:['Albert Lewis']}]->(WhatDreamsMayCome), @@ -161,10 +161,10 @@ CREATE (VincentW)-[:DIRECTED]->(WhatDreamsMayCome) CREATE (SnowFallingonCedars:Movie {title:'Snow Falling on Cedars', released:1999, tagline:'First loves last. Forever.'}) -CREATE (EthanH:Person {name:'Ethan Hawke', born:1970}) -CREATE (RickY:Person {name:'Rick Yune', born:1971}) -CREATE (JamesC:Person {name:'James Cromwell', born:1940}) -CREATE (ScottH:Person {name:'Scott Hicks', born:1953}) +CREATE (EthanH:Person:Other {name:'Ethan Hawke', id: randomUUID(), born:1970}) +CREATE (RickY:Person:Other {name:'Rick Yune', id: randomUUID(), born:1971}) +CREATE (JamesC:Person:Other {name:'James Cromwell', id: randomUUID(), born:1940}) +CREATE (ScottH:Person:Other {name:'Scott Hicks', id: randomUUID(), born:1953}) CREATE (EthanH)-[:ACTED_IN {roles:['Ishmael Chambers']}]->(SnowFallingonCedars), (RickY)-[:ACTED_IN {roles:['Kazuo Miyamoto']}]->(SnowFallingonCedars), @@ -173,11 +173,11 @@ CREATE (ScottH)-[:DIRECTED]->(SnowFallingonCedars) CREATE (YouveGotMail:Movie {title:"You've Got Mail", released:1998, tagline:'At odds in life... in love on-line.'}) -CREATE (ParkerP:Person {name:'Parker Posey', born:1968}) -CREATE (DaveC:Person {name:'Dave Chappelle', born:1973}) -CREATE (SteveZ:Person {name:'Steve Zahn', born:1967}) -CREATE (TomH:Person {name:'Tom Hanks', born:1956}) -CREATE (NoraE:Person {name:'Nora Ephron', born:1941}) +CREATE (ParkerP:Person:Other {name:'Parker Posey', id: randomUUID(), born:1968}) +CREATE (DaveC:Person:Other {name:'Dave Chappelle', id: randomUUID(), born:1973}) +CREATE (SteveZ:Person:Other {name:'Steve Zahn', id: randomUUID(), born:1967}) +CREATE (TomH:Person:Other {name:'Tom Hanks', id: randomUUID(), born:1956}) +CREATE (NoraE:Person:Other {name:'Nora Ephron', id: randomUUID(), born:1941}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Fox']}]->(YouveGotMail), (MegR)-[:ACTED_IN {roles:['Kathleen Kelly']}]->(YouveGotMail), @@ -188,10 +188,10 @@ CREATE (NoraE)-[:DIRECTED]->(YouveGotMail) CREATE (SleeplessInSeattle:Movie {title:'Sleepless in Seattle', released:1993, tagline:'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'}) -CREATE (RitaW:Person {name:'Rita Wilson', born:1956}) -CREATE (BillPull:Person {name:'Bill Pullman', born:1953}) -CREATE (VictorG:Person {name:'Victor Garber', born:1949}) -CREATE (RosieO:Person {name:"Rosie O'Donnell", born:1962}) +CREATE (RitaW:Person:Other {name:'Rita Wilson', id: randomUUID(), born:1956}) +CREATE (BillPull:Person:Other {name:'Bill Pullman', id: randomUUID(), born:1953}) +CREATE (VictorG:Person:Other {name:'Victor Garber', id: randomUUID(), born:1949}) +CREATE (RosieO:Person:Other {name:"Rosie O'Donnell", id: randomUUID(), born:1962}) CREATE (TomH)-[:ACTED_IN {roles:['Sam Baldwin']}]->(SleeplessInSeattle), (MegR)-[:ACTED_IN {roles:['Annie Reed']}]->(SleeplessInSeattle), @@ -202,8 +202,8 @@ CREATE (NoraE)-[:DIRECTED]->(SleeplessInSeattle) CREATE (JoeVersustheVolcano:Movie {title:'Joe Versus the Volcano', released:1990, tagline:'A story of love, lava and burning desire.'}) -CREATE (JohnS:Person {name:'John Patrick Stanley', born:1950}) -CREATE (Nathan:Person {name:'Nathan Lane', born:1956}) +CREATE (JohnS:Person:Other {name:'John Patrick Stanley', id: randomUUID(), born:1950}) +CREATE (Nathan:Person:Other {name:'Nathan Lane', id: randomUUID(), born:1956}) CREATE (TomH)-[:ACTED_IN {roles:['Joe Banks']}]->(JoeVersustheVolcano), (MegR)-[:ACTED_IN {roles:['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(JoeVersustheVolcano), @@ -211,9 +211,9 @@ CREATE (JohnS)-[:DIRECTED]->(JoeVersustheVolcano) CREATE (WhenHarryMetSally:Movie {title:'When Harry Met Sally', released:1998, tagline:'At odds in life... in love on-line.'}) -CREATE (BillyC:Person {name:'Billy Crystal', born:1948}) -CREATE (CarrieF:Person {name:'Carrie Fisher', born:1956}) -CREATE (BrunoK:Person {name:'Bruno Kirby', born:1949}) +CREATE (BillyC:Person:Other {name:'Billy Crystal', id: randomUUID(), born:1948}) +CREATE (CarrieF:Person:Other {name:'Carrie Fisher', id: randomUUID(), born:1956}) +CREATE (BrunoK:Person:Other {name:'Bruno Kirby', id: randomUUID(), born:1949}) CREATE (BillyC)-[:ACTED_IN {roles:['Harry Burns']}]->(WhenHarryMetSally), (MegR)-[:ACTED_IN {roles:['Sally Albright']}]->(WhenHarryMetSally), @@ -225,7 +225,7 @@ CREATE (NoraE)-[:WROTE]->(WhenHarryMetSally) CREATE (ThatThingYouDo:Movie {title:'That Thing You Do', released:1996, tagline:'In every life there comes a time when that thing you dream becomes that thing you do'}) -CREATE (LivT:Person {name:'Liv Tyler', born:1977}) +CREATE (LivT:Person:Other {name:'Liv Tyler', id: randomUUID(), born:1977}) CREATE (TomH)-[:ACTED_IN {roles:['Mr. White']}]->(ThatThingYouDo), (LivT)-[:ACTED_IN {roles:['Faye Dolan']}]->(ThatThingYouDo), @@ -233,10 +233,10 @@ CREATE (TomH)-[:DIRECTED]->(ThatThingYouDo) CREATE (TheReplacements:Movie {title:'The Replacements', released:2000, tagline:'Pain heals, Chicks dig scars... Glory lasts forever'}) -CREATE (Brooke:Person {name:'Brooke Langton', born:1970}) -CREATE (Gene:Person {name:'Gene Hackman', born:1930}) -CREATE (Orlando:Person {name:'Orlando Jones', born:1968}) -CREATE (Howard:Person {name:'Howard Deutch', born:1950}) +CREATE (Brooke:Person:Other {name:'Brooke Langton', id: randomUUID(), born:1970}) +CREATE (Gene:Person:Other {name:'Gene Hackman', id: randomUUID(), born:1930}) +CREATE (Orlando:Person:Other {name:'Orlando Jones', id: randomUUID(), born:1968}) +CREATE (Howard:Person:Other {name:'Howard Deutch', id: randomUUID(), born:1950}) CREATE (Keanu)-[:ACTED_IN {roles:['Shane Falco']}]->(TheReplacements), (Brooke)-[:ACTED_IN {roles:['Annabelle Farrell']}]->(TheReplacements), @@ -245,8 +245,8 @@ CREATE (Howard)-[:DIRECTED]->(TheReplacements) CREATE (RescueDawn:Movie {title:'RescueDawn', released:2006, tagline:"Based on the extraordinary true story of one man's fight for freedom"}) -CREATE (ChristianB:Person {name:'Christian Bale', born:1974}) -CREATE (ZachG:Person {name:'Zach Grenier', born:1954}) +CREATE (ChristianB:Person:Other {name:'Christian Bale', id: randomUUID(), born:1974}) +CREATE (ZachG:Person:Other {name:'Zach Grenier', id: randomUUID(), born:1954}) CREATE (MarshallB)-[:ACTED_IN {roles:['Admiral']}]->(RescueDawn), (ChristianB)-[:ACTED_IN {roles:['Dieter Dengler']}]->(RescueDawn), @@ -255,7 +255,7 @@ CREATE (WernerH)-[:DIRECTED]->(RescueDawn) CREATE (TheBirdcage:Movie {title:'The Birdcage', released:1996, tagline:'Come as you are'}) -CREATE (MikeN:Person {name:'Mike Nichols', born:1931}) +CREATE (MikeN:Person:Other {name:'Mike Nichols', id: randomUUID(), born:1931}) CREATE (Robin)-[:ACTED_IN {roles:['Armand Goldman']}]->(TheBirdcage), (Nathan)-[:ACTED_IN {roles:['Albert Goldman']}]->(TheBirdcage), @@ -263,8 +263,8 @@ CREATE (MikeN)-[:DIRECTED]->(TheBirdcage) CREATE (Unforgiven:Movie {title:'Unforgiven', released:1992, tagline:"It's a hell of a thing, killing a man"}) -CREATE (RichardH:Person {name:'Richard Harris', born:1930}) -CREATE (ClintE:Person {name:'Clint Eastwood', born:1930}) +CREATE (RichardH:Person:Other {name:'Richard Harris', id: randomUUID(), born:1930}) +CREATE (ClintE:Person:Other {name:'Clint Eastwood', id: randomUUID(), born:1930}) CREATE (RichardH)-[:ACTED_IN {roles:['English Bob']}]->(Unforgiven), (ClintE)-[:ACTED_IN {roles:['Bill Munny']}]->(Unforgiven), @@ -272,10 +272,10 @@ CREATE (ClintE)-[:DIRECTED]->(Unforgiven) CREATE (JohnnyMnemonic:Movie {title:'Johnny Mnemonic', released:1995, tagline:'The hottest data on earth. In the coolest head in town'}) -CREATE (Takeshi:Person {name:'Takeshi Kitano', born:1947}) -CREATE (Dina:Person {name:'Dina Meyer', born:1968}) -CREATE (IceT:Person {name:'Ice-T', born:1958}) -CREATE (RobertL:Person {name:'Robert Longo', born:1953}) +CREATE (Takeshi:Person:Other {name:'Takeshi Kitano', id: randomUUID(), born:1947}) +CREATE (Dina:Person:Other {name:'Dina Meyer', id: randomUUID(), born:1968}) +CREATE (IceT:Person:Other {name:'Ice-T', id: randomUUID(), born:1958}) +CREATE (RobertL:Person:Other {name:'Robert Longo', id: randomUUID(), born:1953}) CREATE (Keanu)-[:ACTED_IN {roles:['Johnny Mnemonic']}]->(JohnnyMnemonic), (Takeshi)-[:ACTED_IN {roles:['Takahashi']}]->(JohnnyMnemonic), @@ -284,9 +284,9 @@ CREATE (RobertL)-[:DIRECTED]->(JohnnyMnemonic) CREATE (CloudAtlas:Movie {title:'Cloud Atlas', released:2012, tagline:'Everything is connected'}) -CREATE (HalleB:Person {name:'Halle Berry', born:1966}) -CREATE (JimB:Person {name:'Jim Broadbent', born:1949}) -CREATE (TomT:Person {name:'Tom Tykwer', born:1965}) +CREATE (HalleB:Person:Other {name:'Halle Berry', id: randomUUID(), born:1966}) +CREATE (JimB:Person:Other {name:'Jim Broadbent', id: randomUUID(), born:1949}) +CREATE (TomT:Person:Other {name:'Tom Tykwer', id: randomUUID(), born:1965}) CREATE (TomH)-[:ACTED_IN {roles:['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(CloudAtlas), (Hugo)-[:ACTED_IN {roles:['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(CloudAtlas), @@ -297,10 +297,10 @@ CREATE (LanaW)-[:DIRECTED]->(CloudAtlas) CREATE (TheDaVinciCode:Movie {title:'The Da Vinci Code', released:2006, tagline:'Break The Codes'}) -CREATE (IanM:Person {name:'Ian McKellen', born:1939}) -CREATE (AudreyT:Person {name:'Audrey Tautou', born:1976}) -CREATE (PaulB:Person {name:'Paul Bettany', born:1971}) -CREATE (RonH:Person {name:'Ron Howard', born:1954}) +CREATE (IanM:Person:Other {name:'Ian McKellen', id: randomUUID(), born:1939}) +CREATE (AudreyT:Person:Other {name:'Audrey Tautou', id: randomUUID(), born:1976}) +CREATE (PaulB:Person:Other {name:'Paul Bettany', id: randomUUID(), born:1971}) +CREATE (RonH:Person:Other {name:'Ron Howard', id: randomUUID(), born:1954}) CREATE (TomH)-[:ACTED_IN {roles:['Dr. Robert Langdon']}]->(TheDaVinciCode), (IanM)-[:ACTED_IN {roles:['Sir Leight Teabing']}]->(TheDaVinciCode), @@ -309,10 +309,10 @@ CREATE (RonH)-[:DIRECTED]->(TheDaVinciCode) CREATE (VforVendetta:Movie {title:'V for Vendetta', released:2006, tagline:'Freedom! Forever!'}) -CREATE (NatalieP:Person {name:'Natalie Portman', born:1981}) -CREATE (StephenR:Person {name:'Stephen Rea', born:1946}) -CREATE (JohnH:Person {name:'John Hurt', born:1940}) -CREATE (BenM:Person {name: 'Ben Miles', born:1967}) +CREATE (NatalieP:Person:Other {name:'Natalie Portman', id: randomUUID(), born:1981}) +CREATE (StephenR:Person:Other {name:'Stephen Rea', id: randomUUID(), born:1946}) +CREATE (JohnH:Person:Other {name:'John Hurt', id: randomUUID(), born:1940}) +CREATE (BenM:Person:Other {name: 'Ben Miles', id: randomUUID(), born:1967}) CREATE (Hugo)-[:ACTED_IN {roles:['V']}]->(VforVendetta), (NatalieP)-[:ACTED_IN {roles:['Evey Hammond']}]->(VforVendetta), @@ -327,12 +327,12 @@ CREATE (LanaW)-[:WROTE]->(VforVendetta) CREATE (SpeedRacer:Movie {title:'Speed Racer', released:2008, tagline:'Speed has no limits'}) -CREATE (EmileH:Person {name:'Emile Hirsch', born:1985}) -CREATE (JohnG:Person {name:'John Goodman', born:1960}) -CREATE (SusanS:Person {name:'Susan Sarandon', born:1946}) -CREATE (MatthewF:Person {name:'Matthew Fox', born:1966}) -CREATE (ChristinaR:Person {name:'Christina Ricci', born:1980}) -CREATE (Rain:Person {name:'Rain', born:1982}) +CREATE (EmileH:Person:Other {name:'Emile Hirsch', id: randomUUID(), born:1985}) +CREATE (JohnG:Person:Other {name:'John Goodman', id: randomUUID(), born:1960}) +CREATE (SusanS:Person:Other {name:'Susan Sarandon', id: randomUUID(), born:1946}) +CREATE (MatthewF:Person:Other {name:'Matthew Fox', id: randomUUID(), born:1966}) +CREATE (ChristinaR:Person:Other {name:'Christina Ricci', id: randomUUID(), born:1980}) +CREATE (Rain:Person:Other {name:'Rain', id: randomUUID(), born:1982}) CREATE (EmileH)-[:ACTED_IN {roles:['Speed Racer']}]->(SpeedRacer), (JohnG)-[:ACTED_IN {roles:['Pops']}]->(SpeedRacer), @@ -348,7 +348,7 @@ CREATE (JoelS)-[:PRODUCED]->(SpeedRacer) CREATE (NinjaAssassin:Movie {title:'Ninja Assassin', released:2009, tagline:'Prepare to enter a secret world of assassins'}) -CREATE (NaomieH:Person {name:'Naomie Harris'}) +CREATE (NaomieH:Person:Other {name:'Naomie Harris'}) CREATE (Rain)-[:ACTED_IN {roles:['Raizo']}]->(NinjaAssassin), (NaomieH)-[:ACTED_IN {roles:['Mika Coretti']}]->(NinjaAssassin), @@ -360,12 +360,12 @@ CREATE (JoelS)-[:PRODUCED]->(NinjaAssassin) CREATE (TheGreenMile:Movie {title:'The Green Mile', released:1999, tagline:"Walk a mile you'll never forget."}) -CREATE (MichaelD:Person {name:'Michael Clarke Duncan', born:1957}) -CREATE (DavidM:Person {name:'David Morse', born:1953}) -CREATE (SamR:Person {name:'Sam Rockwell', born:1968}) -CREATE (GaryS:Person {name:'Gary Sinise', born:1955}) -CREATE (PatriciaC:Person {name:'Patricia Clarkson', born:1959}) -CREATE (FrankD:Person {name:'Frank Darabont', born:1959}) +CREATE (MichaelD:Person:Other {name:'Michael Clarke Duncan', id: randomUUID(), born:1957}) +CREATE (DavidM:Person:Other {name:'David Morse', id: randomUUID(), born:1953}) +CREATE (SamR:Person:Other {name:'Sam Rockwell', id: randomUUID(), born:1968}) +CREATE (GaryS:Person:Other {name:'Gary Sinise', id: randomUUID(), born:1955}) +CREATE (PatriciaC:Person:Other {name:'Patricia Clarkson', id: randomUUID(), born:1959}) +CREATE (FrankD:Person:Other {name:'Frank Darabont', id: randomUUID(), born:1959}) CREATE (TomH)-[:ACTED_IN {roles:['Paul Edgecomb']}]->(TheGreenMile), (MichaelD)-[:ACTED_IN {roles:['John Coffey']}]->(TheGreenMile), @@ -378,9 +378,9 @@ CREATE (FrankD)-[:DIRECTED]->(TheGreenMile) CREATE (FrostNixon:Movie {title:'Frost/Nixon', released:2008, tagline:'400 million people were waiting for the truth.'}) -CREATE (FrankL:Person {name:'Frank Langella', born:1938}) -CREATE (MichaelS:Person {name:'Michael Sheen', born:1969}) -CREATE (OliverP:Person {name:'Oliver Platt', born:1960}) +CREATE (FrankL:Person:Other {name:'Frank Langella', id: randomUUID(), born:1938}) +CREATE (MichaelS:Person:Other {name:'Michael Sheen', id: randomUUID(), born:1969}) +CREATE (OliverP:Person:Other {name:'Oliver Platt', id: randomUUID(), born:1960}) CREATE (FrankL)-[:ACTED_IN {roles:['Richard Nixon']}]->(FrostNixon), (MichaelS)-[:ACTED_IN {roles:['David Frost']}]->(FrostNixon), @@ -390,8 +390,8 @@ CREATE (RonH)-[:DIRECTED]->(FrostNixon) CREATE (Hoffa:Movie {title:'Hoffa', released:1992, tagline:"He didn't want law. He wanted justice."}) -CREATE (DannyD:Person {name:'Danny DeVito', born:1944}) -CREATE (JohnR:Person {name:'John C. Reilly', born:1965}) +CREATE (DannyD:Person:Other {name:'Danny DeVito', id: randomUUID(), born:1944}) +CREATE (JohnR:Person:Other {name:'John C. Reilly', id: randomUUID(), born:1965}) CREATE (JackN)-[:ACTED_IN {roles:['Hoffa']}]->(Hoffa), (DannyD)-[:ACTED_IN {roles:['Robert "Bobby" Ciaro']}]->(Hoffa), @@ -400,8 +400,8 @@ CREATE (DannyD)-[:DIRECTED]->(Hoffa) CREATE (Apollo13:Movie {title:'Apollo 13', released:1995, tagline:'Houston, we have a problem.'}) -CREATE (EdH:Person {name:'Ed Harris', born:1950}) -CREATE (BillPax:Person {name:'Bill Paxton', born:1955}) +CREATE (EdH:Person:Other {name:'Ed Harris', id: randomUUID(), born:1950}) +CREATE (BillPax:Person:Other {name:'Bill Paxton', id: randomUUID(), born:1955}) CREATE (TomH)-[:ACTED_IN {roles:['Jim Lovell']}]->(Apollo13), (KevinB)-[:ACTED_IN {roles:['Jack Swigert']}]->(Apollo13), @@ -411,8 +411,8 @@ CREATE (RonH)-[:DIRECTED]->(Apollo13) CREATE (Twister:Movie {title:'Twister', released:1996, tagline:"Don't Breathe. Don't Look Back."}) -CREATE (PhilipH:Person {name:'Philip Seymour Hoffman', born:1967}) -CREATE (JanB:Person {name:'Jan de Bont', born:1943}) +CREATE (PhilipH:Person:Other {name:'Philip Seymour Hoffman', id: randomUUID(), born:1967}) +CREATE (JanB:Person:Other {name:'Jan de Bont', id: randomUUID(), born:1943}) CREATE (BillPax)-[:ACTED_IN {roles:['Bill Harding']}]->(Twister), (HelenH)-[:ACTED_IN {roles:['Dr. Jo Harding']}]->(Twister), @@ -421,22 +421,22 @@ CREATE (JanB)-[:DIRECTED]->(Twister) CREATE (CastAway:Movie {title:'Cast Away', released:2000, tagline:'At the edge of the world, his journey begins.'}) -CREATE (RobertZ:Person {name:'Robert Zemeckis', born:1951}) +CREATE (RobertZ:Person:Other {name:'Robert Zemeckis', id: randomUUID(), born:1951}) CREATE (TomH)-[:ACTED_IN {roles:['Chuck Noland']}]->(CastAway), (HelenH)-[:ACTED_IN {roles:['Kelly Frears']}]->(CastAway), (RobertZ)-[:DIRECTED]->(CastAway) CREATE (OneFlewOvertheCuckoosNest:Movie {title:"One Flew Over the Cuckoo's Nest", released:1975, tagline:"If he's crazy, what does that make you?"}) -CREATE (MilosF:Person {name:'Milos Forman', born:1932}) +CREATE (MilosF:Person:Other {name:'Milos Forman', id: randomUUID(), born:1932}) CREATE (JackN)-[:ACTED_IN {roles:['Randle McMurphy']}]->(OneFlewOvertheCuckoosNest), (DannyD)-[:ACTED_IN {roles:['Martini']}]->(OneFlewOvertheCuckoosNest), (MilosF)-[:DIRECTED]->(OneFlewOvertheCuckoosNest) CREATE (SomethingsGottaGive:Movie {title:"Something's Gotta Give", released:2003}) -CREATE (DianeK:Person {name:'Diane Keaton', born:1946}) -CREATE (NancyM:Person {name:'Nancy Meyers', born:1949}) +CREATE (DianeK:Person:Other {name:'Diane Keaton', id: randomUUID(), born:1946}) +CREATE (NancyM:Person:Other {name:'Nancy Meyers', id: randomUUID(), born:1949}) CREATE (JackN)-[:ACTED_IN {roles:['Harry Sanborn']}]->(SomethingsGottaGive), (DianeK)-[:ACTED_IN {roles:['Erica Barry']}]->(SomethingsGottaGive), @@ -446,14 +446,14 @@ CREATE (NancyM)-[:WROTE]->(SomethingsGottaGive) CREATE (BicentennialMan:Movie {title:'Bicentennial Man', released:1999, tagline:"One robot's 200 year journey to become an ordinary man."}) -CREATE (ChrisC:Person {name:'Chris Columbus', born:1958}) +CREATE (ChrisC:Person:Other {name:'Chris Columbus', id: randomUUID(), born:1958}) CREATE (Robin)-[:ACTED_IN {roles:['Andrew Marin']}]->(BicentennialMan), (OliverP)-[:ACTED_IN {roles:['Rupert Burns']}]->(BicentennialMan), (ChrisC)-[:DIRECTED]->(BicentennialMan) CREATE (CharlieWilsonsWar:Movie {title:"Charlie Wilson's War", released:2007, tagline:"A stiff drink. A little mascara. A lot of nerve. Who said they couldn't bring down the Soviet empire."}) -CREATE (JuliaR:Person {name:'Julia Roberts', born:1967}) +CREATE (JuliaR:Person:Other {name:'Julia Roberts', id: randomUUID(), born:1967}) CREATE (TomH)-[:ACTED_IN {roles:['Rep. Charlie Wilson']}]->(CharlieWilsonsWar), (JuliaR)-[:ACTED_IN {roles:['Joanne Herring']}]->(CharlieWilsonsWar), @@ -466,10 +466,10 @@ CREATE (RobertZ)-[:DIRECTED]->(ThePolarExpress) CREATE (ALeagueofTheirOwn:Movie {title:'A League of Their Own', released:1992, tagline:'Once in a lifetime you get a chance to do something different.'}) -CREATE (Madonna:Person {name:'Madonna', born:1954}) -CREATE (GeenaD:Person {name:'Geena Davis', born:1956}) -CREATE (LoriP:Person {name:'Lori Petty', born:1963}) -CREATE (PennyM:Person {name:'Penny Marshall', born:1943}) +CREATE (Madonna:Person:Other {name:'Madonna', id: randomUUID(), born:1954}) +CREATE (GeenaD:Person:Other {name:'Geena Davis', id: randomUUID(), born:1956}) +CREATE (LoriP:Person:Other {name:'Lori Petty', id: randomUUID(), born:1963}) +CREATE (PennyM:Person:Other {name:'Penny Marshall', id: randomUUID(), born:1943}) CREATE (TomH)-[:ACTED_IN {roles:['Jimmy Dugan']}]->(ALeagueofTheirOwn), (GeenaD)-[:ACTED_IN {roles:['Dottie Hinson']}]->(ALeagueofTheirOwn), @@ -479,10 +479,10 @@ CREATE (BillPax)-[:ACTED_IN {roles:['Bob Hinson']}]->(ALeagueofTheirOwn), (PennyM)-[:DIRECTED]->(ALeagueofTheirOwn) -CREATE (PaulBlythe:Person {name:'Paul Blythe'}) -CREATE (AngelaScope:Person {name:'Angela Scope'}) -CREATE (JessicaThompson:Person {name:'Jessica Thompson'}) -CREATE (JamesThompson:Person {name:'James Thompson'}) +CREATE (PaulBlythe:Person:Other {name:'Paul Blythe'}) +CREATE (AngelaScope:Person:Other {name:'Angela Scope'}) +CREATE (JessicaThompson:Person:Other {name:'Jessica Thompson'}) +CREATE (JamesThompson:Person:Other {name:'James Thompson'}) CREATE (JamesThompson)-[:FOLLOWS]->(JessicaThompson), diff --git a/core/src/main/java/apoc/cypher/Timeboxed.java b/core/src/main/java/apoc/cypher/Timeboxed.java index 011b76d47..0ac102a11 100644 --- a/core/src/main/java/apoc/cypher/Timeboxed.java +++ b/core/src/main/java/apoc/cypher/Timeboxed.java @@ -2,6 +2,7 @@ import apoc.Pools; import apoc.result.MapResult; +import apoc.util.Util; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Result; import org.neo4j.graphdb.Transaction; @@ -11,6 +12,7 @@ import org.neo4j.procedure.Description; import org.neo4j.procedure.Name; import org.neo4j.procedure.Procedure; +import org.neo4j.procedure.TerminationGuard; import java.util.*; import java.util.concurrent.ArrayBlockingQueue; @@ -36,6 +38,9 @@ public class Timeboxed { @Context public Pools pools; + @Context + public TerminationGuard terminationGuard; + private final static Map POISON = Collections.singletonMap("__magic", "POISON"); @Procedure("apoc.cypher.runTimeboxed") @@ -52,6 +57,12 @@ public Stream runTimeboxed(@Name("statement") String cypher, @Name("p txAtomic.set(innerTx); Result result = innerTx.execute(cypher, params == null ? Collections.EMPTY_MAP : params); while (result.hasNext()) { + if (Util.transactionIsTerminated(terminationGuard)) { + txAtomic.get().close(); + offerToQueue(queue, POISON, timeout); + return; + } + final Map map = result.next(); offerToQueue(queue, map, timeout); } diff --git a/core/src/main/java/apoc/export/csv/CsvFormat.java b/core/src/main/java/apoc/export/csv/CsvFormat.java index 9eb124b42..79c65058e 100644 --- a/core/src/main/java/apoc/export/csv/CsvFormat.java +++ b/core/src/main/java/apoc/export/csv/CsvFormat.java @@ -16,7 +16,7 @@ import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Result; import org.neo4j.graphdb.Transaction; -import org.neo4j.kernel.impl.coreapi.InternalTransaction; +import org.neo4j.procedure.TerminationGuard; import java.io.IOException; import java.io.PrintWriter; @@ -253,6 +253,9 @@ private void writeRow(ExportConfig config, ExportFileManager writer, Set } else { csvWriter.writeNext(headerNode.toArray(new String[headerNode.size()]), false); } + +// terminationGuard.check(); + // todo - here... rows.forEach(row -> csvWriter.writeNext(row.toArray(new String[row.size()]), false)); } catch (IOException e) { throw new RuntimeException(e); diff --git a/core/src/main/java/apoc/load/LoadJson.java b/core/src/main/java/apoc/load/LoadJson.java index 6705e349e..cf8c20271 100644 --- a/core/src/main/java/apoc/load/LoadJson.java +++ b/core/src/main/java/apoc/load/LoadJson.java @@ -4,9 +4,11 @@ import apoc.result.ObjectResult; import apoc.util.CompressionAlgo; import apoc.util.JsonUtil; +import org.neo4j.procedure.Context; import org.neo4j.procedure.Description; import org.neo4j.procedure.Name; import org.neo4j.procedure.Procedure; +import org.neo4j.procedure.TerminationGuard; import java.util.Collections; import java.util.List; @@ -17,6 +19,9 @@ import static apoc.util.CompressionConfig.COMPRESSION; public class LoadJson { + + @Context + public TerminationGuard terminationGuard; @SuppressWarnings("unchecked") @Procedure("apoc.load.jsonArray") @@ -49,7 +54,7 @@ public Stream jsonParams(@Name("urlOrKeyOrBinary") Object urlOrKeyOrB boolean failOnError = (boolean) config.getOrDefault("failOnError", true); String compressionAlgo = (String) config.getOrDefault(COMPRESSION, CompressionAlgo.NONE.name()); List pathOptions = (List) config.get("pathOptions"); - return loadJsonStream(urlOrKeyOrBinary, headers, payload, path, failOnError, compressionAlgo, pathOptions); + return loadJsonStream(urlOrKeyOrBinary, headers, payload, path, failOnError, compressionAlgo, pathOptions, terminationGuard); } } diff --git a/core/src/main/java/apoc/load/Xml.java b/core/src/main/java/apoc/load/Xml.java index e45931403..7e66039bc 100644 --- a/core/src/main/java/apoc/load/Xml.java +++ b/core/src/main/java/apoc/load/Xml.java @@ -8,6 +8,7 @@ import apoc.util.CompressionAlgo; import apoc.util.CompressionConfig; import apoc.util.FileUtils; +import apoc.util.Util; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.neo4j.graphdb.Label; @@ -19,6 +20,7 @@ import org.neo4j.procedure.Mode; import org.neo4j.procedure.Name; import org.neo4j.procedure.Procedure; +import org.neo4j.procedure.TerminationGuard; import org.neo4j.procedure.UserFunction; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; @@ -82,6 +84,9 @@ public class Xml { @Context public Log log; + @Context + public TerminationGuard terminationGuard; + @Procedure("apoc.load.xml") @Description("Loads a single nested map from an XML URL (e.g. web-API).") public Stream xml(@Name("urlOrBinary") Object urlOrBinary, @Name(value = "path", defaultValue = "/") String path, @Name(value = "config",defaultValue = "{}") Map config, @Name(value = "simple", defaultValue = "false") boolean simpleMode) throws Exception { @@ -176,6 +181,7 @@ private XMLStreamReader getXMLStreamReader(Object urlOrBinary, XmlImportConfig c } private void handleNode(Deque> stack, Node node, boolean simpleMode) { + terminationGuard.check(); // Handle document node if (node.getNodeType() == Node.DOCUMENT_NODE) { diff --git a/core/src/test/java/apoc/cypher/CypherTest.java b/core/src/test/java/apoc/cypher/CypherTest.java index 464864f20..f608f0535 100644 --- a/core/src/test/java/apoc/cypher/CypherTest.java +++ b/core/src/test/java/apoc/cypher/CypherTest.java @@ -35,6 +35,9 @@ import static apoc.util.TestUtil.testCallEmpty; import static apoc.util.TestUtil.testFail; import static apoc.util.TestUtil.testResult; +import static apoc.util.TransactionTestUtil.checkTerminationGuard; +import static apoc.util.TransactionTestUtil.checkTransactionNotInList; +import static apoc.util.TransactionTestUtil.terminateTransactionAsync; import static apoc.util.Util.map; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -158,6 +161,43 @@ public void testWithTimeout() { result -> result.hasNext())); } + @Test + public void testWithTermination() { + final String query = "CALL apoc.cypher.runTimeboxed('unwind range (0, 10) as id CALL apoc.util.sleep(2000) return 0', null, 20000)"; + checkTerminationGuard(db, query); + } + + @Test + public void testWithTerminationInnerTransaction() { + final String innerLongQuery = "CALL apoc.util.sleep(10999) RETURN 0"; + final String query = "CALL apoc.cypher.runTimeboxed($innerQuery, null, 99999)"; + + terminateTransactionAsync(db, innerLongQuery); + + final long l = System.currentTimeMillis(); + + // assert query terminated (RETURN 0) + TestUtil.testCall(db, query, + Map.of("innerQuery", innerLongQuery), + row -> assertEquals(Map.of("0", 0L), row.get("value"))); + + final long l1 = System.currentTimeMillis() - l; + System.out.println("l - System.currentTimeMillis() = " + l1); + checkTransactionNotInList(db, query); + } + + @Test + public void testDoItWithTermination() { + final String query = "CALL apoc.cypher.run('unwind range (0, 99) as id CALL apoc.util.sleep(2000) return 0', {})"; + checkTerminationGuard(db, query); + + final String query2 = "CALL apoc.cypher.doIt('CALL apoc.util.sleep(20000) return 0', {})"; + checkTerminationGuard(db, query2); + + final String query3 = "CALL apoc.cypher.doIt('unwind range (0, 9999) as id CREATE (n:Test) return n', {})"; + checkTerminationGuard(db, query3); + } + @Test public void testRunMany() { final Map map = map("name", "John", "name2", "Doe"); diff --git a/core/src/test/java/apoc/export/BigGraphTest.java b/core/src/test/java/apoc/export/BigGraphTest.java new file mode 100644 index 000000000..54462780d --- /dev/null +++ b/core/src/test/java/apoc/export/BigGraphTest.java @@ -0,0 +1,112 @@ +package apoc.export; + +import apoc.export.csv.ExportCSV; +import apoc.export.csv.ImportCsv; +import apoc.export.cypher.ExportCypher; +import apoc.export.graphml.ExportGraphML; +import apoc.export.json.ExportJson; +import apoc.graph.Graphs; +import apoc.meta.Meta; +import apoc.refactor.GraphRefactoring; +import apoc.refactor.rename.Rename; +import apoc.util.TestUtil; +import apoc.util.Util; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.neo4j.configuration.GraphDatabaseSettings; +import org.neo4j.graphdb.Node; +import org.neo4j.test.rule.DbmsRule; +import org.neo4j.test.rule.ImpermanentDbmsRule; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import static apoc.ApocConfig.APOC_EXPORT_FILE_ENABLED; +import static apoc.ApocConfig.APOC_IMPORT_FILE_ENABLED; +import static apoc.ApocConfig.apocConfig; +import static apoc.util.TransactionTestUtil.checkTerminationGuard; +import static org.neo4j.configuration.GraphDatabaseSettings.TransactionStateMemoryAllocation.OFF_HEAP; +import static org.neo4j.configuration.SettingValueParsers.BYTES; + +public class BigGraphTest { + private static File directory = new File("target/import"); + static { //noinspection ResultOfMethodCallIgnored + directory.mkdirs(); + } + + @ClassRule + public static DbmsRule db = new ImpermanentDbmsRule() + .withSetting(GraphDatabaseSettings.memory_tracking, true) + .withSetting(GraphDatabaseSettings.tx_state_memory_allocation, OFF_HEAP) + .withSetting(GraphDatabaseSettings.tx_state_max_off_heap_memory, BYTES.parse("500m")) + .withSetting(GraphDatabaseSettings.load_csv_file_url_root, directory.toPath().toAbsolutePath()); + + @BeforeClass + public static void setUp() throws Exception { + TestUtil.registerProcedure(db, Rename.class, ExportCSV.class, ExportJson.class, ExportCypher.class, ExportGraphML.class, Graphs.class, Meta.class, ImportCsv.class, GraphRefactoring.class); + apocConfig().setProperty(APOC_IMPORT_FILE_ENABLED, true); + apocConfig().setProperty(APOC_EXPORT_FILE_ENABLED, true); + + final String query = Util.readResourceFile("movies.cypher"); + IntStream.range(0, 20000).forEach(__-> db.executeTransactionally(query)); + } + + @Test + public void testTerminateExportCsv() { + checkTerminationGuard(db, "CALL apoc.export.csv.all('testTerminate.csv',{})"); + } + + @Test + public void testTerminateExportGraphMl() { + checkTerminationGuard(db, "CALL apoc.export.graphml.all('testTerminate.graphml', null)"); + } + + @Test + public void testTerminateExportCypher() { + checkTerminationGuard(db, "CALL apoc.export.cypher.all('testTerminate.cypher',{})"); + } + + @Test + public void testTerminateExportJson() { + checkTerminationGuard(db, "CALL apoc.export.json.all('testTerminate.json',{})"); + } + + @Test + public void testTerminateRenameNodeProp() { + checkTerminationGuard(db, "CALL apoc.refactor.rename.nodeProperty('name', 'nameTwo')"); + } + + @Test + public void testTerminateRenameTypeProp() { + checkTerminationGuard(db, "CALL apoc.refactor.rename.typeProperty('roles', 'rolesTwo')"); + } + + @Test + public void testTerminateRenameType() { + checkTerminationGuard(db, "CALL apoc.refactor.rename.type('DIRECTED', 'DIRECTED_TWO')"); + } + + @Test + public void testTerminateRenameLabel() { + checkTerminationGuard(db, "CALL apoc.refactor.rename.label('Other', 'OtherTwo')"); + } + + @Test + public void testTerminateRefactorProcs() { + List nodes = db.executeTransactionally("MATCH (n:Person) RETURN collect(n) as nodes", Collections.emptyMap(), + r -> r.>columnAs("nodes").next()); + + checkTerminationGuard(db, "CALL apoc.refactor.cloneNodes($nodes)", + Map.of("nodes", nodes)); + + checkTerminationGuard(db, "CALL apoc.refactor.cloneSubgraph($nodes)", + Map.of("nodes", nodes)); + + db.executeTransactionally("CREATE CONSTRAINT FOR (n:BornLabel) REQUIRE n.targetKey IS UNIQUE"); + checkTerminationGuard(db, "CALL apoc.refactor.categorize('id', 'SOMETHING', true, 'BornLabel', 'targetKey', [], 1)"); + } +} diff --git a/core/src/test/java/apoc/export/csv/ExportCsvTest.java b/core/src/test/java/apoc/export/csv/ExportCsvTest.java index 17578c919..5e68ec6e3 100644 --- a/core/src/test/java/apoc/export/csv/ExportCsvTest.java +++ b/core/src/test/java/apoc/export/csv/ExportCsvTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.IntStream; import static apoc.ApocConfig.APOC_EXPORT_FILE_ENABLED; import static apoc.ApocConfig.APOC_IMPORT_FILE_ENABLED; @@ -34,7 +35,9 @@ import static apoc.util.CompressionAlgo.GZIP; import static apoc.util.CompressionAlgo.NONE; import static apoc.util.MapUtil.map; +import static apoc.util.TestUtil.testCall; import static apoc.util.TestUtil.testResult; +import static apoc.util.TransactionTestUtil.checkTerminationGuard; import static java.nio.charset.StandardCharsets.UTF_8; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertArrayEquals; @@ -142,6 +145,30 @@ private String readFile(String fileName) { private String readFile(String fileName, Charset charset, CompressionAlgo compression) { return BinaryTestUtil.readFileToString(new File(directory, fileName), charset, compression); } + + // todo - db.executeTransactionally(Util.readResourceFile("movies.cypher")); - basta questo? + @Test + public void testExportInvalidQuoteValue1() throws Exception { + final String query = Util.readResourceFile("movies.cypher"); + IntStream.range(0, 3999).forEach(__-> db.executeTransactionally(query)); + String fileName = "allEEEE.csv"; + System.out.println("im here"); + final long l = System.currentTimeMillis(); + testCall(db, "CALL apoc.export.csv.all($file,{})", Map.of("file", fileName), r -> { + System.out.println("r" + r.values()); + }); + System.out.println("time=" + (System.currentTimeMillis() - l)); +// checkTerminationGuard(db, "CALL apoc.export.csv.all($file,{})", Map.of("file", fileName)); +// try { +// TestUtil.testCall(db, "CALL apoc.export.csv.all($file,{})", +// map("file", fileName), +// (r) -> assertResults(fileName, r, "database")); +// fail(); +// } catch (RuntimeException e) { +// final String expectedMessage = "Failed to invoke procedure `apoc.export.csv.all`: Caused by: java.lang.RuntimeException: The string value of the field quote is not valid"; +// assertEquals(expectedMessage, e.getMessage()); +// } + } @Test public void testExportInvalidQuoteValue() { diff --git a/core/src/test/java/apoc/export/csv/ImportCsvTest.java b/core/src/test/java/apoc/export/csv/ImportCsvTest.java index d879d19b1..fa38544ee 100644 --- a/core/src/test/java/apoc/export/csv/ImportCsvTest.java +++ b/core/src/test/java/apoc/export/csv/ImportCsvTest.java @@ -43,6 +43,7 @@ import static apoc.util.BinaryTestUtil.fileToBinary; import static apoc.util.CompressionConfig.COMPRESSION; import static apoc.util.MapUtil.map; +import static apoc.util.TransactionTestUtil.checkTerminationGuard; import static java.util.Arrays.asList; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -185,6 +186,16 @@ public void testImportCsvLargeFile() { "config", map("batchSize", 100L)), (r) -> assertEquals(664850L, r.get("nodes"))); } + + @Test + public void testImportCsvTerminate() { + // todo - implementare +// checkTerminationGuard(db, ); + TestUtil.testCall(db, "CALL apoc.import.csv([{fileName: $nodeFile, labels: ['Person']}], [], $config)", + map("nodeFile", "file:/largeFile.csv", + "config", map("batchSize", 100L)), + (r) -> assertEquals(664850L, r.get("nodes"))); + } @Test public void testNodesWithIds() { diff --git a/core/src/test/java/apoc/export/json/ImportJsonTest.java b/core/src/test/java/apoc/export/json/ImportJsonTest.java index 3eb1d5a59..cd3eb6043 100644 --- a/core/src/test/java/apoc/export/json/ImportJsonTest.java +++ b/core/src/test/java/apoc/export/json/ImportJsonTest.java @@ -4,6 +4,7 @@ import apoc.util.CompressionAlgo; import apoc.util.JsonUtil; import apoc.util.TestUtil; +import apoc.util.TransactionTestUtil; import apoc.util.Util; import apoc.util.collection.Iterables; import junit.framework.TestCase; @@ -17,7 +18,6 @@ import org.neo4j.graphdb.Label; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; -import org.neo4j.graphdb.ResourceIterator; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.spatial.Point; import org.neo4j.test.rule.DbmsRule; @@ -34,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import static apoc.ApocConfig.APOC_IMPORT_FILE_ENABLED; @@ -196,31 +195,8 @@ public void shouldTerminateImportWhenTransactionIsTimedOut() { String filename = "https://devrel-data-science.s3.us-east-2.amazonaws.com/twitch_all.json"; final String query = "CALL apoc.import.json($file)"; - new Thread(() -> db.executeTransactionally(query, map("file", filename))).start(); - // waiting for 'apoc.import.json() query to cancel when it is found - final String transactionId = TestUtil.singleResultFirstColumn(db, - "SHOW TRANSACTIONS YIELD currentQuery, transactionId WHERE currentQuery = $query RETURN transactionId", - map("query", query)); - - assertEventually(() -> db.executeTransactionally("TERMINATE TRANSACTION $transactionId", - map("transactionId", transactionId), - result -> { - final ResourceIterator msgIterator = result.columnAs("message"); - return msgIterator.hasNext() && msgIterator.next().equals("Transaction terminated."); - }), (value) -> value, 10L, TimeUnit.SECONDS); - - // checking for query cancellation - assertEventually(() -> { - final String transactionListCommand = "SHOW TRANSACTIONS"; - return db.executeTransactionally(transactionListCommand, - map("query", query), - result -> { - final ResourceIterator queryIterator = result.columnAs("currentQuery"); - final String first = queryIterator.next(); - return first.equals(transactionListCommand) && !queryIterator.hasNext(); - } ); - }, (value) -> value, 10L, TimeUnit.SECONDS); + TransactionTestUtil.checkTerminationGuard(db, query, map("file", filename)); } @Test diff --git a/core/src/test/java/apoc/load/LoadJsonTest.java b/core/src/test/java/apoc/load/LoadJsonTest.java index f3f2536a0..d2f945fa3 100644 --- a/core/src/test/java/apoc/load/LoadJsonTest.java +++ b/core/src/test/java/apoc/load/LoadJsonTest.java @@ -3,6 +3,7 @@ import apoc.util.CompressionAlgo; import apoc.util.JsonUtil; import apoc.util.TestUtil; +import apoc.util.TransactionTestUtil; import apoc.util.Util; import apoc.util.collection.Iterators; import org.apache.commons.lang.exception.ExceptionUtils; @@ -13,6 +14,7 @@ import org.mockserver.model.Header; import org.neo4j.configuration.GraphDatabaseInternalSettings; +import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.graphdb.Result; import org.neo4j.test.rule.DbmsRule; @@ -44,6 +46,8 @@ import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.neo4j.configuration.GraphDatabaseSettings.TransactionStateMemoryAllocation.OFF_HEAP; +import static org.neo4j.configuration.SettingValueParsers.BYTES; public class LoadJsonTest { @@ -61,6 +65,9 @@ public static void stopServer() { @Rule public DbmsRule db = new ImpermanentDbmsRule() + .withSetting(GraphDatabaseSettings.memory_tracking, true) + .withSetting(GraphDatabaseSettings.tx_state_memory_allocation, OFF_HEAP) + .withSetting(GraphDatabaseSettings.tx_state_max_off_heap_memory, BYTES.parse("8G")) .withSetting(GraphDatabaseInternalSettings.cypher_ip_blocklist, List.of(new IPAddressString("127.168.0.0/8"))); @Before public void setUp() { @@ -412,4 +419,10 @@ public void testLoadJsonParams() { assertFalse("value should be not empty", value.isEmpty()); }); } + + @Test + public void shouldTerminateLoadWhenTransactionIsTimedOut() { + final String query = "CALL apoc.load.json('https://github.com/knowitall/yelp-dataset-challenge/blob/master/data/yelp_phoenix_academic_dataset/yelp_academic_dataset_review.json?raw=truehttps://github.com/knowitall/yelp-dataset-challenge/blob/master/data/yelp_phoenix_academic_dataset/yelp_academic_dataset_review.json?raw=true')"; + TransactionTestUtil.checkTerminationGuard(db, query); + } } diff --git a/core/src/test/java/apoc/load/XmlTest.java b/core/src/test/java/apoc/load/XmlTest.java index 71a75095e..3d18982b3 100644 --- a/core/src/test/java/apoc/load/XmlTest.java +++ b/core/src/test/java/apoc/load/XmlTest.java @@ -2,6 +2,7 @@ import apoc.util.CompressionAlgo; import apoc.util.TestUtil; +import apoc.util.TransactionTestUtil; import apoc.util.collection.Iterables; import apoc.util.collection.Iterators; import apoc.xml.XmlTestUtils; @@ -62,6 +63,14 @@ public void testLoadXml() { }); } + @Test + public void testTerminateLoadXml() { + testCall(db, "CALL apoc.load.xml('file:databases.xml')", // YIELD value RETURN value + (row) -> { + assertEquals(XmlTestUtils.XML_AS_NESTED_MAP, row.get("value")); + }); + } + @Test public void testLoadXmlAsStream() { testResult(db, "CALL apoc.load.xml('file:databases.xml', '/parent/child')", // YIELD value RETURN value @@ -549,4 +558,14 @@ public void testImportXmlPreventBillionLaughsVulnerabilityThrowsQueryExecutionEx assertTrue(except instanceof RuntimeException); assertEquals(except.getMessage(), "XML documents with a DOCTYPE are not allowed."); } + + @Test + public void testParseWithXPath222() throws Exception { + // todo - testare senza guard + final String file = ClassLoader.getSystemResource("largeFile.graphml").toString(); + TransactionTestUtil.checkTerminationGuard(db, "call apoc.load.xml($file)", Map.of("file", file)); +// testCall(db, "RETURN apoc.xml.parse($xmlString, '/catalog/book[title=\"Maeve Ascendant\"]/.') AS result", +// map("xmlString", xmlString), +// (r) -> assertEquals(XmlTestUtils.XML_XPATH_AS_NESTED_MAP, r.get("result"))); + } } diff --git a/core/src/test/java/apoc/periodic/PeriodicTest.java b/core/src/test/java/apoc/periodic/PeriodicTest.java index b3c313524..e032ce9e4 100644 --- a/core/src/test/java/apoc/periodic/PeriodicTest.java +++ b/core/src/test/java/apoc/periodic/PeriodicTest.java @@ -4,6 +4,7 @@ import apoc.schema.Schemas; import apoc.util.MapUtil; import apoc.util.TestUtil; +import apoc.util.Utils; import apoc.util.collection.Iterators; import org.apache.commons.lang.exception.ExceptionUtils; import org.junit.Before; @@ -13,6 +14,8 @@ import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.graphdb.Result; import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.TransactionStatusFailureException; +import org.neo4j.graphdb.TransactionTerminatedException; import org.neo4j.graphdb.TransientTransactionFailureException; import org.neo4j.graphdb.schema.ConstraintDefinition; import org.neo4j.graphdb.schema.IndexDefinition; @@ -34,6 +37,8 @@ import static apoc.periodic.Periodic.applyPlanner; import static apoc.util.TestUtil.testCall; import static apoc.util.TestUtil.testResult; +import static apoc.util.TransactionTestUtil.checkTransactionNotInList; +import static apoc.util.TransactionTestUtil.terminateTransactionAsync; import static apoc.util.Util.map; import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; @@ -221,6 +226,42 @@ public void testTerminateIterate() { PeriodicTestUtils.testTerminatePeriodicQuery(db, "CALL apoc.periodic.iterate('UNWIND range(0,1000) as id RETURN id', 'WITH $id as id CREATE (:Foo {id: $id})', {batchSize:10,iterateList:false})"); } + @Test + public void testTerminateIterateWithTerminateTransactionCommand() { + // apoc.periodic.iterate + PeriodicTestUtils.testTerminateWithCommand(db, "CALL apoc.periodic.iterate('UNWIND range(0,9999999) as id RETURN id', 'WITH $id as id CREATE (:Foo {id: $id})', {batchSize:1,parallel:true})"); + PeriodicTestUtils.testTerminateWithCommand(db, "CALL apoc.periodic.iterate('UNWIND range(0,9999999) as id RETURN id', 'WITH $id as id CREATE (:Foo {id: $id})', {batchSize:10,iterateList:true})"); + PeriodicTestUtils.testTerminateWithCommand(db, "CALL apoc.periodic.iterate('UNWIND range(0,9999999) as id RETURN id', 'WITH $id as id CREATE (:Foo {id: $id})', {batchSize:10,iterateList:false})"); + PeriodicTestUtils.testTerminateWithCommand(db, "CALL apoc.periodic.iterate('CALL apoc.util.sleep(19999) RETURN 1 as id', 'WITH $id as id CREATE (:Foo {id: $id})', {batchSize:10,iterateList:false})"); + + // apoc.periodic.commit + PeriodicTestUtils.testTerminateWithCommand(db, "CALL apoc.periodic.commit('UNWIND range(0,999999) as id WITH id CREATE (n:Foo {id: id}) RETURN n limit 1000', {})"); + } + + @Test + public void testWithTerminationInnerTransaction() { + // terminating the apoc.util.sleep should instantly terminate the periodic query without any creation + final String innerLongQuery = "CALL apoc.util.sleep(99999) RETURN 0"; + final String query = "CALL apoc.periodic.iterate($innerQuery, 'WITH $id as id CREATE (:Foo {id: $id})', {params: {innerQuery: $innerQuery}})"; + + terminateTransactionAsync(db, innerLongQuery); + + // assert query terminated (RETURN 0 nodesCreated) + try { + TestUtil.testCall(db, query, + Map.of("innerQuery", innerLongQuery), + row -> { + final Object actual = ((Map) row.get("updateStatistics")).get("nodesCreated"); + assertEquals(0L, actual); + }); + fail("Should have terminated"); + } catch (TransactionStatusFailureException e) { + assertEquals("Unable to complete transaction.: Explicitly terminated by the user.", e.getMessage()); + } + + checkTransactionNotInList(db, query); + } + /** * test for https://github.com/neo4j-contrib/neo4j-apoc-procedures/issues/1314 * note that this test might depend on timings on your machine diff --git a/test-utils/src/main/java/apoc/periodic/PeriodicTestUtils.java b/test-utils/src/main/java/apoc/periodic/PeriodicTestUtils.java index 423eb16d9..05463015a 100644 --- a/test-utils/src/main/java/apoc/periodic/PeriodicTestUtils.java +++ b/test-utils/src/main/java/apoc/periodic/PeriodicTestUtils.java @@ -1,5 +1,6 @@ package apoc.periodic; +import apoc.util.TransactionTestUtil; import apoc.util.collection.Iterators; import java.util.concurrent.TimeUnit; import org.neo4j.common.DependencyResolver; @@ -41,8 +42,19 @@ public static boolean terminateQuery(String pattern, GraphDatabaseAPI db) { return numberOfKilledTransactions > 0; } + // todo - we could get rid of this method (and related code), and leverage the testTerminateWithCommand public static void testTerminatePeriodicQuery(DbmsRule db, String periodicQuery) { killPeriodicQueryAsync(db); + checkPeriodicTerminated(db, periodicQuery); + } + + public static void testTerminateWithCommand(DbmsRule db, String periodicQuery) { + TransactionTestUtil.terminateTransactionAsync(db, periodicQuery); + checkPeriodicTerminated(db, periodicQuery); + TransactionTestUtil.checkTransactionNotInList(db, periodicQuery); + } + + private static void checkPeriodicTerminated(DbmsRule db, String periodicQuery) { try { org.neo4j.test.assertion.Assert.assertEventually( () -> db.executeTransactionally(periodicQuery, Map.of(), diff --git a/test-utils/src/main/java/apoc/util/TransactionTestUtil.java b/test-utils/src/main/java/apoc/util/TransactionTestUtil.java new file mode 100644 index 000000000..0d3ecbe90 --- /dev/null +++ b/test-utils/src/main/java/apoc/util/TransactionTestUtil.java @@ -0,0 +1,96 @@ +package apoc.util; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.QueryExecutionException; +import org.neo4j.graphdb.ResourceIterator; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.TransactionTerminatedException; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static apoc.util.MapUtil.map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.neo4j.test.assertion.Assert.assertEventually; + +public class TransactionTestUtil { + public static final String TRANSACTION_LIST = "SHOW TRANSACTIONS"; + + public static void checkTerminationGuard(GraphDatabaseService db, String query) { + checkTerminationGuard(db, query, Collections.emptyMap()); + } + + public static void checkTerminationGuard(GraphDatabaseService db, String query, Map params) { + terminateTransactionAsync(db, query); + + // check that the procedure/function fails with TransactionFailureException when transaction is terminated + // todo 5, TimeUnit.SECONDS as parameter + +// db.executeTransactionally(query, params, r -> r.resultAsString()); + + final long l = System.currentTimeMillis(); + try(Transaction transaction = db.beginTx(5, TimeUnit.SECONDS)) { + transaction.execute(query, params).resultAsString(); +// System.out.println("s = " + s); + transaction.commit(); + fail("Should fail because of TransactionFailureException");// todo - necessary this row? fails with timeboxed + } catch (Exception e) { + final Throwable rootCause = ExceptionUtils.getRootCause(e); + System.out.println("TransactionTestUtil.checkTerminationGuard"); + final String expected = "The transaction has been terminated. " + + "Retry your operation in a new transaction, and you should see a successful result. Explicitly terminated by the user. "; + assertEquals(expected, rootCause.getMessage()); + } + final long l1 = System.currentTimeMillis() - l; + System.out.println("l - System.currentTimeMillis() = " + l1); + + checkTransactionNotInList(db, query); + } + + public static void checkTransactionNotInList(GraphDatabaseService db, String query) { + // checking for query cancellation from transaction list command + TestUtil.testResult(db, TRANSACTION_LIST, + map("query", query), + result -> { + final boolean currentQuery = result.columnAs("currentQuery") + .stream() + .noneMatch(currQuery -> currQuery.equals(query)); + assertTrue(currentQuery); + }); + } + + public static void terminateTransactionAsync(GraphDatabaseService db, String query) { + new Thread(() -> { + System.out.println("TransactionTestUtil.terminateAndCheckTransaction"); + // waiting for apoc query to cancel when it is found + final String[] transactionId = new String[1]; + + assertEventually(() -> db.executeTransactionally(TRANSACTION_LIST + " YIELD currentQuery, transactionId " + + "WHERE currentQuery CONTAINS $query AND NOT currentQuery STARTS WITH $transactionList " + + "RETURN transactionId", + map("query", query, "transactionList", TRANSACTION_LIST), + result -> { + final ResourceIterator msgIterator = result.columnAs("transactionId"); + if (!msgIterator.hasNext()) { + return false; + } + transactionId[0] = msgIterator.next(); + return transactionId[0] != null; + }), (value) -> value, 5L, TimeUnit.SECONDS); + + // todo - delete + System.out.println("transactionId = " + transactionId[0]); + final long l = System.currentTimeMillis(); + TestUtil.testCall(db, "TERMINATE TRANSACTION $transactionId", + map("transactionId", transactionId[0]), + result -> assertEquals("Transaction terminated.", result.get("message"))); + // todo - delete + System.out.println("time=" + (System.currentTimeMillis() - l)); + }).start(); + + } +}