-
-
Notifications
You must be signed in to change notification settings - Fork 136
#804 - correct types for Set and Seq derived types with interpreted serde - basis for #803 #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d3ddaf1
a435adc
24bde95
2fa1bb0
ee38804
fb1c109
ae8b69a
0435c3a
52034b2
9e45d92
e7881c0
594fceb
5a01976
365b21f
4395c16
c792c05
f0d5f16
5bf68cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package frameless | ||
|
|
||
| import frameless.TypedEncoder.CollectionConversion | ||
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.expressions.codegen.{ | ||
| CodegenContext, | ||
| CodegenFallback, | ||
| ExprCode | ||
| } | ||
| import org.apache.spark.sql.catalyst.expressions.{ Expression, UnaryExpression } | ||
| import org.apache.spark.sql.types.{ DataType, ObjectType } | ||
|
|
||
| case class CollectionCaster[F[_], C[_], Y]( | ||
| child: Expression, | ||
| conversion: CollectionConversion[F, C, Y]) | ||
| extends UnaryExpression | ||
| with CodegenFallback { | ||
|
|
||
| protected def withNewChildInternal(newChild: Expression): Expression = | ||
| copy(child = newChild) | ||
|
|
||
| override def eval(input: InternalRow): Any = { | ||
| val o = child.eval(input).asInstanceOf[Object] | ||
| o match { | ||
| case col: F[Y] @unchecked => | ||
| conversion.convert(col) | ||
| case _ => o | ||
| } | ||
| } | ||
|
|
||
| override def dataType: DataType = child.dataType | ||
| } | ||
|
|
||
| case class SeqCaster[C[X] <: Iterable[X], Y](child: Expression) | ||
| extends UnaryExpression { | ||
|
|
||
| protected def withNewChildInternal(newChild: Expression): Expression = | ||
| copy(child = newChild) | ||
|
|
||
| // eval on interpreted works, fallback on codegen does not, e.g. with ColumnTests.asCol and Vectors, the code generated still has child of type Vector but child eval returns X2, which is not good | ||
| override def eval(input: InternalRow): Any = { | ||
| val o = child.eval(input).asInstanceOf[Object] | ||
| o match { | ||
| case col: Set[Y] @unchecked => | ||
| col.toSeq | ||
| case _ => o | ||
| } | ||
| } | ||
|
|
||
| def toSeqOr[T](isSet: => T, or: => T): T = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming is a bit surprising for me |
||
| child.dataType match { | ||
| case ObjectType(cls) | ||
| if classOf[scala.collection.Set[_]].isAssignableFrom(cls) => | ||
| isSet | ||
| case t => or | ||
| } | ||
|
|
||
| override def dataType: DataType = | ||
| toSeqOr(ObjectType(classOf[scala.collection.Seq[_]]), child.dataType) | ||
|
|
||
| override protected def doGenCode( | ||
| ctx: CodegenContext, | ||
| ev: ExprCode | ||
| ): ExprCode = | ||
| defineCodeGen(ctx, ev, c => toSeqOr(s"$c.toVector()", s"$c")) | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,10 @@ | ||
| package frameless | ||
|
|
||
| import java.math.BigInteger | ||
|
|
||
| import java.util.Date | ||
|
|
||
| import java.time.{ Duration, Instant, Period, LocalDate } | ||
|
|
||
| import java.time.{ Duration, Instant, LocalDate, Period } | ||
| import java.sql.Timestamp | ||
|
|
||
| import scala.reflect.ClassTag | ||
|
|
||
| import org.apache.spark.sql.FramelessInternals | ||
| import org.apache.spark.sql.FramelessInternals.UserDefinedType | ||
| import org.apache.spark.sql.{ reflection => ScalaReflection } | ||
|
|
@@ -22,10 +17,11 @@ import org.apache.spark.sql.catalyst.util.{ | |
| } | ||
| import org.apache.spark.sql.types._ | ||
| import org.apache.spark.unsafe.types.UTF8String | ||
|
|
||
| import shapeless._ | ||
| import shapeless.ops.hlist.IsHCons | ||
|
|
||
| import scala.collection.immutable.{ ListSet, TreeSet } | ||
|
|
||
| abstract class TypedEncoder[T]( | ||
| implicit | ||
| val classTag: ClassTag[T]) | ||
|
|
@@ -501,10 +497,76 @@ object TypedEncoder { | |
| override def toString: String = s"arrayEncoder($jvmRepr)" | ||
| } | ||
|
|
||
| implicit def collectionEncoder[C[X] <: Seq[X], T]( | ||
| /** | ||
| * Per #804 - when MapObjects is used in interpreted mode the type returned is Seq, not the derived type used in compilation | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention |
||
| * | ||
| * This type class offers extensible conversion for more specific types. By default Seq, List and Vector for Seq's and Set, TreeSet and ListSet are supported. | ||
| * | ||
| * @tparam C | ||
| */ | ||
| trait CollectionConversion[F[_], C[_], Y] extends Serializable { | ||
| def convert(c: F[Y]): C[Y] | ||
| } | ||
|
|
||
| object CollectionConversion { | ||
|
|
||
| implicit def seqToSeq[Y] = new CollectionConversion[Seq, Seq, Y] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| override def convert(c: Seq[Y]): Seq[Y] = | ||
| c match { | ||
| // Stream is produced | ||
| case _: Stream[Y] @unchecked => c.toVector.toSeq | ||
| case _ => c | ||
| } | ||
| } | ||
|
|
||
| implicit def seqToVector[Y] = new CollectionConversion[Seq, Vector, Y] { | ||
| override def convert(c: Seq[Y]): Vector[Y] = c.toVector | ||
| } | ||
|
|
||
| implicit def seqToList[Y] = new CollectionConversion[Seq, List, Y] { | ||
| override def convert(c: Seq[Y]): List[Y] = c.toList | ||
| } | ||
|
|
||
| implicit def setToSet[Y] = new CollectionConversion[Set, Set, Y] { | ||
| override def convert(c: Set[Y]): Set[Y] = c | ||
| } | ||
|
|
||
| implicit def setToTreeSet[Y]( | ||
| implicit | ||
| ordering: Ordering[Y] | ||
| ) = new CollectionConversion[Set, TreeSet, Y] { | ||
|
|
||
| override def convert(c: Set[Y]): TreeSet[Y] = | ||
| TreeSet.newBuilder.++=(c).result() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the dot syntax? |
||
| } | ||
|
|
||
| implicit def setToListSet[Y] = new CollectionConversion[Set, ListSet, Y] { | ||
|
|
||
| override def convert(c: Set[Y]): ListSet[Y] = | ||
| ListSet.newBuilder.++=(c).result() | ||
| } | ||
| } | ||
|
|
||
| implicit def seqEncoder[C[X] <: Seq[X], T]( | ||
| implicit | ||
| i0: Lazy[RecordFieldEncoder[T]], | ||
| i1: ClassTag[C[T]], | ||
| i2: CollectionConversion[Seq, C, T] | ||
| ) = collectionEncoder[Seq, C, T] | ||
|
|
||
| implicit def setEncoder[C[X] <: Set[X], T]( | ||
| implicit | ||
| i0: Lazy[RecordFieldEncoder[T]], | ||
| i1: ClassTag[C[T]], | ||
| i2: CollectionConversion[Set, C, T] | ||
| ) = collectionEncoder[Set, C, T] | ||
|
|
||
| def collectionEncoder[O[_], C[X], T]( | ||
| implicit | ||
| i0: Lazy[RecordFieldEncoder[T]], | ||
| i1: ClassTag[C[T]] | ||
| i1: ClassTag[C[T]], | ||
| i2: CollectionConversion[O, C, T] | ||
| ): TypedEncoder[C[T]] = new TypedEncoder[C[T]] { | ||
| private lazy val encodeT = i0.value.encoder | ||
|
|
||
|
|
@@ -521,38 +583,31 @@ object TypedEncoder { | |
| if (ScalaReflection.isNativeType(enc.jvmRepr)) { | ||
| NewInstance(classOf[GenericArrayData], path :: Nil, catalystRepr) | ||
| } else { | ||
| MapObjects(enc.toCatalyst, path, enc.jvmRepr, encodeT.nullable) | ||
| // converts to Seq, both Set and Seq handling must convert to Seq first | ||
| MapObjects( | ||
| enc.toCatalyst, | ||
| SeqCaster(path), | ||
| enc.jvmRepr, | ||
| encodeT.nullable | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| def fromCatalyst(path: Expression): Expression = | ||
| MapObjects( | ||
| i0.value.fromCatalyst, | ||
| path, | ||
| encodeT.catalystRepr, | ||
| encodeT.nullable, | ||
| Some(i1.runtimeClass) // This will cause MapObjects to build a collection of type C[_] directly | ||
| ) | ||
| CollectionCaster[O, C, T]( | ||
| MapObjects( | ||
| i0.value.fromCatalyst, | ||
| path, | ||
| encodeT.catalystRepr, | ||
| encodeT.nullable, | ||
| Some(i1.runtimeClass) // This will cause MapObjects to build a collection of type C[_] directly when compiling | ||
| ), | ||
| implicitly[CollectionConversion[O, C, T]] | ||
| ) // This will convert Seq to the appropriate C[_] when eval'ing. | ||
|
|
||
| override def toString: String = s"collectionEncoder($jvmRepr)" | ||
| } | ||
|
|
||
| /** | ||
| * @param i1 implicit lazy `RecordFieldEncoder[T]` to encode individual elements of the set. | ||
| * @param i2 implicit `ClassTag[Set[T]]` to provide runtime information about the set type. | ||
| * @tparam T the element type of the set. | ||
| * @return a `TypedEncoder` instance for `Set[T]`. | ||
| */ | ||
| implicit def setEncoder[T]( | ||
| implicit | ||
| i1: shapeless.Lazy[RecordFieldEncoder[T]], | ||
| i2: ClassTag[Set[T]] | ||
| ): TypedEncoder[Set[T]] = { | ||
| implicit val inj: Injection[Set[T], Seq[T]] = Injection(_.toSeq, _.toSet) | ||
|
|
||
| TypedEncoder.usingInjection | ||
| } | ||
|
|
||
| /** | ||
| * @tparam A the key type | ||
| * @tparam B the value type | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asInstanceOfrequired with the immediatematch?