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

anorm.MetaData.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package anorm

/**
 * @param qualified the qualified column name
 * @param alias the column alias
 */
case class ColumnName(qualified: String, alias: Option[String])

/**
 * @param column the name of the column
 * @param nullable true if the column is nullable
 * @param clazz the class of the JDBC column value
 */
case class MetaDataItem(column: ColumnName, nullable: Boolean, clazz: String)

private[anorm] case class MetaData(ms: Seq[MetaDataItem]) {

  /** Returns meta data for specified column. */
  def get(columnName: String): Option[MetaDataItem] = {
    val key = columnName.toUpperCase

    aliasedDictionary.get(key).orElse(dictionary2.get(key)).orElse(dictionary.get(key))
  }

  private lazy val dictionary: Map[String, MetaDataItem] =
    Compat.toMap(ms) { m => m.column.qualified.toUpperCase() -> m }

  private lazy val dictionary2: Map[String, MetaDataItem] =
    Compat.toMap(ms) { m =>
      val column = m.column.qualified.split('.').last

      column.toUpperCase() -> m
    }

  private lazy val aliasedDictionary: Map[String, MetaDataItem] =
    Compat.toFlatMap(ms) { m =>
      m.column.alias.map(a => Map(a.toUpperCase() -> m)).getOrElse(Map.empty)
    }

  lazy val columnCount = ms.size

  lazy val availableColumns: Seq[String] =
    ms.flatMap(i => i.column.qualified :: i.column.alias.toList)

}

/** Allows to define or overwrite the alias for a column. */
trait ColumnAliaser extends Function[(Int, ColumnName), Option[String]] {

  /**
   * Returns the alias for the specified column, if defined.
   *
   * @param column the position (>= 1) and the name of the column
   */
  def apply(column: (Int, ColumnName)): Option[String]
}

object ColumnAliaser {
  import scala.collection.immutable.Set

  private class Default(f: PartialFunction[(Int, ColumnName), String]) extends ColumnAliaser {

    def apply(column: (Int, ColumnName)) = f.lift(column)
  }

  /**
   * Initializes an aliaser from a given partial function.
   *
   * {{{
   * import anorm.{ ColumnAliaser, ColumnName }
   *
   * ColumnAliaser({
   *   case (1, _) => "my_id"
   *   case (_, ColumnName(".foo", _)) => "prefix.foo"
   * })
   * }}}
   */
  def apply(f: PartialFunction[(Int, ColumnName), String]): ColumnAliaser =
    new Default(f)

  object empty extends ColumnAliaser {
    def apply(column: (Int, ColumnName)) = Option.empty[String]
  }

  /**
   * @param positions the column positions (>= 1)
   * @param as function to determine the alias for the matching columns
   *
   * {{{
   * import anorm.ColumnAliaser
   *
   * ColumnAliaser.perPositions((2 to 3).toSet) {
   *   case (2, _) => "prefix.foo"
   *   case _ => "bar"
   * }
   * }}}
   */
  def perPositions(positions: Set[Int])(as: ((Int, ColumnName)) => String): ColumnAliaser = new Default({
    case c @ (pos, _) if positions.contains(pos) => as(c)
  })

  /**
   * @param positions the column positions (>= 1)
   * @param prefix the prefix to be prepended to the aliases of the matching columns
   * @param suffix the suffix to be appended to the aliases (default: `""`)
   *
   * {{{
   * anorm.ColumnAliaser.withPattern((2 to 3).toSet, "prefix.")
   * }}}
   */
  def withPattern(positions: Set[Int], prefix: String, suffix: String = ""): ColumnAliaser = perPositions(positions) {
    case (_, ColumnName(_, Some(alias))) => s"$prefix$alias$suffix"
    case (_, ColumnName(_, _))           => s"$prefix$suffix"
  }

  /**
   * @param prefix the prefix to be prepended to the aliases of the matching columns
   * @param suffix the suffix to be appended to the aliases (default: `""`)
   * @param positions the column positions (>= 1); duplicate are merged
   *
   * {{{
   * anorm.ColumnAliaser.withPattern((2 to 3).toSet, "prefix.")
   * }}}
   */
  def withPattern1(prefix: String, suffix: String = "")(positions: Int*): ColumnAliaser =
    withPattern(positions.toSet, prefix, suffix)
}

private[anorm] object MetaData {
  import java.sql.{ ResultSet, ResultSetMetaData }
  import scala.language.reflectiveCalls

  private type PgMeta = { def getBaseTableName(i: Int): String }

  /** Returns metadata for given result set. */
  def parse(rs: ResultSet, as: ColumnAliaser): MetaData = {
    val meta      = rs.getMetaData()
    val nbColumns = meta.getColumnCount()
    MetaData(List.range(1, nbColumns + 1).map { i =>
      @SuppressWarnings(Array("AsInstanceOf"))
      def tableName = {
        if (meta.getClass.getName.startsWith("org.postgresql.")) {
          // HACK FOR POSTGRES:
          // Fix in https://github.com/pgjdbc/pgjdbc/pull/107

          meta.asInstanceOf[PgMeta].getBaseTableName(i)
        } else {
          meta.getTableName(i)
        }

      }

      val cn = ColumnName(qualified = tableName + "." + meta.getColumnName(i), alias = Option(meta.getColumnLabel(i)))

      val colName = as(i -> cn).fold(cn)(a => cn.copy(alias = Some(a)))

      MetaDataItem(
        column = colName,
        nullable = meta.isNullable(i) == ResultSetMetaData.columnNullable,
        clazz = meta.getColumnClassName(i)
      )
    })
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy