I'm writing a small data access library to help me use Cassandra prepared statements in a Scala program (its not open source but maybe one day). What I'd like to do is automatically generate a Java Array for the bind statement from the case class
com.datastax.driver.core
PreparedStatement...
public BoundStatement bind(Object... values);
So currently I have
case class Entity(foo:String, optionalBar:Option[String])
object Entity {
def toJArray(e:Entity) = { Array(e.foo, e.optionalBar.getOrElse(null)) }
}
val e1 = Entity("fred", Option("bill"))
val e2 = Entity("fred", None)
Entity.toJArray(e1)
res5: Array[String] = Array(fred, bill)
Entity.toJArray(e2)
res6: Array[String] = Array(fred, null)
The toJArray returns an Array I can use in the bind statement. The boiler plate code gets worse if there is a date or double or a java enum
new java.util.Date(createdOn)
scala.Double.box(price)
priceType.name
Is there a way of automatically generating the Array in Scala assuming the bind parameters have the same order as the case class fields?
EDIT Thanks to #srgfed01
Here's what I came up with (not complete) but allows me to do something like
val customer1 = Customer( "email", "name", None, Option(new Date), OrdStatus.New)
session.execute(populate(customer1, insert))
val customer2 = Customer( "email2", "name2", Option(22), Option(new Date), OrdStatus.Rejected)
session.execute(populate(customer2, insert))
using this function
def populate(state:Product, statement:PreparedStatement): BoundStatement = {
def set(bnd:BoundStatement, i:Int, aval:Any): Unit = {
aval match {
case v:Date => bnd.setDate(i, v)
case v:Int => bnd.setInt(i, v)
case v:Long => bnd.setLong(i, v)
case v:Double => bnd.setDouble(i, v)
case v:String => bnd.setString(i, v)
case null => bnd.setToNull(i)
case _ => bnd.setString(i, aval.toString)
}
}
val bnd = statement.bind
for(i <- 0 until state.productArity) {
state.productElement(i) match {
case op: Option[_] => set(bnd, i, op.getOrElse(null))
case v => set(bnd, i, v)
}
}
bnd
}
You can use productIterator call for your case class object:
case class Entity(foo: String, optionalBar: Option[String])
val e1 = Entity("fred", Option("bill"))
val e2 = Entity("fred", None)
def run(e: Entity): Array[Any] = e.productIterator
.map {
case op: Option[_] => op.getOrElse(null)
case v => v
}
.toArray
println(run(e1).mkString(" ")) // fred bill
println(run(e2).mkString(" ")) // fred null
Related
I have string like this.
val input = "perm1|0,perm2|2,perm2|1"
Desired output type is
val output: Set<String, Set<Long>>
and desired output value is
{perm1 [], perm2 [1,2] }
Here I need empty set if value is 0. I am using groupByTo like this
val output = input.split(",")
.map { it.split("|") }
.groupByTo(
mutableMapOf(),
keySelector = { it[0] },
valueTransform = { it[1].toLong() }
)
However the output structure is like this
MutableMap<String, MutableList<Long>>
and output is
{perm1 [0], perm2 [1,2] }
I am looking for best way to get desired output without using imperative style like this.
val output = mutableMapOf<String, Set<Long>>()
input.split(",").forEach {
val t = it.split("|")
if (t[1].contentEquals("0")) {
output[t[0]] = mutableSetOf()
}
if (output.containsKey(t[0]) && !t[1].contentEquals("0")) {
output[t[0]] = output[t[0]]!! + t[1].toLong()
}
if (!output.containsKey(t[0]) && !t[1].contentEquals("0")) {
output[t[0]] = mutableSetOf()
output[t[0]] = output[t[0]]!! + t[1].toLong()
}
}
You can simply use mapValues to convert values type from List<Long> to Set<Long>
var res : Map<String, Set<Long>> = input.split(",")
.map { it.split("|") }
.groupBy( {it[0]}, {it[1].toLong()} )
.mapValues { it.value.toSet() }
And of you want to replace list of 0 with empty set you can do it using if-expression
var res : Map<String, Set<Long>> = input.split(",")
.map { it.split("|") }
.groupBy( {it[0]}, {it[1].toLong()} )
.mapValues { if(it.value == listOf<Long>(0)) setOf() else it.value.toSet() }
Note that you cannot have Set with key-value pair, result will be of type map. Below code gives sorted set in the values.
val result = "perm1|0,perm2|2,perm2|1".split(",")
.map {
val split = it.split("|")
split[0] to split[1].toLong()
}.groupBy({ it.first }, { it.second })
.mapValues { it.value.toSortedSet() }
While the other answer(s) might be easier to grasp, they build immediate lists and maps in between, that are basically discarded right after the next operation. The following tries to omit that using splitToSequence (Sequences) and groupingBy (see Grouping bottom part):
val result: Map<String, Set<Long>> = input.splitToSequence(',')
.map { it.split('|', limit = 2) }
.groupingBy { it[0] }
.fold({ _, _ -> mutableSetOf<Long>() }) { _, accumulator, element ->
accumulator.also {
it.add(element[1].toLong()))
}
}
You can of course also filter out the addition of 0 in the set with a simple condition in the fold-step:
// alternative fold skipping 0-values, but keeping keys
.fold({ _, _ -> mutableSetOf<Long>() }) { _, accumulator, element ->
accumulator.also {
val value = element[1].toLong()
if (value != 0L)
it.add(value)
}
}
Alternatively also aggregating might be ok, but then your result-variable needs to change to Map<String, MutableSet<Long>>:
val result: Map<String, MutableSet<Long>> = // ...
.aggregate { _, accumulator, element, first ->
(if (first) mutableSetOf<Long>() else accumulator!!).also {
val value = element[1].toLong()
if (value != 0L)
it.add(value)
}
}
The problem is map data from class OldCompanyMovie(ocm : List[(company:String, movie:String, actor:String)])
to List[CompanyMovie(company:String, movies: List[Movies(movie:String, actors : List[actor:String])])]
Description :
by the actor in the same movie should be list of actor in movie like class Movies(movie: String, actors: List[actor:String])
same with list actor the movie should be list of movie in company like CompanyMovie(company: String, movies : List[movie:Movies]))
overall should be List[CompanyMovie(company:String, Movies(mv : List[movie:String, List[actor:String]]))]
*Update
I have tried for a haft of day so nothing be good the result is close to but might be bad performance
val companies: List[Company] = oldWorldMovieList.map { item =>
val moviesOfeachCompany: List[Option[Pattern]] = oldWorldMovieList.map { oldWML =>
if (item.company == oldWML.company) {
val actorsOfeachMovie: List[Option[String]] = oldWorldMovieList.map { oldWML2 =>
if (item.movie == oldWML2.movie) {
Some(oldWML2.actor)
} else None
}.distinct
Some(Pattern(item.movie, actorsOfeachMovie))
} else None
}.distinct
Company(item.company, moviesOfeachCompany)
}.distinct
val worldMovies: WorldMovies = WorldMovies(companies)
ps. I can't change the pattern of source data.
if it's Json It's be like this List[String, String, String]
[{"company":"Marvel","movie":"Avengers","actor":"ROBERT DOWNEY JR."},{"company":"Marvel","movie":"Avengers","actor":"CHRIS EVANS"},{"company":"Marvel","movie":"Avengers","actor":"MARK RUFFALO"},{"movie":"Marvel","movie":"Guardian of the galaxy","actor":"KAREN GILLAN"},{"company":"Marvel","movie":"Guardian of the galaxy","actor":"ZOE SALDANA"},{"company":"dc","movie":"Batman","actor":"CHRISTIAN BALE"},{"company":"dc","movie":"Batman","actor":"CHRISTOPHER REEVE"}]
After converted it's should be this
[{"company": "Marvel", "movies" : [{"movie": "Avengers", "actor": ["ROBERT DOWNEY JR.", "CHRIS EVANS", "MARK RUFFALO"]},{"movie": "Guardian of the galaxy", "actor": ["KAREN GILLAN", "ZOE SALDANA"]}]},{"company": "dc","movies" : [{"movie": "Batman", "actor": ["CHRISTOPHER REEVE", "CHRISTIAN BALE"]}]}]
Similar to Norwæ solution, but (IMHO) more simply and direct.
Also, since it also uses Iterators it may be more performant.
final case class OldModel(company: String, movie: String, actor: String)
final case class Company(name: String, movies: List[Movie])
final case class Movie(name: String, actors: List[String])
def toNewModel(oldData: List[OldModel]): List[Company] =
oldData
.groupBy(_.company)
.iterator
.map { case (company, group) =>
val movies =
group
.groupBy(_.movie)
.iterator
.map { case (movie, group) =>
val actors = group.map(_.actor)
Movie(movie, actors)
}.toList
Company(company, movies)
}.toList
My try. I'm not sure it's good practice.
val companies: List[Company] = oldWorldMovieList.map { item =>
val moviesOfeachCompany: List[Option[Pattern]] = oldWorldMovieList.map { oldWML =>
if (item.company == oldWML.company) {
val actorsOfeachMovie: List[Option[String]] = oldWorldMovieList.map { oldWML2 =>
if (item.movie == oldWML2.movie) {
Some(oldWML2.actor)
} else None
}.distinct
Some(Pattern(item.movie, actorsOfeachMovie))
} else None
}.distinct
Company(item.company, moviesOfeachCompany)
}.distinct
val worldMovies: WorldMovies = WorldMovies(companies)
I think you can do this quite elegantly by using groupBy. For example:
case class CompanyMovie(company: String, movies: Seq[Movie])
case class Movie(name: String, actors: Seq[String])
def convert(in: Seq[(String, String, String)]): Seq[CompanyMovie] = {
val byCompany = in.groupBy(_._1)
val byCompanyAndMovie = byCompany.mapValues(_.groupBy(_._2).toSeq)
byCompanyAndMovie.toSeq.map {
case (company, rawMovies) =>
val movies = rawMovies.map {
case (name, t) => Movie(name, t.map(_._3))
}
CompanyMovie(company, movies)
}
}
I have the following code(which I've simplified for the purpose of the question):
def openFile(directory: File): Try[String] = {
var input = ""
do {
input = readLine("prompt>")
println("alibaba.txt: 100%")
} while(input != ":quit")
}
The workflow is this:
the user gets a prompt:
prompt>
The user writes alibaba and then presses enter
The user sees:
alibaba.txt: 100%
prompt>
Everything:
prompt>alibaba
alibaba.txt: 100%
prompt>
Now, I want to test it.
I wrote the following code to test the user interaction:
test("simulate user input from readline") {
val prompt = new Prompt()
prompt.openFile(new File("../resources"))
val in = new ByteArrayInputStream("alibaba\n".getBytes)
System.setIn(in)
val scanner: Scanner = new Scanner(System.in)
val programResponse: String = scanner.nextLine()
println("programResponse: " + programResponse)
System.setIn(System.in)
assert(programResponse == "alibaba.txt: 100%")
}
However, I'm getting this result and I'm confused:
"alibaba[]" did not equal "alibaba[.txt: 100%]"
So how can I make the test simulate the user interaction?
How can I read what my program wrote?
IMO you should structure your code in a way that it is simply testable meaning that you should extract IO to higher abstractions.
For demonstration purpose I slightly modified your example to the following code:
import java.util.Scanner
object YourObject {
def consoleMethod(in: () => String = new Scanner(System.in).nextLine,
out: String => Unit = System.out.println): Unit = {
var input = ""
do {
out("prompt>")
input = in()
out("alibaba.txt: 100%")
} while (input != ":quit")
}
}
Let's break it down:
in: () => String = new Scanner(System.in).nextLine stands for our source of the user input. By default it is System.in.
out: String => Unit = System.out.println stands for our output source. By default it is System.out
Let's test the scenario when user entered ":quit" right away:
import org.scalatest.{Matchers, WordSpec}
class Test extends WordSpec with Matchers {
"We" should {
"simulate user input from readline" in {
var outputs = List.empty[String]
def accumulate(output: String): Unit = outputs = outputs :+ output
val in: () => String = () => ":quit"
val out: String => Unit = accumulate _
YourObject.consoleMethod(in, out)
outputs shouldBe List("prompt>", "alibaba.txt: 100%")
}
}
}
In case you want more control you can use scalamock:
In this case we can mock out in and out to behave as we need them to do.
val in = mock[() => String]
val out = mock[String => Unit]
Setting up source expectations:
(in.apply _).expects().anyNumberOfTimes().onCall(_ => ":quit")
Now we want to set up out to record whatever we are going to write:
var outputs = List.empty[String]
def accumulate(output: String): Unit = outputs = outputs :+ output
(out.apply _)
.expects(new FunctionAdapter1[String, Boolean](_ => true))
.anyNumberOfTimes()
.onCall(accumulate _)
Perfect, now let's set up the expectations:
outputs shouldBe List("prompt>", "alibaba.txt: 100%")
Full source code of the test:
import org.scalamock.function.FunctionAdapter1
import org.scalamock.scalatest.MockFactory
import org.scalatest.{Matchers, WordSpec}
class Test extends WordSpec with Matchers with MockFactory {
"We" should {
"simulate user input from readline" in {
val in = mock[() => String]
val out = mock[String => Unit]
(in.apply _).expects().anyNumberOfTimes().onCall(_ => ":quit")
var outputs = List.empty[String]
def accumulate(output: String): Unit = outputs = outputs :+ output
(out.apply _)
.expects(new FunctionAdapter1[String, Boolean](_ => true))
.anyNumberOfTimes()
.onCall(accumulate _)
YourObject.consoleMethod(in, out)
outputs shouldBe List("prompt>", "alibaba.txt: 100%")
}
}
}
In the library json4s, I intend to write a weakly typed deserializer for some malformed data (mostly the result of XML -> JSON conversions)
I want the dynamic program to get the type information of a given constructor (easy, e.g. 'Int'), apply it on a parsed string (e.g. "12.51"), automatically convert string into the type (in this case 12.51 should be typecasted to 13), then call the constructor.
I come up with the following implementation:
import org.json4s.JsonAST.{JDecimal, JDouble, JInt, JString}
import org.json4s._
import scala.reflect.ClassTag
object WeakNumDeserializer extends Serializer[Any] {
def cast[T](cc: Class[T], v: Any): Option[T] = {
implicit val ctg: ClassTag[T] = ClassTag(cc)
try {
Some(v.asInstanceOf[T])
}
catch {
case e: Throwable =>
None
}
}
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = Function.unlift{
tuple: (TypeInfo, JValue) =>
tuple match {
case (TypeInfo(cc, _), JInt(v)) =>
cast(cc, v)
case (TypeInfo(cc, _), JDouble(v)) =>
cast(cc, v)
case (TypeInfo(cc, _), JDecimal(v)) =>
cast(cc, v)
case (TypeInfo(cc, _), JString(v)) =>
cast(cc, v.toDouble)
case _ =>
None
}
}
}
However executing the above code on a real Double => Int case always yield IllegalArgumentException. Debugging reveals that the line:
v.asInstanceOf[T]
does not convert Double type to Int in memory, it remains as a Double number after type erasure, and after it is used in reflection to call the constructor it triggers the error.
How do I bypass this and make the reflective function figuring this out?
Is there a way to tell the Java compiler to actually convert it into an Int type?
UPDATE: to help validating your answer I've posted my test cases:
case class StrStr(
a: String,
b: String
)
case class StrInt(
a: String,
b: Int
)
case class StrDbl(
a: String,
b: Double
)
case class StrIntArray(
a: String,
b: Array[Int]
)
case class StrIntSeq(
a: String,
b: Seq[Int]
)
case class StrIntSet(
a: String,
b: Set[Int]
)
class WeakSerializerSuite extends FunSuite with TestMixin{
implicit val formats = DefaultFormats ++ Seq(StringToNumberDeserializer, ElementToArrayDeserializer)
import org.json4s.Extraction._
test("int to String") {
val d1 = StrInt("a", 12)
val json = decompose(d1)
val d2 = extract[StrStr](json)
d2.toString.shouldBe("StrStr(a,12)")
}
test("string to int") {
val d1 = StrStr("a", "12")
val json = decompose(d1)
val d2 = extract[StrInt](json)
d2.toString.shouldBe("StrInt(a,12)")
}
test("double to int") {
val d1 = StrDbl("a", 12.51)
val json = decompose(d1)
val d2 = extract[StrInt](json)
d2.toString.shouldBe("StrInt(a,12)")
}
test("int to int array") {
val d1 = StrInt("a", 12)
val json = decompose(d1)
val d2 = extract[StrIntArray](json)
d2.copy(b = null).toString.shouldBe("StrIntArray(a,null)")
}
test("int to int seq") {
val d1 = StrInt("a", 12)
val json = decompose(d1)
val d2 = extract[StrIntSeq](json)
d2.toString.shouldBe("StrIntSeq(a,List(12))")
}
test("int to int set") {
val d1 = StrInt("a", 12)
val json = decompose(d1)
val d2 = extract[StrIntSet](json)
d2.toString.shouldBe("StrIntSet(a,Set(12))")
}
test("string to int array") {
val d1 = StrStr("a", "12")
val json = decompose(d1)
val d2 = extract[StrIntArray](json)
d2.copy(b = null).toString.shouldBe("StrIntArray(a,null)")
}
test("string to int seq") {
val d1 = StrStr("a", "12")
val json = decompose(d1)
val d2 = extract[StrIntSeq](json)
d2.toString.shouldBe("StrIntSeq(a,List(12))")
}
test("string to int set") {
val d1 = StrStr("a", "12")
val json = decompose(d1)
val d2 = extract[StrIntSet](json)
d2.toString.shouldBe("StrIntSet(a,Set(12))")
}
I've found the first solution, TL:DR: its totally absurd & illogical, and absolutely full of boilerplates for a established strongly typed language. Please post your answer deemed any better:
abstract class WeakDeserializer[T: Manifest] extends Serializer[T] {
// final val tpe = implicitly[Manifest[T]]
// final val clazz = tpe.runtimeClass
// cannot serialize
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = PartialFunction.empty
}
object StringToNumberDeserializer extends WeakDeserializer[Any] {
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = {
case (TypeInfo(cc, _), JString(v)) =>
cc match {
case java.lang.Byte.TYPE => v.toByte
case java.lang.Short.TYPE => v.toShort
case java.lang.Character.TYPE => v.toInt.toChar
case java.lang.Integer.TYPE => v.toInt
case java.lang.Long.TYPE => v.toLong
case java.lang.Float.TYPE => v.toFloat
case java.lang.Double.TYPE => v.toDouble
case java.lang.Boolean.TYPE => v.toBoolean
//TODO: add boxed type
}
}
}
object ElementToArrayDeserializer extends WeakDeserializer[Any] {
val listClass = classOf[List[_]]
val seqClass = classOf[Seq[_]]
val setClass = classOf[Set[_]]
val arrayListClass = classOf[java.util.ArrayList[_]]
override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = {
case (ti# TypeInfo(this.listClass, _), jv) =>
List(extractInner(ti, jv, format))
case (ti# TypeInfo(this.seqClass, _), jv) =>
Seq(extractInner(ti, jv, format))
case (ti# TypeInfo(this.setClass, _), jv) =>
Set(extractInner(ti, jv, format))
case (ti# TypeInfo(this.arrayListClass, _), jv) =>
import scala.collection.JavaConverters._
new java.util.ArrayList[Any](List(extractInner(ti, jv, format)).asJava)
case (ti# TypeInfo(cc, _), jv) if cc.isArray =>
val a = Array(extractInner(ti, jv, format))
mkTypedArray(a, firstTypeArg(ti))
}
def mkTypedArray(a: Array[_], typeArg: ScalaType) = {
import java.lang.reflect.Array.{newInstance => newArray}
a.foldLeft((newArray(typeArg.erasure, a.length), 0)) { (tuple, e) => {
java.lang.reflect.Array.set(tuple._1, tuple._2, e)
(tuple._1, tuple._2 + 1)
}}._1
}
def extractInner(ti: TypeInfo, jv: JValue, format: Formats): Any = {
val result = extract(jv, firstTypeArg(ti))(format)
result
}
def firstTypeArg(ti: TypeInfo): ScalaType = {
val tpe = ScalaType.apply(ti)
val firstTypeArg = tpe.typeArgs.head
firstTypeArg
}
}
in below two sql query sql1 not selecting any row, and sql2 selecting only 1 for 111#k2.com
var ids="'111#k2.com','222#k2.com','333#k2.com','444#k2.com','555#k2.com','666#k2.com'"
val sql1 = SQL("SELECT id,point,privacy FROM `pointTable` WHERE state=1 and id in ({users})").on("users" -> ids)
sql1().map { row =>
val point = if (row[Boolean]("privacy")) { row[Double]("point").toString } else { "0" }
println(write(Map("id" -> row[String]("id"), "point" -> point)))
}
val sql2 = SQL("SELECT id,point,privacy FROM `pointTable` WHERE state=1 and id in (" + ids + ")")
sql2().map { row =>
val point = if (row[Boolean]("privacy")) { row[Double]("point").toString } else { "0" }
println(write(Map("id" -> row[String]("id"), "point" -> point)))
}
in phpmyadmin when i run this query manualy it returns 6 rows then why not working perfectly here.
i am using play framework 2.2 with scala 2.1
That's not going to work. Passing users though on is going to escape the entire string, so it's going to appear as one value instead of a list. Anorm in Play 2.3 actually allows you to pass lists as parameters, but here you'll have to work around that.
val ids: List[String] = List("111#k2.com", "222#k2.com", "333#k2.com")
val indexedIds: List[(String, Int)] = ids.zipWithIndex
// Create a bunch of parameter tokens for the IN clause.. {id_0}, {id_1}, ..
val tokens: String = indexedIds.map{ case (id, index) => s"{id_${index}}" }.mkString(", ")
// Create the parameter bindings for the tokens
val parameters = indexedIds.map{ case (id, index) => (s"id_${index}" -> toParameterValue(id)) }
val sql1 = SQL(s"SELECT id,point,privacy FROM `pointTable` WHERE state=1 and id in (${tokens})")
.on(parameters: _ *)