All Downloads are FREE. Search and download functionalities are using the official Maven repository.

za.co.absa.db.balta.implicits.QueryResultRowImplicits.scala Maven / Gradle / Ivy

/*
 * Copyright 2023 ABSA Group Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package za.co.absa.db.balta.implicits

import za.co.absa.db.balta.classes.QueryResultRow
import za.co.absa.db.balta.implicits.OptionImplicits.OptionEnhancements
import za.co.absa.db.mag.naming.NamingConvention

import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._

object QueryResultRowImplicits {

  def isOptionType(typeToCheck: Type): Boolean = {
    typeToCheck <:< typeOf[Option[_]]
  }

  /**
   * This class provides an implicit conversion from QueryResultRow to a case class
   * This logic placed in an implicit class to prevent polluting the QueryResultRow class with too much unrelated logic
   * @param row The QueryResultRow to convert
   */
  implicit class ProductTypeConvertor(val row: QueryResultRow) extends AnyVal {

    /**
     * Converts a QueryResultRow to a case class
     * @param namingConvention  - The naming convention to use when converting field names to column names
     * @tparam T                - The case class to convert to
     * @return                  - The case class instance filled with data from the QueryResultRow
     */
    def toProductType[T <: Product : TypeTag](implicit namingConvention: NamingConvention): T = {
      val tpe = typeOf[T]
      val defaultConstructor = getConstructor(tpe)
      val constructorMirror = getConstructorMirror(tpe, defaultConstructor)
      val params = readParamsFromRow(defaultConstructor)
      constructorMirror(params: _*).asInstanceOf[T]
    }

    private def getConstructor(tpe: Type): MethodSymbol = {
      val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR)
      val defaultConstructor =
        if (constructorSymbol.isMethod) constructorSymbol.asMethod
        else {
          val ctors = constructorSymbol.asTerm.alternatives
          ctors.map(_.asMethod).find(_.isPrimaryConstructor).get
        }
      defaultConstructor
    }

    private def getConstructorMirror(tpe: Type, constructor: MethodSymbol): MethodMirror = {
      val classSymbol = tpe.typeSymbol.asClass
      val classMirror = currentMirror.reflectClass(classSymbol)
      val constructorMirror = classMirror.reflectConstructor(constructor)
      constructorMirror
    }

    private def readParamsFromRow(constructor: MethodSymbol)(implicit namingConvention: NamingConvention): List[Any] = {
      constructor.paramLists.flatten.map { param =>
        val name = param.name.decodedName.toString
        val paramType = param.typeSignature
        val columnLabel = namingConvention.stringPerConvention(name)
        getParamValue(columnLabel, paramType)
      }

    }

    private def getParamValue[T: TypeTag](columnLabel: String, expectedType: Type): Any = {
      val value = row(columnLabel)
      if (isOptionType(expectedType)) {
        value
      } else {
        value.getOrThrow(new NullPointerException(s"Column '$columnLabel' is null"))
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy