Skip to content

Commit

Permalink
Upgrade to Neo4j 5
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcelKonrad authored and manuelprinz committed Feb 12, 2025
1 parent 7c513f2 commit 98e6c38
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 29 deletions.
11 changes: 5 additions & 6 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,18 @@ services:
condition: service_healthy

neo4j:
image: neo4j:4.4-community
image: neo4j:5-community
ports:
- '127.0.0.1:7474:7474' # HTTP
#- '::1:7474:7474'
- '127.0.0.1:7687:7687' # BOLT
#- '::1:7687:7687'
environment:
NEO4JLABS_PLUGINS: '["apoc"]'
NEO4J_PLUGINS: '["apoc", "apoc-extended"]'
NEO4J_AUTH: none
NEO4J_dbms_mode: SINGLE
NEO4J_dbms_default__database: orkg
NEO4J_dbms_memory_heap_max__size: 4G
NEO4J_dbms_transaction_timeout: 30s
NEO4J_initial_dbms_default__database: orkg
NEO4J_server_memory_heap_max__size: 4G
NEO4J_db_transaction_timeout: 30s
healthcheck:
test: [ "CMD", "/var/lib/neo4j/bin/cypher-shell", "-u", "$${NEO4J_USERNAME}", "-p", "$${NEO4J_PASSWORD}", "MATCH () RETURN count(*) as count" ]
interval: 5s
Expand Down
2 changes: 1 addition & 1 deletion constants/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildConfig {
className("BuildConfig")

buildConfigField("APP_NAME", rootProject.name)
buildConfigField("CONTAINER_IMAGE_NEO4J", "neo4j:4.4-community")
buildConfigField("CONTAINER_IMAGE_NEO4J", "neo4j:5-community")
buildConfigField("CONTAINER_IMAGE_POSTGRES", "postgres:11")
buildConfigField("CONTAINER_IMAGE_KEYCLOAK", "registry.gitlab.com/tibhannover/orkg/keycloak-docker-image:26.0.5")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface Neo4jPaperRepository : Neo4jRepository<Neo4jResource, ThingId> {
MATCH (r:Resource {id: $id})
CALL apoc.path.expandConfig(r, {relationshipFilter: "<RELATED", labelFilter: "/Paper", uniqueness: "RELATIONSHIP_GLOBAL"})
YIELD path
WITH last(nodes(path)) AS paper, apoc.coll.reverse(apoc.coll.flatten([r in relationships(path) | [r, startNode(r)]])) AS path
WITH last(nodes(path)) AS paper, reverse(apoc.coll.flatten([r in relationships(path) | [r, startNode(r)]])) AS path
UNWIND path AS thing
MATCH (t:Thing)
WHERE t.id = thing.id
Expand Down
2 changes: 1 addition & 1 deletion migrations/neo4j-migrations/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.gradle.jvm.tasks.Jar

plugins {
id("java-library")
id("org.orkg.gradle.kotlin-library")
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.orkg.migrations.neo4j

import ac.simons.neo4j.migrations.core.JavaBasedMigration
import ac.simons.neo4j.migrations.core.MigrationContext
import java.lang.Thread.sleep
import java.util.*

abstract class AbstractCustomProcedureMigration(
private val migrationQuery: String,
private val validationQuery: String,
private val version: Long,
private val validationIntervalMs: Long = 100,
) : JavaBasedMigration {
override fun apply(context: MigrationContext) {
val systemDBContext = context.getSessionConfig { it.withDatabase("system") }
context.driver.session(systemDBContext).executeWriteWithoutResult { tx ->
tx.run(migrationQuery)
}
val orkgDBContext = context.getSessionConfig { it.withDatabase("orkg") }
val validate = {
try {
context.driver.session(orkgDBContext).executeRead { tx ->
tx.run(validationQuery).list().size
}
} catch (e: Exception) {
-1
}
}
var result = validate()
while (result == -1) {
sleep(validationIntervalMs)
result = validate()
}
}

override fun getOptionalDescription(): Optional<String> = Optional.ofNullable(
this::class.simpleName
?.replace(Regex("""^[RV]\d+__"""), "")
?.replace(Regex("""([A-Z])""")) { " " + it.value.lowercase() }
?.trim()
)

override fun getChecksum(): Optional<String> = Optional.of(version.toString())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.orkg.migrations.neo4j

@Suppress("ClassName")
class R0027__CreateCustomSubgraphProcedure : AbstractCustomProcedureMigration(
"""
CALL apoc.custom.installProcedure(
'subgraph(start :: ANY?, config :: MAP) :: (relationships :: LIST? OF RELATIONSHIP?)',
'CALL apoc.path.subgraphAll(${'$'}start, ${'$'}config) YIELD relationships WITH relationships UNWIND relationships AS rel WITH rel WHERE rel:RELATED RETURN COLLECT(rel) AS relationships',
'orkg',
'read',
'custom.subgraph(startNode <id>|Node|list, {maxLevel,relationshipFilter,labelFilter,bfs:true, filterStartNode:false, limit:-1, endNodes:[], terminatorNodes:[], sequence, beginSequenceAtStart:true}) yield relationships - expand the subgraph reachable from start node following relationships to max-level adhering to the label filters, returning all RELATED relationships within the subgraph'
);
""".trimIndent(),
validationQuery = "CALL custom.subgraph([], {})",
version = 1
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.orkg.migrations.neo4j

import org.springframework.stereotype.Component

@Component
@Suppress("ClassName")
class R0031__CreateCustomTimestampParsingFunction : AbstractCustomProcedureMigration(
"""
CALL apoc.custom.installFunction(
'parseIsoOffsetDateTime(time :: STRING, unit = ms :: STRING) :: (INTEGER?)',
'WITH apoc.text.regreplace(${'$'}time, "(\.\d{1,3})\d*", "${'$'}1") AS time // limit fraction to 3 digits because parsing pattern only supports milliseconds (see java.text.SimpleDateFormat)
WITH apoc.text.regreplace(time, "(T\d{2}:\d{2})([+-]\d{2}:\d{2}|Z)${'$'}", "${'$'}1:00${'$'}2") AS time // insert seconds block if missing because parsing pattern does not support optional sections (see java.text.SimpleDateFormat)
WITH apoc.text.regreplace(time, "(T\d{2}:\d{2}:\d{2})([+-]\d{2}:\d{2}|Z)${'$'}", "${'$'}1.0${'$'}2") AS time // insert milliseconds block if missing because parsing pattern does not support optional sections (see java.text.SimpleDateFormat)
RETURN apoc.date.parse(time, ${'$'}unit, "yyyy-MM-dd\'T\'HH:mm:ss.SSSXXX")',
'orkg',
false,
'custom.parseIsoOffsetDateTime(\'2012-12-23T21:15:05.645313+02:00\', \'ms|s|m|h|d\') - parse date string as offset date time into the specified time unit'
);
""".trimIndent(),
validationQuery = "RETURN custom.parseIsoOffsetDateTime('2012-12-23T21:15:05.645313+02:00', 'ms')",
version = 1
)

This file was deleted.

This file was deleted.

5 changes: 5 additions & 0 deletions rest-api-server/src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ management:
enabled: true
hawtio:
authenticationEnabled: false
org:
neo4j:
migrations:
packages-to-scan:
- "org.orkg.migrations.neo4j"
---
spring:
config:
Expand Down
4 changes: 2 additions & 2 deletions scripts/reload-dump
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
set -e

neo4j_version=4.4-community
neo4j_version=5-community

dump_volume=orkg-dumps
source_dir=orkg-backend
Expand Down Expand Up @@ -47,7 +47,7 @@ docker compose stop neo4j
# Load dump.
# The double dash seems to be required to make the script work with "Git Bash" in Windows. :-/
docker run -i --rm --entrypoint //bin/bash --volumes-from "${source_dir}-neo4j-1" --volume "${dump_volume}:/dumps" neo4j:$neo4j_version <<EOS
neo4j-admin load --from=/dumps/neo4j-dump-latest.dump --database=orkg --force
neo4j-admin database load orkg --overwrite-destination=true --from-stdin < /dumps/neo4j-dump-latest.dump
EOS

# Destroy database container. This is required to get rid of the logs, so the next step can wait successfully.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ class Neo4jContainerInitializer : ApplicationContextInitializer<ConfigurableAppl
// TODO: might be nice to aggregate values for debugging, if possible

companion object {
val neo4jContainer: Neo4jContainer<*> =
Neo4jContainer(DockerImageName.parse(BuildConfig.CONTAINER_IMAGE_NEO4J))
val neo4jContainer: Neo4jContainer<*> = Neo4jContainer(DockerImageName.parse(BuildConfig.CONTAINER_IMAGE_NEO4J))
.withNeo4jConfig("initial.dbms.default_database", "orkg")
.withNeo4jConfig("apoc.custom.procedures.refresh", "100")
.withoutAuthentication()
.withPlugins("apoc")
.withPlugins("apoc", "apoc-extended")
}

override fun initialize(applicationContext: ConfigurableApplicationContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ import org.springframework.core.annotation.AliasFor
import org.springframework.data.neo4j.core.DatabaseSelectionProvider
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource

@SpringBootTest
@ContextConfiguration(initializers = [Neo4jContainerInitializer::class])
@ImportAutoConfiguration(MigrationsAutoConfiguration::class)
@TestPropertySource(properties = ["org.neo4j.migrations.packages-to-scan=org.orkg.migrations.neo4j"])
annotation class Neo4jContainerIntegrationTest

@DataNeo4jTest
@EnableAutoConfiguration
@ContextConfiguration(initializers = [Neo4jContainerInitializer::class])
@ImportAutoConfiguration(MigrationsAutoConfiguration::class)
@Import(Neo4jTestConfiguration::class)
@TestPropertySource(properties = ["org.neo4j.migrations.packages-to-scan=org.orkg.migrations.neo4j"])
annotation class Neo4jContainerUnitTest(
@get:AliasFor(annotation = ContextConfiguration::class, attribute = "classes")
val classes: Array<KClass<*>> = [],
Expand Down

0 comments on commit 98e6c38

Please sign in to comment.