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

com.twitter.storehaus.mysql.ValueMapper.scala Maven / Gradle / Ivy

/*
 * Copyright 2013 Twitter Inc.
 *
 * 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 com.twitter.storehaus.mysql

import java.lang.UnsupportedOperationException

import com.twitter.bijection.Injection
import com.twitter.finagle.exp.mysql.{
  EmptyValue,
  IntValue,
  LongValue,
  NullValue,
  RawValue,
  ShortValue,
  StringValue,
  Value,
  Charset,
  Type
}

import org.jboss.netty.buffer.ChannelBuffer
import org.jboss.netty.buffer.ChannelBuffers
import org.jboss.netty.util.CharsetUtil.UTF_8
import scala.util.Try

/** Helper class for mapping finagle-mysql Values to types we care about. */
object ValueMapper {

  // for finagle Value mappings, see:
  // https://github.com/twitter/finagle/blob/master/finagle-mysql/src/main/scala/com/twitter/finagle/mysql/Value.scala

  // currently supported types and corresponding finagle types:
  // INTEGER, INT, MEDIUMINT  => IntValue
  // BIGINT => LongValue
  // SMALLINT => ShortValue
  // BLOB => RawValue
  // TEXT => RawValue
  // CHAR/VARCHAR => StringValue

  def toChannelBuffer(v: Value): Option[ChannelBuffer] = {
    v match {
      case IntValue(d) => Some(ChannelBuffers.copiedBuffer(d.toString, UTF_8))
      case LongValue(d) => Some(ChannelBuffers.copiedBuffer(d.toString, UTF_8))
      case RawValue(_,_, true, d) => Some(ChannelBuffers.copiedBuffer(d)) // from byte array
      case RawValue(_, _, false, d) => Some(ChannelBuffers.copiedBuffer(new String(d), UTF_8))
      case ShortValue(d) => Some(ChannelBuffers.copiedBuffer(d.toString, UTF_8))
      case StringValue(d) => Some(ChannelBuffers.copiedBuffer(d, UTF_8))
      case EmptyValue => Some(ChannelBuffers.EMPTY_BUFFER)
      case NullValue => None
      // all other types are currently unsupported
      case _ => throw new UnsupportedOperationException(v.getClass.getName + " is currently not supported.")
    }
  }

  def toString(v: Value): Option[String] = {
    v match {
      case IntValue(v) => Some(v.toString)
      case LongValue(v) => Some(v.toString)
      case RawValue(_, _, _, v) => Some(new String(v)) // from byte array
      case ShortValue(v) => Some(v.toString)
      case StringValue(v) => Some(v)
      // finagle-mysql text protocol wraps null strings as NullValue
      // and empty strings as EmptyValue
      case EmptyValue => Some("")
      case NullValue => None
      // all other types are currently unsupported
      case _ => throw new UnsupportedOperationException(v.getClass.getName + " is currently not supported.")
    }
  }

  def toLong(v: Value): Option[Long] = {
    toString(v).map { _.toLong }
  }
}

/** Factory for [[com.twitter.storehaus.mysql.MySqlValue]] instances. */
object MySqlValue {
  def apply(v: Any) = v match {
    case v: Value => new MySqlValue(v)
    case v: String => new MySqlValue(RawValue(Type.String, Charset.Utf8_general_ci, false, v.getBytes))
    case v: Int => new MySqlValue(IntValue(v))
    case v: Long => new MySqlValue(LongValue(v))
    case v: Short => new MySqlValue(ShortValue(v))
    case v: ChannelBuffer => new MySqlValue(RawValue(Type.String, Charset.Utf8_general_ci, false, v.toString(UTF_8).getBytes))
    case _ => throw new UnsupportedOperationException(v.getClass.getName + " is currently not supported.")
  }
}

/**
 * Wraps finagle-mysql Value ADT.
 *
 * Since finagle maps MySQL column types to specific Value types,
 * we use this type class as an abstraction.
 * MySqlValue objects can then be converted to string, channelbuffer or any other type
 * without having to worry about the underlying finagle type.
 */
class MySqlValue(val v: Value) {
  override def equals(o: Any) = o match {
    // we consider two values to be equal if their underlying string representation are equal
    case o: MySqlValue => ValueMapper.toString(o.v) == ValueMapper.toString(this.v)
    case _ => false
  }
  override def hashCode: Int = {
    ValueMapper.toString(this.v).hashCode
  }
}

/**
 * Injection from MySqlValue to String.
 * Returns string representation of the finagle-mysql Value wrapped by MySqlValue
 * Both null values and empty values map to empty string.
 */
object MySqlStringInjection extends Injection[MySqlValue, String] {
  def apply(a: MySqlValue): String = ValueMapper.toString(a.v).getOrElse("") // should this be null: String instead?
  override def invert(b: String) = Try(MySqlValue(RawValue(Type.String, Charset.Utf8_general_ci, false, b.getBytes)))
}

/**
 * Injection from MySqlValue to ChannelBuffer.
 * Returns a channel buffer containing the Value wrapped by MySqlValue.
 * Both null values and empty values map to empty channel buffer.
 */
object MySqlCbInjection extends Injection[MySqlValue, ChannelBuffer] {
  def apply(a: MySqlValue): ChannelBuffer = ValueMapper.toChannelBuffer(a.v).getOrElse(ChannelBuffers.EMPTY_BUFFER)
  override def invert(b: ChannelBuffer) = Try(MySqlValue((Type.String, Charset.Utf8_general_ci, false, b.toString(UTF_8))))
}

object LongMySqlInjection extends Injection[Long, MySqlValue] {
  def apply(a: Long): MySqlValue = MySqlValue(a)
  override def invert(b: MySqlValue) = Try(ValueMapper.toLong(b.v).get)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy