<commit_before>/**
 * Copyright (c) 2002-2013 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.cypher.docgen

import org.neo4j.cypher.{MergeConstraintConflictException, StatisticsChecker}
import org.junit.{Before, Test}
import org.neo4j.visualization.graphviz.GraphStyle
import org.neo4j.visualization.graphviz.AsciiDocSimpleStyle
import org.neo4j.graphdb.DynamicLabel

class MergeTest extends DocumentingTestBase with StatisticsChecker {

  override protected def getGraphvizStyle: GraphStyle = 
    AsciiDocSimpleStyle.withAutomaticRelationshipTypeColors()

  def section = "Merge"

  override def graphDescription = List(
    "Charlie:Person ACTED_IN WallStreet:Movie",
    "Martin:Person ACTED_IN WallStreet:Movie",
    "Michael:Person ACTED_IN WallStreet:Movie",
    "Martin:Person ACTED_IN TheAmericanPresident:Movie",
    "Michael:Person ACTED_IN TheAmericanPresident:Movie",
    "Oliver:Person DIRECTED WallStreet:Movie",
    "Rob:Person DIRECTED TheAmericanPresident:Movie",
    "Charlie:Person FATHER Martin:Person")

  override val properties = Map(
    "Charlie" -> Map("name" -> "<NAME>"),
    "Oliver" -> Map("name" -> "<NAME>"),
    "Michael" -> Map("name" -> "<NAME>"),
    "Rob" -> Map("name" -> "<NAME>"),
    "Martin" -> Map("name" -> "<NAME>"),
    "WallStreet" -> Map("title" -> "Wall Street"),
    "TheAmericanPresident" -> Map("title" -> "The American President")
  )

  override val setupContraintQueries = List(
    "CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS UNIQUE",
    "CREATE CONSTRAINT ON (n:Person) ASSERT n.role IS UNIQUE")

  @Test def merge_single_node_with_label() {
    testQuery(
      title = "Merge single node with a label",
      text = "Merging a single node with a given label.",
      queryText = "merge (robert:Critic)\nreturn robert, labels(robert)",
      returns = "Because there are no nodes labeled +Critic+ in the database, a new node is created.",
      assertions = (p) => assertStats(p, nodesCreated = 1, labelsAdded = 1)
    )
  }

  @Test def merge_single_node_with_properties() {
    testQuery(
      title = "Merge single node with properties",
      text = "Merging a single node with properties where not all properties match any existing node.",
      queryText = "merge (charlie {name:'<NAME>', age:10})\nreturn charlie",
      returns = "A new node with the name <NAME> will be created since not all properties " +
        "matched the existing Charlie Sheen node.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2)
    )
  }

  @Test def merge_single_node_with_label_and_property() {
    testQuery(
      title = "Merge single node specifying both label and property",
      text = "Merging a single node with both label and property matching an existing node.",
      queryText = "merge (michael:Person {name:'<NAME>'})\nreturn michael",
      returns = "<NAME> will be matched and returned.",
      assertions = (p) => assertStats(p, nodesCreated = 0, propertiesSet = 0)
    )
  }

  @Test def merge_node_and_set_property_on_creation() {
    testQuery(
      title = "Merge with ON CREATE",
      text = "Merge a node and set properties if the node needs to be created.",
      queryText = """merge (keanu:Person {name:'<NAME>'})
on create set keanu.created = timestamp()
return keanu""",
      returns = "Creates the Keanu node, and sets a timestamp on creation time.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_set_property_on_match() {
    testQuery(
      title = "Merge with ON MATCH",
      text = "Merging nodes and setting properties on found nodes.",
      queryText = "merge (person:Person)\non match set person.found = true\nreturn person",
      returns = "Finds all the +Person+ nodes, sets a property on them, and returns them.",
      assertions = (p) => assertStats(p, propertiesSet = 5)
    )
  }

  @Test def merge_node_and_set_property_on_creation_or_update_prop() {
    testQuery(
      title = "Merge with ON CREATE and ON MATCH",
      text = "Merge a node and set properties if the node needs to be created.",
      queryText =
        """merge (keanu:Person {name:'<NAME>'})
on create set keanu.created = timestamp()
on match set keanu.lastSeen = timestamp()
return keanu""",
      returns = "The query creates the Keanu node, and sets a timestamp on creation time. If Keanu already existed, a " +
        "different property would have been set.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_create_with_unique_constraint() {
    testQuery(
      title = "Merge using unique constraints creates a new node if no node is found",
      text = "Merge using unique constraints creates a new node if no node is found.",
      queryText = """merge (laurence:Person {name: '<NAME>'}) return laurence""",
      returns = "The query creates the laurence node. If laurence already existed, merge would " +
        "just return the existing node.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 1, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_match_with_unique_constraint() {
    testQuery(
      title = "Merge using unique constraints matches an existing node",
      text = "Merge using unique constraints matches an existing node.",
      queryText = """merge (oliver:Person {name:'<NAME>'}) return oliver""",
      returns = "The oliver node already exists, so merge just returns it.",
      assertions = (p) => assertStats(p, nodesCreated = 0, propertiesSet = 0, labelsAdded = 0)
    )
  }

  @Test def merge_node_and_match_many_with_unique_constraint_fails_for_partial_matches() {
    generateConsole = false
    testFailingQuery[MergeConstraintConflictException](
      title = "Merge with unique constraints and partial matches",
      text = "Merge using unique constraints fails when finding partial matches.",
      queryText = """merge (michael:Person {name:'<NAME>', role:'<NAME>'}) return michael""",
      returns = "While there is a matching unique michael node with the name '<NAME>', there is no " +
        "unique node with the role of 'Gordon Gekko' and merge fails to match."
    )
  }

  @Test def merge_node_and_match_many_with_unique_constraint_fails_for_conflicting_matches() {
    generateConsole = false
    testFailingQuery[MergeConstraintConflictException](
      title = "Merge with unique constraints and conflicting matches",
      text = "Merge using unique constraints fails when finding conflicting matches.",
      queryText = """merge (oliver:Person {name:'<NAME>', role:'<NAME>'}) return oliver""",
      returns = "While there is a matching unique oliver node with the name '<NAME>', there is also another " +
        "unique node with the role of 'Gordon Gekko' and merge fails to match."
    )
  }

  @Test def using_map_parameters_with_merge() {
    prepareAndTestQuery(
      title = "Using map parameters with MERGE",
      text = """+MERGE+ does not support map parameters like for example +CREATE+ does.
To use map parameters with +MERGE+, it is necessary to explicitly use the expected properties, like in the following example.
For more information on parameters, see <<cypher-parameters>>.""",
      prepare = setParameters(Map("param" -> Map("name" -> "<NAME>", "role" -> "Neo"))),
      queryText = "merge (oliver:Person {name:{param}.name, role:{param}.role}) return oliver",
      returns = "",
      assertions = p => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merging_on_a_single_relationship() {
    testQuery(
      title = "Merge on a relationship",
      text = "+MERGE+ can be used to match or create a relationship.",
      queryText =
        """match (charlie:Person {name:'<NAME>'}), (wallStreet:Movie {title:'Wall Street'})
merge (charlie)-[r:ACTED_IN]->(wallStreet)
return r""",
      returns = "<NAME> had already been marked as acting on Wall Street, so the existing relationship is found and returned",
      assertions = (p) => assertStats(p, relationshipsCreated = 0)
    )
  }

  @Test def merging_on_a_longer_pattern() {
    testQuery(
      title = "Merge on multiple relationships",
      text = "When +MERGE+ is used on a whole pattern, either everything matches, or everything is created.",
      queryText =
        """match (oliver:Person {name:'<NAME>'}), (reiner:Person {name:'<NAME>'})
merge (oliver)-[:DIRECTED]->(movie:Movie)<-[:ACTED_IN]-(reiner)
return movie""",
      returns = "In our example graph, <NAME> and <NAME> have never worked together. When we try to +MERGE+ a " +
        "movie between them, Cypher will not use any of the existing movies already connected to either person. Instead, " +
        "a new movie node is created.",
      assertions = (p) => assertStats(p, relationshipsCreated = 2, nodesCreated = 1, propertiesSet = 0, labelsAdded = 1)
    )
  }<commit_msg>Cypher-docs: note requirement of a bound node when creating relationships with MERGE clause.<commit_after>/**
 * Copyright (c) 2002-2013 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.cypher.docgen

import org.neo4j.cypher.{MergeConstraintConflictException, StatisticsChecker}
import org.junit.{Before, Test}
import org.neo4j.visualization.graphviz.GraphStyle
import org.neo4j.visualization.graphviz.AsciiDocSimpleStyle
import org.neo4j.graphdb.DynamicLabel

class MergeTest extends DocumentingTestBase with StatisticsChecker {

  override protected def getGraphvizStyle: GraphStyle = 
    AsciiDocSimpleStyle.withAutomaticRelationshipTypeColors()

  def section = "Merge"

  override def graphDescription = List(
    "Charlie:Person ACTED_IN WallStreet:Movie",
    "Martin:Person ACTED_IN WallStreet:Movie",
    "Michael:Person ACTED_IN WallStreet:Movie",
    "Martin:Person ACTED_IN TheAmericanPresident:Movie",
    "Michael:Person ACTED_IN TheAmericanPresident:Movie",
    "Oliver:Person DIRECTED WallStreet:Movie",
    "Rob:Person DIRECTED TheAmericanPresident:Movie",
    "Charlie:Person FATHER Martin:Person")

  override val properties = Map(
    "Charlie" -> Map("name" -> "<NAME>"),
    "Oliver" -> Map("name" -> "<NAME>"),
    "Michael" -> Map("name" -> "<NAME>"),
    "Rob" -> Map("name" -> "<NAME>"),
    "Martin" -> Map("name" -> "<NAME>"),
    "WallStreet" -> Map("title" -> "Wall Street"),
    "TheAmericanPresident" -> Map("title" -> "The American President")
  )

  override val setupContraintQueries = List(
    "CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS UNIQUE",
    "CREATE CONSTRAINT ON (n:Person) ASSERT n.role IS UNIQUE")

  @Test def merge_single_node_with_label() {
    testQuery(
      title = "Merge single node with a label",
      text = "Merging a single node with a given label.",
      queryText = "merge (robert:Critic)\nreturn robert, labels(robert)",
      returns = "Because there are no nodes labeled +Critic+ in the database, a new node is created.",
      assertions = (p) => assertStats(p, nodesCreated = 1, labelsAdded = 1)
    )
  }

  @Test def merge_single_node_with_properties() {
    testQuery(
      title = "Merge single node with properties",
      text = "Merging a single node with properties where not all properties match any existing node.",
      queryText = "merge (charlie {name:'<NAME>', age:10})\nreturn charlie",
      returns = "A new node with the name <NAME> will be created since not all properties " +
        "matched the existing Charlie Sheen node.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2)
    )
  }

  @Test def merge_single_node_with_label_and_property() {
    testQuery(
      title = "Merge single node specifying both label and property",
      text = "Merging a single node with both label and property matching an existing node.",
      queryText = "merge (michael:Person {name:'<NAME>'})\nreturn michael",
      returns = "<NAME> will be matched and returned.",
      assertions = (p) => assertStats(p, nodesCreated = 0, propertiesSet = 0)
    )
  }

  @Test def merge_node_and_set_property_on_creation() {
    testQuery(
      title = "Merge with ON CREATE",
      text = "Merge a node and set properties if the node needs to be created.",
      queryText = """merge (keanu:Person {name:'<NAME>'})
on create set keanu.created = timestamp()
return keanu""",
      returns = "Creates the Keanu node, and sets a timestamp on creation time.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_set_property_on_match() {
    testQuery(
      title = "Merge with ON MATCH",
      text = "Merging nodes and setting properties on found nodes.",
      queryText = "merge (person:Person)\non match set person.found = true\nreturn person",
      returns = "Finds all the +Person+ nodes, sets a property on them, and returns them.",
      assertions = (p) => assertStats(p, propertiesSet = 5)
    )
  }

  @Test def merge_node_and_set_property_on_creation_or_update_prop() {
    testQuery(
      title = "Merge with ON CREATE and ON MATCH",
      text = "Merge a node and set properties if the node needs to be created.",
      queryText =
        """merge (keanu:Person {name:'<NAME>'})
on create set keanu.created = timestamp()
on match set keanu.lastSeen = timestamp()
return keanu""",
      returns = "The query creates the Keanu node, and sets a timestamp on creation time. If Keanu already existed, a " +
        "different property would have been set.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_create_with_unique_constraint() {
    testQuery(
      title = "Merge using unique constraints creates a new node if no node is found",
      text = "Merge using unique constraints creates a new node if no node is found.",
      queryText = """merge (laurence:Person {name: '<NAME>'}) return laurence""",
      returns = "The query creates the laurence node. If laurence already existed, merge would " +
        "just return the existing node.",
      assertions = (p) => assertStats(p, nodesCreated = 1, propertiesSet = 1, labelsAdded = 1)
    )
  }

  @Test def merge_node_and_match_with_unique_constraint() {
    testQuery(
      title = "Merge using unique constraints matches an existing node",
      text = "Merge using unique constraints matches an existing node.",
      queryText = """merge (oliver:Person {name:'<NAME>'}) return oliver""",
      returns = "The oliver node already exists, so merge just returns it.",
      assertions = (p) => assertStats(p, nodesCreated = 0, propertiesSet = 0, labelsAdded = 0)
    )
  }

  @Test def merge_node_and_match_many_with_unique_constraint_fails_for_partial_matches() {
    generateConsole = false
    testFailingQuery[MergeConstraintConflictException](
      title = "Merge with unique constraints and partial matches",
      text = "Merge using unique constraints fails when finding partial matches.",
      queryText = """merge (michael:Person {name:'<NAME>', role:'<NAME>'}) return michael""",
      returns = "While there is a matching unique michael node with the name '<NAME>', there is no " +
        "unique node with the role of 'Gordon Gekko' and merge fails to match."
    )
  }

  @Test def merge_node_and_match_many_with_unique_constraint_fails_for_conflicting_matches() {
    generateConsole = false
    testFailingQuery[MergeConstraintConflictException](
      title = "Merge with unique constraints and conflicting matches",
      text = "Merge using unique constraints fails when finding conflicting matches.",
      queryText = """merge (oliver:Person {name:'<NAME>', role:'<NAME>'}) return oliver""",
      returns = "While there is a matching unique oliver node with the name '<NAME>', there is also another " +
        "unique node with the role of '<NAME>' and merge fails to match."
    )
  }

  @Test def using_map_parameters_with_merge() {
    prepareAndTestQuery(
      title = "Using map parameters with MERGE",
      text = """+MERGE+ does not support map parameters like for example +CREATE+ does.
To use map parameters with +MERGE+, it is necessary to explicitly use the expected properties, like in the following example.
For more information on parameters, see <<cypher-parameters>>.""",
      prepare = setParameters(Map("param" -> Map("name" -> "<NAME>", "role" -> "Neo"))),
      queryText = "merge (oliver:Person {name:{param}.name, role:{param}.role}) return oliver",
      returns = "",
      assertions = p => assertStats(p, nodesCreated = 1, propertiesSet = 2, labelsAdded = 1)
    )
  }

  @Test def merging_on_a_single_relationship() {
    testQuery(
      title = "Merge on a relationship",
      text = "+MERGE+ can be used to match or create a relationship.",
      queryText =
        """match (charlie:Person {name:'<NAME>'}), (wallStreet:Movie {title:'Wall Street'})
merge (charlie)-[r:ACTED_IN]->(wallStreet)
return r""",
      returns = "<NAME> had already been marked as acting on Wall Street, so the existing relationship is found " +
        "and returned. Note that in order to match or create a relationship when using +MERGE+, at least one bound node " +
        "must be specified, which is done via the +MATCH+ clause in the above example.",
      assertions = (p) => assertStats(p, relationshipsCreated = 0)
    )
  }

  @Test def merging_on_a_longer_pattern() {
    testQuery(
      title = "Merge on multiple relationships",
      text = "When +MERGE+ is used on a whole pattern, either everything matches, or everything is created.",
      queryText =
        """match (oliver:Person {name:'<NAME>'}), (reiner:Person {name:'<NAME>'})
merge (oliver)-[:DIRECTED]->(movie:Movie)<-[:ACTED_IN]-(reiner)
return movie""",
      returns = "In our example graph, <NAME> and <NAME> have never worked together. When we try to +MERGE+ a " +
        "movie between them, Cypher will not use any of the existing movies already connected to either person. Instead, " +
        "a new movie node is created.",
      assertions = (p) => assertStats(p, relationshipsCreated = 2, nodesCreated = 1, propertiesSet = 0, labelsAdded = 1)
    )
  }
{'fraction_non_alphanumeric': 0.10963015719590985, 'fraction_numerical': 0.004578521646232894, 'mean_word_length': 3.684938036224976, 'pattern_counts': {'":': 0, '<': 64, '<?xml version=': 0, '>': 122, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3754769', 'n_tokens_mistral': 5790, 'n_tokens_neox': 5503, 'n_words': 2047}