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

za.co.absa.db.balta.classes.QueryResultRow.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.classes

import QueryResultRow._
import za.co.absa.db.balta.implicits.MapImplicits.MapEnhancements

import java.sql
import java.sql.{Date, ResultSet, ResultSetMetaData, Time, Types}
import java.time.{Instant, LocalDateTime, OffsetDateTime, OffsetTime}
import java.util.UUID

/**
 * This is a row of a query result. It allows to safely extract values from the row by column name.
 *
 * @param rowNumber     - the number of the row in the result set
 * @param fields        - the values of the row
 * @param columnLabels  - the names of the columns; class uses `columnLabel(s)` to refer to the column names in accordance
 *                      to `java.sql.ResultSet`, which is is build around and that despite that aliases are not expected
 *                      to appear here
 */
class QueryResultRow private[classes](val rowNumber: Int,
                                      private val fields: Vector[Option[Object]],
                                      private val columnLabels: FieldNames) {

  def columnCount: Int = fields.length
  def columnNumber(columnLabel: String): Int = {
    val actualLabel = columnLabel.toLowerCase
    columnLabels.getOrThrow(actualLabel, new NoSuchElementException(s"Column '$actualLabel' not found"))
  }

  /**
   * Extracts a value from the row by column number.
   * @param column  - the number of the column, 1 based
   * @return        - the value stored in the column, type `Any` is for warningless comparison with any type
   */
  def apply(column: Int): Option[Any] = getObject(column - 1)
  /**
   * Extracts a value from the row by column name.
   * @param columnLabel - the name of the column
   * @return            - the value stored in the column, type `Any` is for warningless comparison with any type
   */
  def apply(columnLabel: String): Option[Any] = getObject(columnNumber(columnLabel))

  def getAs[T](column: Int, transformer: TransformerFnc[T]): Option[T] = getObject(column).map(transformer)
  def getAs[T](column: Int): Option[T] = getObject(column)map(_.asInstanceOf[T])

  def getAs[T](columnLabel: String, transformer: TransformerFnc[T]): Option[T] = getAs(columnNumber(columnLabel), transformer)
  def getAs[T](columnLabel: String): Option[T] = getObject(columnNumber(columnLabel)).map(_.asInstanceOf[T])

  def getObject(column: Int): Option[Object] = fields(column - 1)
  def getObject(columnLabel: String): Option[Object] = getObject(columnNumber(columnLabel))

  def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, {item: Object => item.asInstanceOf[Boolean]})
  def getBoolean(columnLabel: String): Option[Boolean] = getBoolean(columnNumber(columnLabel))

  def getChar(column: Int): Option[Char] = {
    getString(column) match {
      case Some(value) =>
        if (value.isEmpty) None else Some(value.charAt(0))
      case None =>
        None
    }
  }
  def getChar(columnLabel: String): Option[Char] = getChar(columnNumber(columnLabel))

  def getString(column: Int): Option[String] = getAs(column: Int, {item: Object => item.asInstanceOf[String]})
  def getString(columnLabel: String): Option[String] = getString(columnNumber(columnLabel))

  def getInt(column: Int): Option[Int] = getAs(column: Int, {item: Object => item.asInstanceOf[Int]})
  def getInt(columnLabel: String): Option[Int] = getInt(columnNumber(columnLabel))

  def getLong(column: Int): Option[Long] = getAs(column: Int, {item: Object => item.asInstanceOf[Long]})
  def getLong(columnLabel: String): Option[Long] = getLong(columnNumber(columnLabel))

  def getDouble(column: Int): Option[Double] = getAs(column: Int, {item: Object => item.asInstanceOf[Double]})
  def getDouble(columnLabel: String): Option[Double] = getDouble(columnNumber(columnLabel))

  def getFloat(column: Int): Option[Float] = getAs(column: Int, {item: Object => item.asInstanceOf[Float]})
  def getFloat(columnLabel: String): Option[Float] = getFloat(columnNumber(columnLabel))

  def getBigDecimal(column: Int): Option[BigDecimal] =
    getAs(column: Int, {item: Object => item.asInstanceOf[java.math.BigDecimal]})
      .map(scala.math.BigDecimal(_))
  def getBigDecimal(columnLabel: String): Option[BigDecimal] = getBigDecimal(columnNumber(columnLabel))

  def getTime(column: Int): Option[Time] = getAs(column: Int, {item: Object => item.asInstanceOf[Time]})
  def getTime(columnLabel: String): Option[Time] = getTime(columnNumber(columnLabel))

  def getDate(column: Int): Option[Date] = getAs(column: Int, {item: Object => item.asInstanceOf[Date]})
  def getDate(columnLabel: String): Option[Date] = getDate(columnNumber(columnLabel))

  def getLocalDateTime(column: Int): Option[LocalDateTime] = getAs(column: Int, {item: Object => item.asInstanceOf[LocalDateTime]})
  def getLocalDateTime(columnLabel: String): Option[LocalDateTime] = getLocalDateTime(columnNumber(columnLabel))

  def getOffsetDateTime(column: Int): Option[OffsetDateTime] = getAs(column: Int, {item: Object => item.asInstanceOf[OffsetDateTime]})
  def getOffsetDateTime(columnLabel: String): Option[OffsetDateTime] = getOffsetDateTime(columnNumber(columnLabel))

  def getInstant(column: Int): Option[Instant] = getOffsetDateTime(column).map(_.toInstant)
  def getInstant(columnLabel: String): Option[Instant] = getOffsetDateTime(columnLabel).map(_.toInstant)

  def getUUID(column: Int): Option[UUID] = getAs(column: Int, {item: Object => item.asInstanceOf[UUID]})
  def getUUID(columnLabel: String): Option[UUID] = getUUID(columnNumber(columnLabel))

  def getArray[T](column: Int): Option[Vector[T]] = {
    def transformerFnc(obj: Object): Vector[T] = {
      obj.asInstanceOf[sql.Array].getArray().asInstanceOf[Array[T]].toVector
    }
    getAs(column: Int, transformerFnc _)
  }

  def getArray[T](columnLabel: String): Option[Vector[T]] = getArray[T](columnNumber(columnLabel))

  def getArray[T](column: Int, itemTransformerFnc: TransformerFnc[T]): Option[Vector[T]] = {
    def transformerFnc(obj: Object): Vector[T] = {
      obj
        .asInstanceOf[sql.Array]
        .getArray()
        .asInstanceOf[Array[Object]]
        .toVector
        .map(itemTransformerFnc)
    }

    getAs(column: Int, transformerFnc _)
  }
}

