Skip to content

Commit

Permalink
Merge pull request #154 from Grupo-Abraxas/merge_syntax
Browse files Browse the repository at this point in the history
Merge syntax
  • Loading branch information
krabbit93 committed Dec 21, 2021
2 parents 7590161 + 47519a4 commit daeb4be
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ object CypherFragment {
case class With(ret: Return[_], where: Option[Expr[Boolean]]) extends Read
case class Unwind(expr: Expr[Seq[_]], as: Alias) extends Read

case class Merge(pattern: PatternTuple) extends Write

case class Call(
procedure: String,
params: List[Expr[_]],
Expand All @@ -480,6 +482,10 @@ object CypherFragment {
case class Create(pattern: PatternTuple) extends Write
case class Delete(elems: NonEmptyList[Expr[_]], detach: Boolean) extends Write

case class OnMatch(props: Write) extends Write

case class OnCreate(props: Write) extends Write

case class SetProps(set: NonEmptyList[SetProps.One]) extends Write

object SetProps {
Expand Down Expand Up @@ -508,6 +514,18 @@ object CypherFragment {
opt <- part(if (optional) "OPTIONAL " else "")
wh <- part(wh0.map(w => s" WHERE $w").getOrElse(""))
} yield s"${opt}MATCH ${ps.mkString(", ")}$wh"
case Merge(pattern) =>
for {
ps <- partsSequence(pattern.toList)
} yield s"MERGE ${ps.mkString(", ")}"
case OnMatch(props) =>
for {
ps <- part(props)
} yield s"ON MATCH $ps"
case OnCreate(props) =>
for {
ps <- part(props)
} yield s"ON CREATE $ps"
case Unwind(expr, as) =>
for {
expr <- part(expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class CypherSyntaxPatternMacros(val c: blackbox.Context) {
def match_[R: WeakTypeTag](query: c.Expr[Node => CF.Query.Query0[R]]): c.Expr[CF.Query.Query0[R]] =
matchImpl(query, reify(false))

def merge[R: WeakTypeTag](query: c.Expr[Node => CF.Query.Query0[R]]): c.Expr[CF.Query.Query0[R]] =
mergeImpl(query)

def optional[R: WeakTypeTag](query: c.Expr[Node => CF.Query.Query0[R]]): c.Expr[CF.Query.Query0[R]] =
matchImpl(query, reify(true))

Expand Down Expand Up @@ -51,6 +54,16 @@ class CypherSyntaxPatternMacros(val c: blackbox.Context) {
}
}

protected def mergeImpl[R: WeakTypeTag](
query: c.Expr[Node => CF.Query.Query0[R]]
): c.Expr[CF.Query.Query0[R]] =
qImpl(query) { (guard, pattern) =>
if (!(guard.tree equalsStructure NoneTree)) c.abort(guard.tree.pos, "`if` guard is not allowed at Merge clause.")
reify {
CF.Clause.Merge(NonEmptyList.one[P](pattern.splice))
}
}

protected type Guard = c.Expr[Option[CF.Expr[Boolean]]]
protected type QPattern = c.Expr[P]
protected type QDefs = List[Tree]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import scala.language.{ dynamics, implicitConversions }
import cats.data.{ Ior, NonEmptyList }
import shapeless.{ ::, =:!=, |∨|, ops, HList, HNil, Refute, Unpack1 }

import com.arkondata.slothql.cypher.CypherFragment.Clause.Write
import com.arkondata.slothql.cypher.syntax.OnCreate.PartialCreateApply
import com.arkondata.slothql.cypher.syntax.OnMatch.PartialMatchApply
import com.arkondata.slothql.cypher.{ CypherFragment => CF }

package object syntax extends CypherSyntaxLowPriorityImplicits {
Expand All @@ -22,6 +25,63 @@ package object syntax extends CypherSyntaxLowPriorityImplicits {
macro CypherSyntaxPatternMacros.maybe[R]
}

/** To use inside MERGE clause
*/
object OnMatch extends {
type Prop = CF.Clause.SetProps.One

case class PartialMatchApply(clause: Write) {

def *>[R](res: Query[R]): Query[R] = CF.Query.Clause(
clause,
res
)

def *>[R](partial: PartialCreateApply): PartialMatchCreate = PartialMatchCreate(this, partial)

}

def apply(prop: Prop, props: Prop*): PartialMatchApply = PartialMatchApply(
CF.Clause.OnMatch(CF.Clause.SetProps(NonEmptyList(prop, props.toList)))
)

def apply(setNode: CF.Clause.SetNode): PartialMatchApply = PartialMatchApply(CF.Clause.OnMatch(setNode))

def apply(extendNode: CF.Clause.ExtendNode): PartialMatchApply = PartialMatchApply(
CF.Clause.OnMatch(extendNode)
)
}

case class PartialMatchCreate(matchP: PartialMatchApply, create: PartialCreateApply) {
def *>[R](res: Query[R]): Query[R] = matchP.*>(create.*>(res))
}

/** To use inside MERGE clause
*/
object OnCreate extends {
type Prop = CF.Clause.SetProps.One

case class PartialCreateApply(clause: Write) {

def *>[R](res: Query[R]): Query[R] = CF.Query.Clause(
clause,
res
)

def *>[R](partial: PartialMatchApply): PartialMatchCreate = PartialMatchCreate(partial, this)
}

def apply(prop: Prop, props: Prop*): PartialCreateApply = PartialCreateApply(
CF.Clause.OnCreate(CF.Clause.SetProps(NonEmptyList(prop, props.toList)))
)

def apply(setNode: CF.Clause.SetNode): PartialCreateApply = PartialCreateApply(CF.Clause.OnCreate(setNode))

def apply(extendNode: CF.Clause.ExtendNode): PartialCreateApply = PartialCreateApply(
CF.Clause.OnCreate(extendNode)
)
}

object With extends {

@compileTimeOnly("would have been replaced at With.apply")
Expand Down Expand Up @@ -170,7 +230,7 @@ package object syntax extends CypherSyntaxLowPriorityImplicits {
}

object Merge {
// def apply[R](query: Node => CF.Query[R]): CF.Query[R] = macro CypherSyntaxPatternMacros.merge[R]
def apply[R](query: Node => CF.Query.Query0[R]): CF.Query.Query0[R] = macro CypherSyntaxPatternMacros.merge[R]
}

object Update {
Expand Down Expand Up @@ -883,10 +943,21 @@ package object syntax extends CypherSyntaxLowPriorityImplicits {

implicit final class SetPropOps(e: CF.Expr[GraphElem]) {

def apply(fn: set.type => Update.Prop): Update.Prop = fn(set)

object set extends Dynamic {

@deprecated("Use select dynamic + partially like: n.set.id := value")
def updateDynamic[V](prop: String)(value: Expr[V]): Update.Prop =
CF.Clause.SetProps.One(e.asInstanceOf[Expr[Map[String, Any]]], prop, value)

case class PartialUpd(prop: String) {

def :=[V](value: Expr[V]): Update.Prop =
CF.Clause.SetProps.One(e.asInstanceOf[Expr[Map[String, Any]]], prop, value)
}

def selectDynamic(prop: String): PartialUpd = PartialUpd(prop)
}

def :=(props: Expr[Map[String, Expr[_]]]): CF.Clause.SetNode =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,109 @@ class CypherSyntaxWriteSpec extends CypherSyntaxBaseSpec {
},
"CREATE (:`Foo`{ `id`: 1 }) "
).returns[Unit]

"support merge nodes" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) "
).returns[Unit]

"support merge nodes with relations" in
test(
Merge { case (n @ Node("Foo", "bar" := 1, "another" := "stub")) - Rel("do") > Node("Bar") =>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) -[:`do`]-> (:`Bar`) "
).returns[Unit]

"support merge nodes with on match with set" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnMatch(n.set.id := lit(2), n.set.bar := lit("stub")) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON MATCH SET `n0`.`id` = 2, `n0`.`bar` = \"stub\" "
).returns[Unit]

"support merge nodes with on match with set node" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnMatch(n := lit(Map("id" -> lit(2)))) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON MATCH SET `n0` = {`id`: 2} "
).returns[Unit]

"support merge nodes with on match with extend node" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnMatch(n += lit(Map("id" -> lit(2)))) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON MATCH SET `n0` += {`id`: 2} "
).returns[Unit]

"support merge nodes with on create with set" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnCreate(n.set.id := lit(2), n.set.bar := lit("stub")) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON CREATE SET `n0`.`id` = 2, `n0`.`bar` = \"stub\" "
).returns[Unit]

"support merge nodes with on create with set node" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnCreate(n := lit(Map("id" -> lit(2)))) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON CREATE SET `n0` = {`id`: 2} "
).returns[Unit]

"support merge nodes with on create with extend node" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnCreate(n += lit(Map("id" -> lit(2)))) *>
returnNothing
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON CREATE SET `n0` += {`id`: 2} "
).returns[Unit]

"support merge nodes with on create and on match with return" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnMatch(n += lit(Map("id" -> lit(3)))) *>
OnCreate(n += lit(Map("id" -> lit(2)))) *>
n
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON MATCH SET `n0` += {`id`: 3} " +
"ON CREATE SET `n0` += {`id`: 2} " +
"RETURN `n0`"
).returns[GraphElem.Node]

"support merge nodes with on match and on create with return" in
test(
Merge { case n @ Node("Foo", "bar" := 1, "another" := "stub") =>
OnCreate(n += lit(Map("id" -> lit(2)))) *>
OnMatch(n += lit(Map("id" -> lit(3)))) *>
n
},
"MERGE (`n0`:`Foo`{ `bar`: 1, `another`: \"stub\" }) " +
"ON MATCH SET `n0` += {`id`: 3} " +
"ON CREATE SET `n0` += {`id`: 2} " +
"RETURN `n0`"
).returns[GraphElem.Node]

}

}

0 comments on commit daeb4be

Please sign in to comment.