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

com.twitter.finagle.mysql.Row.scala Maven / Gradle / Ivy

There is a newer version: 21.2.0
Show newest version
package com.twitter.finagle.mysql

import com.twitter.finagle.mysql.transport.MysqlBuf
import com.twitter.io.Buf

/**
 * A `Row` makes it easy to extract [[Value]]'s from a mysql row.
 * Specific [[Value]]'s based on mysql column name can be accessed
 * via the `apply` method.
 */
trait Row {
  /**
   * Contains a Field object for each
   * Column in the Row. The data is 0-indexed
   * so fields(0) contains the column meta-data
   * for the first column in the Row.
   */
  val fields: IndexedSeq[Field]

  /** The values for this Row. */
  val values: IndexedSeq[Value]

  /**
   * Retrieves the index of the column with the given
   * name.
   * @param columnName name of the column.
   * @return Some(Int) if the column
   * exists with the given name. Otherwise, None.
   */
  def indexOf(columnName: String): Option[Int]

  /**
   * Retrieves the Value in the column with the
   * given name.
   * @param columnName name of the column.
   * @return Some(Value) if the column
   * exists with the given name. Otherwise, None.
   */
  def apply(columnName: String): Option[Value] =
    apply(indexOf(columnName))

  protected def apply(columnIndex: Option[Int]): Option[Value] =
    for (idx <- columnIndex) yield values(idx)

  override def toString = (fields zip values).toString
}

/**
 * Defines a row where the data is presumed to be encoded with the mysql
 * text-based protocol.
 * [[http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow]]
 */
class StringEncodedRow(rawRow: Buf, val fields: IndexedSeq[Field], indexMap: Map[String, Int]) extends Row {
  private val reader = MysqlBuf.reader(rawRow)

  /**
   * Convert the string representation of each value
   * into an appropriate Value object.
   * [[http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow]]
   */
  lazy val values: IndexedSeq[Value] =
    for (field <- fields) yield {
      val charset = field.charset
      val bytes = reader.readLengthCodedBytes()
      if (bytes == null)
        NullValue
      else if (bytes.isEmpty)
        EmptyValue
      else if (!Charset.isCompatible(charset))
        RawValue(field.fieldType, field.charset, false, bytes)
      else {
        val str = new String(bytes, Charset(charset))
        field.fieldType match {
          case Type.Tiny       => ByteValue(str.toByte)
          case Type.Short      => ShortValue(str.toShort)
          case Type.Int24      => IntValue(str.toInt)
          case Type.Long       => IntValue(str.toInt)
          case Type.LongLong   => LongValue(str.toLong)
          case Type.Float      => FloatValue(str.toFloat)
          case Type.Double     => DoubleValue(str.toDouble)
          case Type.Year       => ShortValue(str.toShort)
          // Nonbinary strings as stored in the CHAR, VARCHAR, and TEXT data types
          case Type.VarChar | Type.String | Type.VarString |
               Type.TinyBlob | Type.Blob | Type.MediumBlob
               if !Charset.isBinary(charset) => StringValue(str)
          // LongBlobs indicate a sequence of bytes with length >= 2^24 which
          // can't fit into a Array[Byte]. This should be streamed and
          // support for this needs to begin at the transport layer.
          case Type.LongBlob => throw new UnsupportedOperationException("LongBlob is not supported!")
          case typ => RawValue(typ, charset, isBinary = false, bytes)
        }
      }
    }

  def indexOf(name: String) = indexMap.get(name)
}

/**
 * Defines a Row where the data is presumed to be encoded with the
 * mysql binary protocol.
 * [[http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html]]
 */
class BinaryEncodedRow(rawRow: Buf, val fields: IndexedSeq[Field], indexMap: Map[String, Int]) extends Row {
  private val reader = MysqlBuf.reader(rawRow)
  reader.skip(1)

  /**
   * In a binary encoded row, null values are not sent from the
   * server. Instead, the server sends a bit vector where
   * each bit corresponds to the index of the column. If the bit
   * is set, the value is null.
   */
  val nullBitmap: BigInt = {
    val len = ((fields.size + 7 + 2) / 8).toInt
    val bytesAsBigEndian = reader.take(len).reverse
    BigInt(bytesAsBigEndian)
  }

  /**
   * Check if the bit is set. Note, the
   * first 2 bits are reserved.
   */
  def isNull(index: Int) = nullBitmap.testBit(index + 2)

  /**
   * Convert the binary representation of each value
   * into an appropriate Value object.
   */
  lazy val values: IndexedSeq[Value] =
    for ((field, idx) <- fields.zipWithIndex) yield {
      if (isNull(idx)) NullValue
      else field.fieldType match {
        case Type.Tiny        => ByteValue(reader.readByte())
        case Type.Short       => ShortValue(reader.readShortLE())
        case Type.Int24       => IntValue(reader.readIntLE())
        case Type.Long        => IntValue(reader.readIntLE())
        case Type.LongLong    => LongValue(reader.readLongLE())
        case Type.Float       => FloatValue(reader.readFloatLE())
        case Type.Double      => DoubleValue(reader.readDoubleLE())
        case Type.Year        => ShortValue(reader.readShortLE())
        // Nonbinary strings as stored in the CHAR, VARCHAR, and TEXT data types
        case Type.VarChar | Type.String | Type.VarString |
             Type.TinyBlob | Type.Blob | Type.MediumBlob
             if !Charset.isBinary(field.charset) && Charset.isCompatible(field.charset) =>
              StringValue(reader.readLengthCodedString(Charset(field.charset)))

        case Type.LongBlob => throw new UnsupportedOperationException("LongBlob is not supported!")
        case typ => RawValue(typ, field.charset, isBinary = true, reader.readLengthCodedBytes())
      }
    }

  def indexOf(name: String) = indexMap.get(name)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy