Skip to content

Commit 22dfa6e

Browse files
Reproduce derivation of generic case class issue & fix it for Scala 2
1 parent 1ba841b commit 22dfa6e

File tree

4 files changed

+138
-7
lines changed

4 files changed

+138
-7
lines changed

modules/scala-api/src/main/scala-2/org/apache/flinkx/api/LowPrioImplicits.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package org.apache.flinkx.api
22

3-
import org.apache.flinkx.api.serializer.{CoproductSerializer, ScalaCaseClassSerializer, ScalaCaseObjectSerializer}
4-
import org.apache.flinkx.api.typeinfo.{CoproductTypeInformation, ProductTypeInformation}
53
import magnolia1.{CaseClass, Magnolia, SealedTrait}
64
import org.apache.flink.api.common.ExecutionConfig
75
import org.apache.flink.api.common.typeinfo.TypeInformation
6+
import org.apache.flinkx.api.serializer.{CoproductSerializer, ScalaCaseClassSerializer, ScalaCaseObjectSerializer}
7+
import org.apache.flinkx.api.typeinfo.{CoproductTypeInformation, ProductTypeInformation}
88

99
import scala.collection.mutable
1010
import scala.language.experimental.macros
@@ -21,7 +21,7 @@ private[api] trait LowPrioImplicits {
2121
def join[T <: Product: ClassTag: TypeTag](
2222
ctx: CaseClass[TypeInformation, T]
2323
): TypeInformation[T] = {
24-
val cacheKey = typeName(ctx.typeName)
24+
val cacheKey = typeName[T]
2525
cache.get(cacheKey) match {
2626
case Some(cached) => cached.asInstanceOf[TypeInformation[T]]
2727
case None =>
@@ -45,8 +45,8 @@ private[api] trait LowPrioImplicits {
4545
}
4646
}
4747

48-
def split[T: ClassTag](ctx: SealedTrait[TypeInformation, T]): TypeInformation[T] = {
49-
val cacheKey = typeName(ctx.typeName)
48+
def split[T: ClassTag : TypeTag](ctx: SealedTrait[TypeInformation, T]): TypeInformation[T] = {
49+
val cacheKey = typeName[T]
5050
cache.get(cacheKey) match {
5151
case Some(cached) => cached.asInstanceOf[TypeInformation[T]]
5252
case None =>
@@ -61,8 +61,7 @@ private[api] trait LowPrioImplicits {
6161
}
6262
}
6363

64-
private def typeName(tn: magnolia1.TypeName): String =
65-
s"${tn.full}[${tn.typeArguments.map(typeName).mkString(",")}]"
64+
private def typeName[T: TypeTag]: String = typeOf[T].toString
6665

6766
implicit def deriveTypeInformation[T]: TypeInformation[T] = macro Magnolia.gen[T]
6867
}

modules/scala-api/src/main/scala-3/org/apache/flinkx/api/LowPrioImplicits.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ private[api] trait LowPrioImplicits extends TaggedDerivation[TypeInformation]:
2727
typeTag: TypeTag[T]
2828
): Typeclass[T] =
2929
val cacheKey = typeName(ctx.typeInfo)
30+
println(cacheKey)
3031
cache.get(cacheKey) match
3132
case Some(cached) =>
3233
cached.asInstanceOf[TypeInformation[T]]
@@ -55,6 +56,7 @@ private[api] trait LowPrioImplicits extends TaggedDerivation[TypeInformation]:
5556
typeTag: TypeTag[T]
5657
): Typeclass[T] =
5758
val cacheKey = typeName(ctx.typeInfo)
59+
println(cacheKey)
5860
cache.get(cacheKey) match
5961
case Some(cached) =>
6062
cached.asInstanceOf[TypeInformation[T]]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.apache.flinkx.api
2+
3+
import org.apache.flink.api.common.typeinfo.TypeInformation
4+
import org.apache.flinkx.api.serializers._
5+
import org.apache.flinkx.api.typeinfo.ProductTypeInformation
6+
import org.scalatest.flatspec.AnyFlatSpec
7+
import org.scalatest.matchers.should
8+
9+
// This import is not available in Scala 3
10+
import scala.reflect.runtime.universe.TypeTag
11+
12+
class GenericCaseClassScala2Test extends AnyFlatSpec with should.Matchers {
13+
14+
import GenericCaseClassScala2Test._
15+
16+
"Both TypeInformation of Animal Basket" should "have their respective TypeInformation of Animal" in {
17+
typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[Cat](classOf[Cat])
18+
// This second call is failing without the fix: TypeInformation of DogBasket hold a wrong TypeInformation of Cat
19+
typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[Dog](classOf[Dog])
20+
}
21+
22+
def typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[A <: Animal: TypeTag: TypeInformation](
23+
aClass: Class[A]
24+
): Unit = {
25+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Cat[] => OK
26+
val catInfo: TypeInformation[Cat] = implicitly[TypeInformation[Cat]]
27+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Dog[] => OK
28+
val dogInfo: TypeInformation[Dog] = implicitly[TypeInformation[Dog]]
29+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Cat[] or Dog[] => OK
30+
val aInfo: TypeInformation[A] = implicitly[TypeInformation[A]]
31+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.Cat[]] => OK
32+
val catBasketInfo: TypeInformation[Basket[Cat]] = implicitly[TypeInformation[Basket[Cat]]]
33+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.Dog[]] => OK
34+
val dogBasketInfo: TypeInformation[Basket[Dog]] = implicitly[TypeInformation[Basket[Dog]]]
35+
// without the fix: cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal.A[]] => issue
36+
// with the fix: cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.Cat] or Dog => OK
37+
val aBasketInfo: TypeInformation[Basket[A]] = implicitly[TypeInformation[Basket[A]]]
38+
39+
if (classOf[Cat].isAssignableFrom(aClass)) {
40+
aInfo should be theSameInstanceAs catInfo
41+
aBasketInfo should be theSameInstanceAs catBasketInfo // Fails without the fix => cache miss
42+
}
43+
if (classOf[Dog].isAssignableFrom(aClass)) {
44+
aInfo should be theSameInstanceAs dogInfo
45+
aBasketInfo should be theSameInstanceAs dogBasketInfo // Fails without the fix => cache miss
46+
}
47+
catBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs catInfo
48+
dogBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs dogInfo
49+
50+
// This check is failing on second call without the fix: TypeInformation of DogBasket hold a wrong TypeInformation of Cat
51+
aBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs aInfo
52+
}
53+
54+
}
55+
56+
object GenericCaseClassScala2Test {
57+
58+
sealed trait Animal extends Product {
59+
def name: String
60+
}
61+
62+
case class Cat(name: String) extends Animal
63+
case class Dog(name: String) extends Animal
64+
65+
case class Basket[A <: Animal](animal: A)
66+
67+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.apache.flinkx.api
2+
3+
import org.apache.flink.api.common.typeinfo.TypeInformation
4+
import org.apache.flinkx.api.serializers._
5+
import org.apache.flinkx.api.typeinfo.ProductTypeInformation
6+
import org.scalatest.flatspec.AnyFlatSpec
7+
import org.scalatest.matchers.should
8+
9+
class GenericCaseClassScala3Test extends AnyFlatSpec with should.Matchers {
10+
11+
import GenericCaseClassScala3Test._
12+
13+
"Both TypeInformation of Animal Basket" should "have their respective TypeInformation of Animal" in {
14+
typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[Cat](classOf[Cat])
15+
// This second call is failing: TypeInformation of DogBasket hold a wrong TypeInformation of Cat
16+
typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[Dog](classOf[Dog])
17+
}
18+
19+
def typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal[A <: Animal: TypeTag: TypeInformation](
20+
aClass: Class[A]
21+
): Unit = {
22+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Cat[] => OK
23+
val catInfo: TypeInformation[Cat] = implicitly[TypeInformation[Cat]]
24+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Dog[] => OK
25+
val dogInfo: TypeInformation[Dog] = implicitly[TypeInformation[Dog]]
26+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Cat[] or Dog[] => OK
27+
val aInfo: TypeInformation[A] = implicitly[TypeInformation[A]]
28+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.Cat[]] => OK
29+
val catBasketInfo: TypeInformation[Basket[Cat]] = implicitly[TypeInformation[Basket[Cat]]]
30+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.Dog[]] => OK
31+
val dogBasketInfo: TypeInformation[Basket[Dog]] = implicitly[TypeInformation[Basket[Dog]]]
32+
// cacheKey=org.apache.flinkx.api.GenericCaseClassTest.Basket[org.apache.flinkx.api.GenericCaseClassTest.typeInformationOfAnimalBasketShouldHaveATypeInformationOfAnimal.A[]] => issue
33+
val aBasketInfo: TypeInformation[Basket[A]] = implicitly[TypeInformation[Basket[A]]]
34+
35+
if (classOf[Cat].isAssignableFrom(aClass)) {
36+
aInfo should be theSameInstanceAs catInfo
37+
// aBasketInfo should be theSameInstanceAs catBasketInfo // Fails => cache miss
38+
}
39+
if (classOf[Dog].isAssignableFrom(aClass)) {
40+
aInfo should be theSameInstanceAs dogInfo
41+
// aBasketInfo should be theSameInstanceAs dogBasketInfo // Fails => cache miss
42+
}
43+
catBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs catInfo
44+
dogBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs dogInfo
45+
46+
// This check is failing on second call: TypeInformation of DogBasket hold a wrong TypeInformation of Cat
47+
aBasketInfo.asInstanceOf[ProductTypeInformation[A]].getFieldTypes()(0) should be theSameInstanceAs aInfo
48+
}
49+
50+
}
51+
52+
object GenericCaseClassScala3Test {
53+
54+
sealed trait Animal extends Product {
55+
def name: String
56+
}
57+
58+
case class Cat(name: String) extends Animal
59+
case class Dog(name: String) extends Animal
60+
61+
case class Basket[A <: Animal](animal: A)
62+
63+
}

0 commit comments

Comments
 (0)