object QueryResultRow {

  type FieldNames = Map[String, Int]
  type TransformerFnc[T] = Object => T
  type Extractor = ResultSet => Option[Object]
  type Extractors = Vector[Extractor]

  def apply(resultSet: ResultSet)(implicit fieldNames: FieldNames, extractors: Extractors): QueryResultRow = {
    val fields = extractors.map(_(resultSet))
    new QueryResultRow(resultSet.getRow, fields, fieldNames)
  }

  def fieldNamesFromMetadata(metaData: ResultSetMetaData): FieldNames = {
    Range.inclusive(1, metaData.getColumnCount).map(i => metaData.getColumnName(i) -> i).toMap
  }

  def createExtractors(metaData: ResultSetMetaData): Extractors = {
    def generalExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column))

    def timeTzExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[OffsetTime]))

    def timestampExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[LocalDateTime]))

    def timestampTzExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[OffsetDateTime]))

    def arrayExtractor(resultSet: ResultSet, column: Int): Option[Object] = {
      val array: sql.Array = resultSet.getArray(column)
      Option(array)
    }

    def columnTypeName(column: Int): String = metaData.getColumnTypeName(column).toLowerCase()

    Range.inclusive(1, metaData.getColumnCount).map { column =>
       val extractor: Extractor = metaData.getColumnType(column) match {
        case Types.TIME if columnTypeName(column) == "timetz" => timeTzExtractor(_, column)
        case Types.TIMESTAMP if columnTypeName(column) == "timestamptz" => timestampTzExtractor(_, column)
        case Types.TIMESTAMP => timestampExtractor(_, column)
        case Types.ARRAY => arrayExtractor(_, column)
        case _ => generalExtractor(_, column)
      }
      extractor
    }.toVector
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy