@@ -63,7 +63,7 @@ object ScalaReflection extends ScalaReflection {
63
63
case t if t <:< definitions.BooleanTpe => BooleanType
64
64
case t if t <:< localTypeOf[Array [Byte ]] => BinaryType
65
65
case _ =>
66
- val className : String = tpe.erasure.typeSymbol.asClass.fullName
66
+ val className = getClassNameFromType( tpe)
67
67
className match {
68
68
case " scala.Array" =>
69
69
val TypeRef (_, _, Seq (elementType)) = tpe
@@ -320,9 +320,23 @@ object ScalaReflection extends ScalaReflection {
320
320
}
321
321
}
322
322
323
- /** Returns expressions for extracting all the fields from the given type. */
323
+ /**
324
+ * Returns expressions for extracting all the fields from the given type.
325
+ *
326
+ * If the given type is not supported, i.e. there is no encoder can be built for this type,
327
+ * an [[UnsupportedOperationException ]] will be thrown with detailed error message to explain
328
+ * the type path walked so far and which class we are not supporting.
329
+ * There are 4 kinds of type path:
330
+ * * the root type: `root class: "abc.xyz.MyClass"`
331
+ * * the value type of [[Option ]]: `option value class: "abc.xyz.MyClass"`
332
+ * * the element type of [[Array ]] or [[Seq ]]: `array element class: "abc.xyz.MyClass"`
333
+ * * the field of [[Product ]]: `field (class: "abc.xyz.MyClass", name: "myField")`
334
+ */
324
335
def extractorsFor [T : TypeTag ](inputObject : Expression ): CreateNamedStruct = {
325
- extractorFor(inputObject, localTypeOf[T ]) match {
336
+ val tpe = localTypeOf[T ]
337
+ val clsName = getClassNameFromType(tpe)
338
+ val walkedTypePath = s """ - root class: " ${clsName}" """ :: Nil
339
+ extractorFor(inputObject, tpe, walkedTypePath) match {
326
340
case s : CreateNamedStruct => s
327
341
case other => CreateNamedStruct (expressions.Literal (" value" ) :: other :: Nil )
328
342
}
@@ -331,7 +345,28 @@ object ScalaReflection extends ScalaReflection {
331
345
/** Helper for extracting internal fields from a case class. */
332
346
private def extractorFor (
333
347
inputObject : Expression ,
334
- tpe : `Type`): Expression = ScalaReflectionLock .synchronized {
348
+ tpe : `Type`,
349
+ walkedTypePath : Seq [String ]): Expression = ScalaReflectionLock .synchronized {
350
+
351
+ def toCatalystArray (input : Expression , elementType : `Type`): Expression = {
352
+ val externalDataType = dataTypeFor(elementType)
353
+ val Schema (catalystType, nullable) = silentSchemaFor(elementType)
354
+ if (isNativeType(catalystType)) {
355
+ NewInstance (
356
+ classOf [GenericArrayData ],
357
+ input :: Nil ,
358
+ dataType = ArrayType (catalystType, nullable))
359
+ } else {
360
+ val clsName = getClassNameFromType(elementType)
361
+ val newPath = s """ - array element class: " $clsName" """ +: walkedTypePath
362
+ // `MapObjects` will run `extractorFor` lazily, we need to eagerly call `extractorFor` here
363
+ // to trigger the type check.
364
+ extractorFor(inputObject, elementType, newPath)
365
+
366
+ MapObjects (extractorFor(_, elementType, newPath), input, externalDataType)
367
+ }
368
+ }
369
+
335
370
if (! inputObject.dataType.isInstanceOf [ObjectType ]) {
336
371
inputObject
337
372
} else {
@@ -378,15 +413,16 @@ object ScalaReflection extends ScalaReflection {
378
413
379
414
// For non-primitives, we can just extract the object from the Option and then recurse.
380
415
case other =>
381
- val className : String = optType.erasure.typeSymbol.asClass.fullName
416
+ val className = getClassNameFromType( optType)
382
417
val classObj = Utils .classForName(className)
383
418
val optionObjectType = ObjectType (classObj)
419
+ val newPath = s """ - option value class: " $className" """ +: walkedTypePath
384
420
385
421
val unwrapped = UnwrapOption (optionObjectType, inputObject)
386
422
expressions.If (
387
423
IsNull (unwrapped),
388
- expressions.Literal .create(null , schemaFor (optType).dataType),
389
- extractorFor(unwrapped, optType))
424
+ expressions.Literal .create(null , silentSchemaFor (optType).dataType),
425
+ extractorFor(unwrapped, optType, newPath ))
390
426
}
391
427
392
428
case t if t <:< localTypeOf[Product ] =>
@@ -412,7 +448,10 @@ object ScalaReflection extends ScalaReflection {
412
448
val fieldName = p.name.toString
413
449
val fieldType = p.typeSignature.substituteTypes(formalTypeArgs, actualTypeArgs)
414
450
val fieldValue = Invoke (inputObject, fieldName, dataTypeFor(fieldType))
415
- expressions.Literal (fieldName) :: extractorFor(fieldValue, fieldType) :: Nil
451
+ val clsName = getClassNameFromType(fieldType)
452
+ val newPath = s """ - field (class: " $clsName", name: " $fieldName") """ +: walkedTypePath
453
+
454
+ expressions.Literal (fieldName) :: extractorFor(fieldValue, fieldType, newPath) :: Nil
416
455
})
417
456
418
457
case t if t <:< localTypeOf[Array [_]] =>
@@ -500,23 +539,11 @@ object ScalaReflection extends ScalaReflection {
500
539
Invoke (inputObject, " booleanValue" , BooleanType )
501
540
502
541
case other =>
503
- throw new UnsupportedOperationException (s " Extractor for type $other is not supported " )
542
+ throw new UnsupportedOperationException (
543
+ s " No Encoder found for $tpe\n " + walkedTypePath.mkString(" \n " ))
504
544
}
505
545
}
506
546
}
507
-
508
- private def toCatalystArray (input : Expression , elementType : `Type`): Expression = {
509
- val externalDataType = dataTypeFor(elementType)
510
- val Schema (catalystType, nullable) = schemaFor(elementType)
511
- if (isNativeType(catalystType)) {
512
- NewInstance (
513
- classOf [GenericArrayData ],
514
- input :: Nil ,
515
- dataType = ArrayType (catalystType, nullable))
516
- } else {
517
- MapObjects (extractorFor(_, elementType), input, externalDataType)
518
- }
519
- }
520
547
}
521
548
522
549
/**
@@ -561,7 +588,7 @@ trait ScalaReflection {
561
588
562
589
/** Returns a catalyst DataType and its nullability for the given Scala Type using reflection. */
563
590
def schemaFor (tpe : `Type`): Schema = ScalaReflectionLock .synchronized {
564
- val className : String = tpe.erasure.typeSymbol.asClass.fullName
591
+ val className = getClassNameFromType( tpe)
565
592
tpe match {
566
593
case t if Utils .classIsLoadable(className) &&
567
594
Utils .classForName(className).isAnnotationPresent(classOf [SQLUserDefinedType ]) =>
@@ -637,6 +664,23 @@ trait ScalaReflection {
637
664
}
638
665
}
639
666
667
+ /**
668
+ * Returns a catalyst DataType and its nullability for the given Scala Type using reflection.
669
+ *
670
+ * Unlike `schemaFor`, this method won't throw exception for un-supported type, it will return
671
+ * `NullType` silently instead.
672
+ */
673
+ private def silentSchemaFor (tpe : `Type`): Schema = try {
674
+ schemaFor(tpe)
675
+ } catch {
676
+ case _ : UnsupportedOperationException => Schema (NullType , nullable = true )
677
+ }
678
+
679
+ /** Returns the full class name for a type. */
680
+ private def getClassNameFromType (tpe : `Type`): String = {
681
+ tpe.erasure.typeSymbol.asClass.fullName
682
+ }
683
+
640
684
/**
641
685
* Returns classes of input parameters of scala function object.
642
686
*/
0 commit comments