Skip to content

Commit

Permalink
Merge pull request #16 from mnbjhu/bug_fix
Browse files Browse the repository at this point in the history
Call and foreach
  • Loading branch information
mnbjhu committed Feb 25, 2023
2 parents c96a209 + 64fdab2 commit a82253f
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ bin/
.DS_Store
/.idea/
/src/test/resources/local.properties
/local.properties
/src/test/resources/local.properties
11 changes: 11 additions & 0 deletions src/main/kotlin/uk/gibby/neo4k/clauses/Call.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.queries.Query
import uk.gibby.neo4k.returns.empty.EmptyReturn

class Call(private val innerQuery: Query<Unit, EmptyReturn, *, *>): Clause(){
override fun getString(): String {
return "CALL { WITH * ${innerQuery.query} }"
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/uk/gibby/neo4k/clauses/ForEach.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.NameCounter
import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.empty.EmptyReturnInstance
import uk.gibby.neo4k.returns.generic.ArrayReturn

class ForEach<T, U: ReturnValue<T>>(private val array: ArrayReturn<T, U>, private val query: QueryScope.(U) -> Unit): Clause(){
private val element = array.inner.createReference(NameCounter.next()) as U
override fun getString(): String {
val scope = QueryScope()
scope.query(element)
return "FOREACH(${element.getString()} IN ${array.getString()}|${scope.getString()})"
}
companion object{
fun <T, U: ReturnValue<T>>QueryScope.forEach(array: ArrayReturn<T, U>, action: QueryScope.(U) -> Unit): EmptyReturn {
addStatement(ForEach(array, action))
return EmptyReturnInstance
}
}
}
6 changes: 4 additions & 2 deletions src/main/kotlin/uk/gibby/neo4k/clauses/Limit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.core.of
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.empty.EmptyReturnInstance
import uk.gibby.neo4k.returns.primitives.LongReturn

/**
Expand All @@ -19,7 +21,7 @@ class Limit(private val count: LongReturn): Clause(){
return "LIMIT ${count.getString()}"
}
companion object{
fun QueryScope.limit(count: LongReturn) = Limit(count).also { addStatementAfterReturn(it) }
fun QueryScope.limit(count: Long) = Limit(count).also { addStatementAfterReturn(it) }
fun QueryScope.limit(count: LongReturn): EmptyReturn = EmptyReturnInstance.also { addStatementAfterReturn(Limit(count)) }
fun QueryScope.limit(count: Long): EmptyReturn = EmptyReturnInstance.also { addStatementAfterReturn(Limit(count)) }
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/uk/gibby/neo4k/clauses/Merge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.Creatable
import uk.gibby.neo4k.core.QueryScope


/**
* Create
*
* Represents the 'CREATE' clause from the CypherQL
*
* [Neo4j Cypher Manual](https://neo4j.com/docs/cypher-manual/current/clauses/create/)
*
* @property creatable The nodes or paths to create
* @constructor Creates a 'CREATE' clause
*/
class Merge(private vararg val creatable: Creatable<*>): Clause(){
override fun getString() = "MERGE ${creatable.joinToString { it.getCreateString() }}"
companion object{
/**
* Create
*
* Used to create nodes or paths as part of a graph query
*
* [Neo4j Cypher Manual](https://neo4j.com/docs/cypher-manual/current/clauses/merge/)
*
* @param T Type of the reference produced
* @param U Type of the [Creatable]
* @param creatable Describes one node or path to merge
* @receiver The scope of the query
* @return The references to the produced nodes or paths
* @see [QueryScope]
*/
fun <T, U : Creatable<T>> QueryScope.merge(creatable: U): T{
addStatement(Create(creatable))
return creatable.getReference()
}
}
}
8 changes: 6 additions & 2 deletions src/main/kotlin/uk/gibby/neo4k/clauses/OrderBy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.empty.EmptyReturnInstance

class OrderBy<T>(private val comparable: ReturnValue<T>, private val descending: Boolean = false): Clause() {

companion object {
fun <T: Any> QueryScope.orderBy(result: ReturnValue<T>) {
fun <T: Any> QueryScope.orderBy(result: ReturnValue<T>): EmptyReturn {
addStatementAfterReturn(OrderBy(result))
return EmptyReturnInstance
}
fun <T: Any>QueryScope.orderByDesc(result: ReturnValue<T>) {
fun <T: Any>QueryScope.orderByDesc(result: ReturnValue<T>): EmptyReturn {
addStatementAfterReturn(OrderBy(result, true))
return EmptyReturnInstance
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/uk/gibby/neo4k/clauses/Skip.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package uk.gibby.neo4k.clauses

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.core.of
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.empty.EmptyReturnInstance
import uk.gibby.neo4k.returns.primitives.LongReturn

class Skip(private val count: LongReturn): Clause(){
Expand All @@ -11,8 +13,8 @@ class Skip(private val count: LongReturn): Clause(){
}

companion object{
fun QueryScope.skip(count: LongReturn) = Skip(count).also { addStatementAfterReturn(it) }
fun QueryScope.skip(count: Long) = Skip(count).also { addStatementAfterReturn(it) }
fun QueryScope.skip(count: LongReturn) : EmptyReturn= EmptyReturnInstance.also { addStatementAfterReturn(Skip(count)) }
fun QueryScope.skip(count: Long): EmptyReturn = EmptyReturnInstance.also { addStatementAfterReturn(Skip(count)) }
}

}
2 changes: 1 addition & 1 deletion src/main/kotlin/uk/gibby/neo4k/core/ParamMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlin.reflect.KType

sealed class ParamMap<out U: Entity<*>>(protected val type: KType){
val entries: MutableList<Pair<String, String>> = mutableListOf()
operator fun <U: ReturnValue<*>>set(attribute: U, value: U){
operator fun <T, U: ReturnValue<T>>set(attribute: U, value: U){
entries.add(attribute.getString() to value.getString())
}
operator fun <T, U: ReturnValue<T>>set(attribute: U, value: T){
Expand Down
25 changes: 23 additions & 2 deletions src/main/kotlin/uk/gibby/neo4k/core/QueryScope.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
package uk.gibby.neo4k.core

import uk.gibby.neo4k.clauses.Clause
import uk.gibby.neo4k.clauses.*
import uk.gibby.neo4k.queries.QueryBuilder
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.primitives.LongReturn

class QueryScope {
private val clauses = mutableListOf<Clause>()
private val afterClauses = mutableListOf<Clause>()
fun addStatement(statement: Clause){ clauses.add(statement) }
fun addStatementAfterReturn(statement: Clause){ afterClauses.add(statement) }


fun getString() = clauses.joinToString(" "){it.getString()}
fun getAfterString() = afterClauses.joinToString(" ") { it.getString() }

fun <T, U: ReturnValue<T>>QueryBuilder<Unit, EmptyReturn, T, U>.call(): U {
val query = _build()
addStatement(Call(query))
return query.returnValue
}
infix fun <U: ReturnValue<*>>U.limit(count: LongReturn): U = this.also { addStatementAfterReturn(Limit(count)) }
infix fun <U: ReturnValue<*>>U.limit(count: Long): U = this.also { addStatementAfterReturn(Limit(count)) }
infix fun <U: ReturnValue<*>> U.orderBy(result: ReturnValue<*>): U {
addStatementAfterReturn(OrderBy(result))
return this
}
infix fun <U: ReturnValue<*>>U.orderByDesc(result: ReturnValue<*>): U{
addStatementAfterReturn(OrderBy(result, true))
return this
}
infix fun <U: ReturnValue<*>>U.skip(count: LongReturn) = this.also { addStatementAfterReturn(Skip(count)) }
infix fun <U: ReturnValue<*>>U.skip(count: Long) = this.also { addStatementAfterReturn(Skip(count)) }
}
14 changes: 7 additions & 7 deletions src/main/kotlin/uk/gibby/neo4k/queries/Query.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import uk.gibby.neo4k.core.Graph
import uk.gibby.neo4k.core.Neo4kLogger
import uk.gibby.neo4k.core.ResultSetParser
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.SingleParser
import uk.gibby.neo4k.returns.multiple.MultipleReturn

class Query<p, P: ReturnValue<p>, r, R: ReturnValue<r>>(val query: String, private val paramSerializers: List<KSerializer<*>>, private val returnSerializer: ResultSetParser<out r?, out KSerializer<out r?>>, private val hasReturn: Boolean){
class Query<p, P: ReturnValue<p>, r, R: ReturnValue<r>>(val query: String, private val paramSerializers: List<KSerializer<*>>, val returnValue: R, private val hasReturn: Boolean){
private val returnSerializer: ResultSetParser<out r?,out KSerializer<out r?>> = if(returnValue is MultipleReturn<*>) ResultSetParser(returnValue.serializer)
else ResultSetParser(SingleParser(returnValue.serializer as KSerializer<r?>).serializer)
fun execute(graph: Graph, args: List<Any?>): List<r?> {
var count = 0
val paramString = args.joinToString { "\"p$count\": " + Json.encodeToString(paramSerializers[count++] as KSerializer<Any?>, it) }
val response = runBlocking { graph.sendQuery(query, paramString) }
if(!hasReturn) return emptyList()
val resultSet = Json.decodeFromString(returnSerializer, response)
resultSet.notifications.forEach {
Neo4kLogger.info("{} {} {} {}", it.code, it.severity, it.title, it.description)
}
resultSet.errors.forEach{
throw it.getError()
}
resultSet.notifications.forEach { Neo4kLogger.info("{} {} {} {}", it.code, it.severity, it.title, it.description) }
resultSet.errors.forEach { Neo4kLogger.error("{} {}", it.code, it.message); throw it.getError() }
return resultSet.data[0]
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/uk/gibby/neo4k/queries/Query3.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import uk.gibby.neo4k.returns.multiple.Single
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.typeOf
import uk.gibby.neo4k.core.Graph

fun <
a, A: Single<a>,
Expand Down Expand Up @@ -35,4 +36,8 @@ fun <a, A: Single<a>, b, B: Single<b>, c, C: Single<c>, r, R: ReturnValue<r>>que
}
inline fun <a, reified A: Single<a>, b, reified B: Single<b>, c, reified C: Single<c>, r, R: ReturnValue<r>>query3(noinline builder: QueryScope.(A, B, C) -> R): QueryBuilder<Triple<a, b, c>, MultipleReturn3<a, A, b, B, c, C>, r, R>{
return query(typeOf<A>(), typeOf<B>(), typeOf<C>(), builder)
}
fun <a, A: Single<a>, b, B: Single<b>, c, C: Single<c>, r, R: ReturnValue<r>> QueryBuilder<Triple<a, b, c>, MultipleReturn3<a, A, b, B, c, C>, r, R>.build(): Graph.(a, b, c) -> List<r> {
val query = _build()
return { first, second, third -> query.execute(this, listOf(first, second, third)) as List<r> }
}
7 changes: 6 additions & 1 deletion src/main/kotlin/uk/gibby/neo4k/queries/Query4.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uk.gibby.neo4k.queries

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.core.Graph
import uk.gibby.neo4k.core.TypeProducer
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.multiple.MultipleReturn4
Expand All @@ -19,7 +20,7 @@ fun <
val param1 = second.inner.createReference("\$p1") as B
val param2 = third.inner.createReference("\$p2") as C
val param3 = forth.inner.createReference("\$p3") as D
return QueryBuilder(MultipleReturn4(param0, param1, param2, param3)){
return QueryBuilder(MultipleReturn4(param0, param1, param2, param3)) {
builder(it.first, it.second, it.third, it.forth)
}
}
Expand All @@ -30,3 +31,7 @@ fun <a, A: Single<a>, b, B: Single<b>, c, C: Single<c>, d, D: Single<d>, r, R: R
inline fun <a, reified A: Single<a>, b, reified B: Single<b>, c, reified C: Single<c>, d, reified D: Single<d>, r, R: ReturnValue<r>>query4(noinline builder: QueryScope.(A, B, C, D) -> R): QueryBuilder<MultipleReturn4.Vec<a, b, c, d>, MultipleReturn4<a, A, b, B, c, C, d, D>, r, R>{
return query(typeOf<A>(), typeOf<B>(), typeOf<C>(), typeOf<D>(), builder)
}
fun <a, A: Single<a>, b, B: Single<b>, c, C: Single<c>, d, D: Single<d>, r, R: ReturnValue<r>> QueryBuilder<MultipleReturn4.Vec<a, b, c, d>, MultipleReturn4<a, A, b, B, c, C, d, D>, r, R>.build(): Graph.(a, b, c, d) -> List<r> {
val query = _build()
return { first, second, third, fourth -> query.execute(this, listOf(first, second, third, fourth)) as List<r> }
}
7 changes: 2 additions & 5 deletions src/main/kotlin/uk/gibby/neo4k/queries/QueryBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,15 @@ class QueryBuilder<p, P: ReturnValue<p>, r, R: ReturnValue<r>>(val args: P, val
fun _build(): Query<p, P, r, R> {
val scope = QueryScope()
val returnValue = scope.builder(args)
val resultParser = if(returnValue is MultipleReturn<*>) ResultSetParser(returnValue.serializer) else ResultSetParser(
SingleParser(returnValue.serializer as KSerializer<r?>).serializer
)
val hasReturn = returnValue !is EmptyReturn
val queryString = if (returnValue is EmptyReturn) "${scope.getString()} ${scope.getAfterString()}"
else "${scope.getString()} RETURN ${returnValue.getString()} ${scope.getAfterString()}"
return if(args is MultipleReturn<*>) Query(
queryString,
args.getList().map { it.serializer },
resultParser,
returnValue,
hasReturn
)
else Query(queryString, listOf(args.serializer), resultParser, hasReturn)
else Query(queryString, listOf(args.serializer), returnValue, hasReturn)
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/uk/gibby/neo4k/returns/multiple/Multiple.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package uk.gibby.neo4k.returns.multiple

import uk.gibby.neo4k.core.QueryScope
import uk.gibby.neo4k.returns.ReturnValue
import uk.gibby.neo4k.returns.empty.EmptyReturn
import uk.gibby.neo4k.returns.empty.EmptyReturnInstance

fun QueryScope.none(): EmptyReturn = EmptyReturnInstance

fun <a, A: ReturnValue<a>, b, B: ReturnValue<b>>QueryScope.many(first: A, second: B) = MultipleReturn2(first, second)
fun <a, A: ReturnValue<a>, b, B: ReturnValue<b>, c, C: ReturnValue<c>>QueryScope.many(first: A, second: B, third: C) =
MultipleReturn3(first, second, third)
fun <a, A: ReturnValue<a>, b, B: ReturnValue<b>, c, C: ReturnValue<c>, d, D: ReturnValue<d>> QueryScope.many(first: A, second: B, third: C, fourth: D) =
MultipleReturn4(first, second, third, fourth)
fun <a, A: ReturnValue<a>, b, B: ReturnValue<b>, c, C: ReturnValue<c>, d, D: ReturnValue<d>, e, E: ReturnValue<e>> QueryScope.many(first: A, second: B, third: C, fourth: D, fifth: E) =
MultipleReturn5(first, second, third, fourth, fifth)
44 changes: 44 additions & 0 deletions src/test/kotlin/e2e/clauses/Call.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package e2e.clauses

import e2e.schemas.ActedIn
import e2e.schemas.Actor
import e2e.schemas.Movie
import org.amshove.kluent.`should contain same`
import org.junit.jupiter.api.Test
import uk.gibby.neo4k.clauses.Create.Companion.create
import uk.gibby.neo4k.clauses.ForEach.Companion.forEach
import uk.gibby.neo4k.clauses.Match.Companion.match
import uk.gibby.neo4k.core.array
import uk.gibby.neo4k.core.invoke
import uk.gibby.neo4k.core.of
import uk.gibby.neo4k.paths.`o-→`
import uk.gibby.neo4k.queries.query
import uk.gibby.neo4k.returns.primitives.StringReturn
import util.GraphTest

class Call : GraphTest(){
@Test
fun forEachTest(){
val myList = listOf("something1", "something2")
graph.query { forEach(array(::StringReturn) of myList) { name -> create(::Actor{ it[this.name] = name }) } }
graph.query {
val actors = match(::Actor)
actors.name
} `should contain same` myList
}

@Test
fun callTest(){
val myList = listOf("something1", "something2", "something3")
graph.query { forEach(array(::StringReturn) of myList) { name -> create(::Actor{ it[this.name] = name }) } }
graph.query { forEach(array(::StringReturn) of myList) { name -> create(::Movie{ it[title] = name; it[releaseYear] = 1234}) } }
graph.query {
val actor = match(::Actor)
query {
val movie = match(::Movie)
create(actor `o-→` ::ActedIn `o-→` movie)
movie
}.call()
}
}
}
2 changes: 1 addition & 1 deletion src/test/kotlin/e2e/queries/QueryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ class QueryTest: GraphTest() {
fun threeParamQuery(){
val myQuery = query(::StringReturn, ::LongReturn, ::LongReturn) { text, bottom, top ->
EmptyReturnInstance
}
}.build()
}
}
4 changes: 2 additions & 2 deletions src/test/kotlin/util/GraphTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ abstract class GraphTest {
protected val graph = Graph(
name = this::class.qualifiedName!!.replace("_", ".").replace(".", "").lowercase(),
host = "localhost",
username = "neo4j",
password = "myPassword123"
username = TestAuth.user,
password = TestAuth.password
)
@BeforeEach
fun clearGraph(){
Expand Down
8 changes: 3 additions & 5 deletions src/test/kotlin/util/TestAuth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import com.natpryce.konfig.stringType

object TestAuth {
private val config = try{ ConfigurationProperties.fromResource("local.properties") } catch (e: Exception) { null }
private val server_port = Key("server.port", intType)
private val server_host = Key("server.host", stringType)
private val server_user = Key("server.user", stringType)
private val server_pass = Key("server.pass", stringType)
val host = try{ config?.get(server_host)!! } catch (e: Exception) { "bolt://localhost" }
val port = try{ config?.get(server_port)!! } catch (e: Exception) { 6379 }
val password = try{ config?.get(server_pass) } catch (e: Exception) { null }
val user = try{ config?.get(server_user)!! } catch (e: Exception) { "neo4j" }
val password = try{ config?.get(server_pass)!! } catch (e: Exception) { "myPassword123" }
}

0 comments on commit a82253f

Please sign in to comment.