Skip to content

Instantly share code, notes, and snippets.

@hsyed
Created November 28, 2013 01:00
Show Gist options
  • Save hsyed/7685742 to your computer and use it in GitHub Desktop.
Save hsyed/7685742 to your computer and use it in GitHub Desktop.
Postgress record and record array mapper
import org.postgresql.util.PGobject
import scala.util.matching.Regex
import scala.reflect.ClassTag
import scala.slick.lifted.{BaseTypeMapper, TypeMapperDelegate}
import scala.slick.driver.BasicProfile
import scala.slick.session.{PositionedResult, PositionedParameters}
import java.util.{Map => JMap}
abstract class RecordMapper[T: ClassTag]{
def name : String
def extractorRegex: Regex
def fnFromString: (String) => T
def fnToString: (T) => String
def getRecordMapper = new GenericRecordMapper[T](name,fnFromString,fnToString)
def getRecordArrayMapper = new GenericRecordArrayMapper[T](name,extractorRegex,fnFromString,fnToString)
}
class GenericRecordArrayMapper[T: ClassTag](name: String, extractorRegex: Regex,fnFromString: (String) => T, fnToString: (T) => String)
extends TypeMapperDelegate[List[T]] with BaseTypeMapper[List[T]] {
def apply(v1: BasicProfile): TypeMapperDelegate[List[T]] = this
def zero: List[T] = Nil
def sqlType: Int = java.sql.Types.ARRAY
def sqlTypeName: String = s"$name ARRAY"
def setValue(v: List[T], p: PositionedParameters) = p.setObject(mkArray(v), sqlType)
def setOption(v: Option[List[T]], p: PositionedParameters) = p.setObjectOption(v.map(mkArray), sqlType)
def nextValue(r: PositionedResult): List[T] = {
r.nextObjectOption().map(_.toString).map(extractorRegex.findAllIn(_).map(fnFromString(_)).toList).getOrElse(zero)
}
def updateValue(v: List[T], r: PositionedResult) = r.updateObject(mkArray(v))
override def valueToSQLLiteral(v: List[T]) = mkArray(v).toString
private def mkArray(v: List[T]): java.sql.Array = new SimpleArray(v, name)
/** only used to transfer array data into driver/preparedStatement */
class SimpleArray(arr: Seq[Any], baseTypeName: String) extends java.sql.Array {
def getBaseTypeName = baseTypeName
def getBaseType = ???
def getArray = arr.toArray
def getArray(map: JMap[String, Class[_]]) = ???
def getArray(index: Long, count: Int) = ???
def getArray(index: Long, count: Int, map: JMap[String, Class[_]]) = ???
def getResultSet = ???
def getResultSet(map: JMap[String, Class[_]]) = ???
def getResultSet(index: Long, count: Int) = ???
def getResultSet(index: Long, count: Int, map: JMap[String, Class[_]]) = ???
override def toString = buildStr(arr).toString()
def free() = { /* nothing to do */ }
/** copy from [[org.postgresql.jdbc4.AbstractJdbc4Connection#createArrayOf(..)]]
* and [[org.postgresql.jdbc2.AbstractJdbc2Array#escapeArrayElement(..)]] */
private def buildStr(elements: Seq[Any]): StringBuilder = {
def escape(s: String) = {
StringBuilder.newBuilder + '"' appendAll (
s map {
c => if (c == '"' || c == '\\') '\\' else c
}) + '"'
}
StringBuilder.newBuilder + '{' append (
elements map {
case arr: Seq[Any] => buildStr(arr)
case o: T if (o != null) => escape(fnToString(o))
case _ => "NULL"
} mkString(",")) + '}'
}
}
}
class GenericRecordMapper[T](pgTypeName: String, fnFromString: (String => T),
fnToString: (T => String) = ((r: T) => r.toString))
extends TypeMapperDelegate[T] with BaseTypeMapper[T] {
def apply(v1: BasicProfile): TypeMapperDelegate[T] = this
def zero: T = null.asInstanceOf[T]
def sqlType: Int = java.sql.Types.OTHER
def sqlTypeName: String = pgTypeName
def setValue(v: T, p: PositionedParameters) = p.setObject(mkPgObject(v), sqlType)
def setOption(v: Option[T], p: PositionedParameters) = p.setObjectOption(v.map(mkPgObject), sqlType)
def nextValue(r: PositionedResult): T = r.nextStringOption().map(fnFromString).getOrElse(zero)
def updateValue(v: T, r: PositionedResult) = r.updateObject(mkPgObject(v))
override def valueToSQLLiteral(v: T) = fnToString(v)
private def mkPgObject(v: T) = {
val obj = new PGobject
obj.setType(sqlTypeName)
obj.setValue(valueToSQLLiteral(v))
obj
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment