question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature: parameter mapper for case classes

See original GitHub issue

Right now, we do not support case classes as parameters on a Cypher query. However, I think this would be very useful.

For example, consider the following case class.

final case class User(name: String, age: Int, ...) // Many more fields.

Then, as the number of fields grow, having to write something like this, becomes more & more tedious.

val user = User("Luis", 22, ...)

c"CREATE (user: User { name: ${user.name}, age: ${user.age}, ... })"

Rather, I would like to write it like this:

c"CREATE (user: User { $user })"

Or even:

c"CREATE $user"

Before diving in the implementation, I would like to see what others think about this.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
iRevivecommented, Nov 13, 2020

Recently @SystemFw assisted with the shapeless part.
I will contribute a version soon.

1reaction
syneducommented, Sep 24, 2019

This is what I’m doing in my project to achieve this kind of functionality. There’s surely a more elegant way to achieve this, and it is by no means complete, but this allows the generation of cypher strings to create nodes from arbitrary case classes:

import java.time.{Instant, ZonedDateTime}

import shapeless._
import shapeless.ops.hlist.{Mapper, ToTraversable}
import shapeless.ops.record._
import shapeless.tag._
import org.apache.commons.text.StringEscapeUtils


object QueryGenerator {

  object attrsPoly extends Poly1 {
    implicit def atTaggedSymbol[T] = at[Symbol with Tagged[T]](_.name)
  }

  object valuesPoly extends Poly1 {
    implicit def atInt= at[Int](identity)
    implicit def atLong = at[Long](identity)
    implicit def atDouble = at[Double](identity)
    implicit def atString= at[String](identity)
    implicit def atBoolean= at[Boolean](identity)
    implicit def atInstant = at[Instant](identity)
    implicit def atOptionString= at[Option[String]](_.getOrElse(None))
    implicit def atZonedDateTime = at[ZonedDateTime](identity)
  }

  def mapToCypher(v: Any): String = {
    v match {
      case s:String => "\"" + StringEscapeUtils.escapeJava(s) + "\""
      case i:Int => i.toString
      case l:Long => l.toString
      case d:Double => d.toString
      case b:Boolean => b.toString
      case i:Instant => "\"" + i.toString + "\""
      case t:ZonedDateTime => "\"" + t.toString + "\""
    }
  }

  def matchById(uuid: String, entityName: String) = {
    val nodeSymbol = entityName(0).toLower
    val s = "MATCH " + s"""($nodeSymbol:${entityName}) WHERE $nodeSymbol.uuid="$uuid" RETURN $nodeSymbol"""
    s
  }

  def createString[
  T,
  ClassRepr <: HList,
  KeysRepr <: HList,
  KeysMapperRepr <: HList,
  ValuesRepr <: HList,
  ValuesMapperRepr <: HList
  ](x: T, entityName: String)(
    implicit
    gen: LabelledGeneric.Aux[T, ClassRepr],
    keys: Keys.Aux[ClassRepr, KeysRepr],
    mapper: Mapper.Aux[attrsPoly.type, KeysRepr, KeysMapperRepr],
    keysTraversable: ToTraversable.Aux[KeysMapperRepr, List, String],
    values: Values.Aux[ClassRepr, ValuesRepr],
    valuesMapper: Mapper.Aux[valuesPoly.type, ValuesRepr, ValuesMapperRepr],
    noneTraversable: ToTraversable.Aux[ValuesMapperRepr, List, Any]
  ): String = {

    val attributes = keys().map(attrsPoly).toList
    val vals       = values.apply(gen.to(x)).map(valuesPoly).toList
    val nodeSymbol = entityName(0).toLower

    val queryParams = vals
      .zip(attributes)
      .filter { case (v, a) => !v.equals(None) }
      .map {
        case (v, a) =>
          s"""$a: ${mapToCypher(v)}"""
      }
      .mkString(", ")

    s"""CREATE ($nodeSymbol: ${entityName} { $queryParams }) RETURN $nodeSymbol"""
  }

  def updateString[
  T,
  ClassRepr <: HList,
  KeysRepr <: HList,
  KeysMapperRepr <: HList,
  ValuesRepr <: HList,
  ValuesMapperRepr <: HList
  ](x: T, entityName: String)(
    implicit
    gen: LabelledGeneric.Aux[T, ClassRepr],
    keys: Keys.Aux[ClassRepr, KeysRepr],
    mapper: Mapper.Aux[attrsPoly.type, KeysRepr, KeysMapperRepr],
    keysTraversable: ToTraversable.Aux[KeysMapperRepr, List, String],
    values: Values.Aux[ClassRepr, ValuesRepr],
    valuesMapper: Mapper.Aux[valuesPoly.type, ValuesRepr, ValuesMapperRepr],
    noneTraversable: ToTraversable.Aux[ValuesMapperRepr, List, Any]
  ): String = {

    val attributes = keys().map(attrsPoly).toList
    val vals       = values.apply(gen.to(x)).map(valuesPoly).toList

    val queryParams = vals
      .zip(attributes)
      .filter { case (v, a) => !v.equals(None) }
      .map {
        case (v, a) =>
          s"""$a: ${mapToCypher(v)}"""
      }
      .mkString(", ")

    val nodeSymbol = entityName(0).toLower
    val uuid = vals.zip(attributes).filter { case (v, a) => a=="uuid"}.head._1

    s"""MATCH ($nodeSymbol: ${entityName}) WHERE ${nodeSymbol}.uuid="${uuid}" SET ${nodeSymbol}= { $queryParams } RETURN $nodeSymbol"""
  }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Scala case class to map - Medium
Case classes in scala are like regular classes which can hold plain and immutable data objects. Case classes comes with following features.
Read more >
Case Classes | Tour of Scala
Case classes are good for modeling immutable data. ... When you create a case class with parameters, the parameters are public val s....
Read more >
Case class to map in Scala - Stack Overflow
Currently I'm working on a Scala server and defining the protocol and all its messages and exceptions using case classes, as they are...
Read more >
9. Objects, Case Classes, and Traits - Learning Scala [Book]
A popular use of self types is to add functionality with traits to classes that require input parameters. A trait cannot easily extend...
Read more >
Scala | Case Class and Case Object - GeeksforGeeks
A Case Class is just like a regular class, which has a feature for modeling unchangeable data. It is also constructive in pattern...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found