Skip to content

Commit e4aba0d

Browse files
Issue #542: Add a strategy to merge edge nodes properties when creating relationships using Relationship pattern strategy (#544)
* Issue #542: Add a strategy to merge edge nodes properties when creating relationships using Relationship pattern strategy * some refactoring Co-authored-by: Andrea Santurbano <[email protected]>
1 parent 571cdf3 commit e4aba0d

File tree

15 files changed

+166
-130
lines changed

15 files changed

+166
-130
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,3 @@ Thumbs.db
3131
bin
3232
doc/node
3333
doc/node_modules
34-

common/src/main/kotlin/streams/service/sink/strategy/NodePatternIngestionStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class NodePatternIngestionStrategy(private val nodePatternConfiguration: NodePat
1515
|MERGE (n${getLabelsAsString(nodePatternConfiguration.labels)}{${
1616
getNodeMergeKeys("keys", nodePatternConfiguration.keys)
1717
}})
18-
|SET n = event.properties
18+
|SET n ${if (nodePatternConfiguration.mergeProperties) "+" else ""}= event.properties
1919
|SET n += event.keys
2020
""".trimMargin()
2121

common/src/main/kotlin/streams/service/sink/strategy/PatternConfiguration.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ private fun cleanProperties(type: PatternConfigurationType, properties: List<Str
4242
interface PatternConfiguration
4343

4444
data class NodePatternConfiguration(val keys: Set<String>, val type: PatternConfigurationType,
45-
val labels: List<String>, val properties: List<String>): PatternConfiguration {
45+
val labels: List<String>, val properties: List<String>, val mergeProperties: Boolean): PatternConfiguration {
4646
companion object {
4747

4848
// (:LabelA{!id,foo,bar})
4949
@JvmStatic private val cypherNodePatternConfigured = """\((:\w+\s*(?::\s*(?:\w+)\s*)*)\s*(?:\{\s*(-?[\w!\.]+\s*(?:,\s*-?[!\w\*\.]+\s*)*)\})?\)$""".toRegex()
5050
// LabelA{!id,foo,bar}
5151
@JvmStatic private val simpleNodePatternConfigured = """^(\w+\s*(?::\s*(?:\w+)\s*)*)\s*(?:\{\s*(-?[\w!\.]+\s*(?:,\s*-?[!\w\*\.]+\s*)*)\})?$""".toRegex()
52-
fun parse(pattern: String): NodePatternConfiguration {
52+
fun parse(pattern: String, mergeProperties: Boolean): NodePatternConfiguration {
5353
val isCypherPattern = pattern.startsWith("(")
5454
val regex = if (isCypherPattern) cypherNodePatternConfigured else simpleNodePatternConfigured
5555
val matcher = regex.matchEntire(pattern)
@@ -75,25 +75,26 @@ data class NodePatternConfiguration(val keys: Set<String>, val type: PatternConf
7575
val cleanedProperties = cleanProperties(type, properties)
7676

7777
return NodePatternConfiguration(keys = keys, type = type,
78-
labels = labels, properties = cleanedProperties)
78+
labels = labels, properties = cleanedProperties, mergeProperties)
7979
}
8080
}
8181
}
82+
8283
}
8384

8485

8586
data class RelationshipPatternConfiguration(val start: NodePatternConfiguration, val end: NodePatternConfiguration,
8687
val relType: String, val type: PatternConfigurationType,
87-
val properties: List<String>): PatternConfiguration {
88+
val properties: List<String>, val mergeProperties: Boolean): PatternConfiguration {
8889
companion object {
8990

9091
// we don't allow ALL for start/end nodes in rels
9192
// it's public for testing purpose
92-
fun getNodeConf(pattern: String): NodePatternConfiguration {
93-
val start = NodePatternConfiguration.parse(pattern)
93+
fun getNodeConf(pattern: String, mergeProperties: Boolean): NodePatternConfiguration {
94+
val start = NodePatternConfiguration.parse(pattern, mergeProperties)
9495
return if (start.type == PatternConfigurationType.ALL) {
9596
NodePatternConfiguration(keys = start.keys, type = PatternConfigurationType.INCLUDE,
96-
labels = start.labels, properties = start.properties)
97+
labels = start.labels, properties = start.properties, mergeProperties)
9798
} else {
9899
start
99100
}
@@ -148,7 +149,7 @@ data class RelationshipPatternConfiguration(val start: NodePatternConfiguration,
148149
}
149150
}
150151

151-
fun parse(pattern: String): RelationshipPatternConfiguration {
152+
fun parse(pattern: String, mergeNodeProps: Boolean, mergeRelProps: Boolean): RelationshipPatternConfiguration {
152153
val isCypherPattern = pattern.startsWith("(")
153154
val regex = if (isCypherPattern) {
154155
cypherRelationshipPatternConfigured
@@ -169,20 +170,20 @@ data class RelationshipPatternConfiguration(val start: NodePatternConfiguration,
169170
val metadata = RelationshipPatternMetaData.create(isCypherPattern, isLeftToRight, matcher.groupValues)
170171

171172
val start = try {
172-
getNodeConf(metadata.startPattern)
173+
getNodeConf(metadata.startPattern, mergeNodeProps)
173174
} catch (e: Exception) {
174175
throw IllegalArgumentException("The Relationship pattern $pattern is invalid")
175176
}
176177
val end = try {
177-
getNodeConf(metadata.endPattern)
178+
getNodeConf(metadata.endPattern, mergeNodeProps)
178179
} catch (e: Exception) {
179180
throw IllegalArgumentException("The Relationship pattern $pattern is invalid")
180181
}
181182
val type = getPatternConfiguredType(metadata.properties)
182183
isHomogeneousPattern(type, metadata.properties, pattern, "Relationship")
183184
val cleanedProperties = cleanProperties(type, metadata.properties)
184185
return RelationshipPatternConfiguration(start = start, end = end, relType = metadata.relType,
185-
properties = cleanedProperties, type = type)
186+
properties = cleanedProperties, type = type, mergeProperties = mergeRelProps)
186187
}
187188
}
188189

common/src/main/kotlin/streams/service/sink/strategy/RelationshipPatternIngestionStrategy.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ class RelationshipPatternIngestionStrategy(private val relationshipPatternConfig
1515
|MERGE (start${getLabelsAsString(relationshipPatternConfiguration.start.labels)}{${
1616
getNodeMergeKeys("start.keys", relationshipPatternConfiguration.start.keys)
1717
}})
18-
|SET start = event.start.properties
18+
|SET start ${if (relationshipPatternConfiguration.mergeProperties) "+" else ""}= event.start.properties
1919
|SET start += event.start.keys
2020
|MERGE (end${getLabelsAsString(relationshipPatternConfiguration.end.labels)}{${
2121
getNodeMergeKeys("end.keys", relationshipPatternConfiguration.end.keys)
2222
}})
23-
|SET end = event.end.properties
23+
|SET end ${if (relationshipPatternConfiguration.mergeProperties) "+" else ""}= event.end.properties
2424
|SET end += event.end.keys
2525
|MERGE (start)-[r:${relationshipPatternConfiguration.relType}]->(end)
26-
|SET r = event.properties
26+
|SET r ${if (relationshipPatternConfiguration.mergeProperties) "+" else ""}= event.properties
2727
""".trimMargin()
2828

2929
private val deleteRelationshipTemplate: String = """

common/src/main/kotlin/streams/service/sink/strategy/SourceIdIngestionStrategy.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import streams.utils.IngestionUtils.getLabelsAsString
77
import streams.utils.SchemaUtils
88
import streams.utils.StreamsUtils
99

10-
data class SourceIdIngestionStrategyConfig(val labelName: String = "SourceEvent", val idName: String = "sourceId")
10+
data class SourceIdIngestionStrategyConfig(val labelName: String = "SourceEvent", val idName: String = "sourceId") {
11+
companion object {
12+
val DEFAULT = SourceIdIngestionStrategyConfig()
13+
}
14+
}
1115

1216
class SourceIdIngestionStrategy(config: SourceIdIngestionStrategyConfig = SourceIdIngestionStrategyConfig()): IngestionStrategy {
1317

common/src/test/kotlin/streams/service/sink/strategy/NodePatternIngestionStrategyTest.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class NodePatternIngestionStrategyTest {
1010
@Test
1111
fun `should get all properties`() {
1212
// given
13-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id})")
13+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id})", true)
1414
val strategy = NodePatternIngestionStrategy(config)
1515
val data = mapOf("id" to 1, "foo" to "foo", "bar" to "bar", "foobar" to "foobar")
1616

@@ -22,7 +22,7 @@ class NodePatternIngestionStrategyTest {
2222
assertEquals("""
2323
|${StreamsUtils.UNWIND}
2424
|MERGE (n:LabelA:LabelB{id: event.keys.id})
25-
|SET n = event.properties
25+
|SET n += event.properties
2626
|SET n += event.keys
2727
""".trimMargin(), queryEvents[0].query)
2828
assertEquals(listOf(mapOf("keys" to mapOf("id" to 1),
@@ -37,7 +37,7 @@ class NodePatternIngestionStrategyTest {
3737
@Test
3838
fun `should get nested properties`() {
3939
// given
40-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, foo.bar})")
40+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, foo.bar})", false)
4141
val strategy = NodePatternIngestionStrategy(config)
4242
val data = mapOf("id" to 1, "foo" to mapOf("bar" to "bar", "foobar" to "foobar"))
4343

@@ -65,7 +65,7 @@ class NodePatternIngestionStrategyTest {
6565
@Test
6666
fun `should exclude nested properties`() {
6767
// given
68-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, -foo})")
68+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, -foo})", false)
6969
val strategy = NodePatternIngestionStrategy(config)
7070
val map = mapOf("id" to 1, "foo" to mapOf("bar" to "bar", "foobar" to "foobar"), "prop" to 100)
7171

@@ -93,7 +93,7 @@ class NodePatternIngestionStrategyTest {
9393
@Test
9494
fun `should include nested properties`() {
9595
// given
96-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, foo})")
96+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id, foo})", true)
9797
val strategy = NodePatternIngestionStrategy(config)
9898
val data = mapOf("id" to 1, "foo" to mapOf("bar" to "bar", "foobar" to "foobar"), "prop" to 100)
9999

@@ -106,7 +106,7 @@ class NodePatternIngestionStrategyTest {
106106
assertEquals("""
107107
|${StreamsUtils.UNWIND}
108108
|MERGE (n:LabelA:LabelB{id: event.keys.id})
109-
|SET n = event.properties
109+
|SET n += event.properties
110110
|SET n += event.keys
111111
""".trimMargin(),
112112
queryEvents[0].query)
@@ -121,7 +121,7 @@ class NodePatternIngestionStrategyTest {
121121
@Test
122122
fun `should exclude the properties`() {
123123
// given
124-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id,-foo,-bar})")
124+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id,-foo,-bar})", false)
125125
val strategy = NodePatternIngestionStrategy(config)
126126
val data = mapOf("id" to 1, "foo" to "foo", "bar" to "bar", "foobar" to "foobar")
127127

@@ -146,7 +146,7 @@ class NodePatternIngestionStrategyTest {
146146
@Test
147147
fun `should include the properties`() {
148148
// given
149-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id,foo,bar})")
149+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id,foo,bar})", false)
150150
val strategy = NodePatternIngestionStrategy(config)
151151
val data = mapOf("id" to 1, "foo" to "foo", "bar" to "bar", "foobar" to "foobar")
152152

@@ -170,7 +170,7 @@ class NodePatternIngestionStrategyTest {
170170
@Test
171171
fun `should delete the node`() {
172172
// given
173-
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id})")
173+
val config = NodePatternConfiguration.parse("(:LabelA:LabelB{!id})", true)
174174
val strategy = NodePatternIngestionStrategy(config)
175175
val data = mapOf("id" to 1, "foo" to "foo", "bar" to "bar", "foobar" to "foobar")
176176

0 commit comments

Comments
 (0)