common.scala

package ru.circumflex.orm

import ru.circumflex.core._
import java.util.regex.Matcher

SQLable

Every object capable of rendering itself into an SQL statement should extend the SQLable trait.

trait SQLable {
  def toSql: String
}

Parameterized expressions

The ParameterizedExpression trait provides basic functionality for dealing with SQL expressions with JDBC-style parameters.

trait ParameterizedExpression extends SQLable {

  def parameters: Seq[Any]

  def toInlineSql: String = parameters.foldLeft(toSql)((sql, p) =>
    sql.replaceFirst("\\?", dialect.escapeParameter(p)
        .replaceAll("\\\\", "\\\\\\\\")
        .replaceAll("\\$", "\\\\\\$")))

  override def equals(that: Any) = that match {
    case e: ParameterizedExpression =>
      e.toSql == this.toSql && e.parameters.toList == this.parameters.toList
    case _ => false
  }

  override def hashCode = 0

  override def toString = toSql
}

Schema Object

Every database object which could be created or dropped should implement the SchemaObject trait.

trait SchemaObject {
  def sqlCreate: String

  def sqlDrop: String

  def objectName: String

  override def hashCode = objectName.toLowerCase.hashCode

  override def equals(obj: Any) = obj match {
    case so: SchemaObject => so.objectName.equalsIgnoreCase(this.objectName)
    case _ => false
  }

  override def toString = objectName
}

Value holders

Value holder is an atomic data-carrier unit of a record. Two implementations of ValueHolder are known: Field and Association.

abstract class ValueHolder[T, R <: Record[_, R]](
    val name: String, val record: R, val sqlType: String)
    extends Equals with Wrapper[Option[T]] {

  def item = value

Setters

Setters provide a handy mechanism for preprocessing values before setting them. They are functions T => T which are applied one-by-one each time you set new non-null value. You can add a setter by invoking the addSetter method:

val pkg = "package".TEXT.NOT_NULL
    .addSetter(_.trim)
    .addSetter(_.toLowerCase)
    .addSetter(_.replaceAll("/","."))

pkg := "  ru/circumflex/ORM  "  // "ru.circumflex.orm" will be assigned
  protected var _setters: Seq[T => T] = Nil
  def setters: Seq[T => T] = _setters
  def addSetter(f: T => T): this.type = {
    _setters ++= List(f)
    return this
  }

Accessing & Setting Values

Values are stored internally as Option[T]. None stands both for uninitialized and null values. Following examples show how field values can be accessed or set:

val id = "id" BIGINT

// accessing
id.value    // Option[Long]
id.get      // Option[Long]
id()        // Long or exception
getOrElse(default: Long)  // Long

// setting
id.set(Some(1l))
id.setNull
id := 1l

The null_? method indicates whether the underlying value is null or not.

  protected var _value: Option[T] = None

  // Accessing

  def value: Option[T] = _value
  def get: Option[T] = value
  def apply(): T = value.get
  def getOrElse(default: T): T = value.getOrElse(default)

  def null_?(): Boolean = value == None

  // Setting

  def set(v: Option[T]): this.type = {
    // process value with setters
    _value = v.map { v =>
      setters.foldLeft(v) { (v, f) => f(v) }
    }
    return this
  }
  def set(v: T): this.type = set(any2option(v))
  def setNull: this.type = set(None)
  def :=(v: T): Unit = set(v)

Column Definition Methods

Following methods help you construct a definition of the column where the field will be persisted:

  • NOT_NULL will render NOT NULL constraint in column's definition; note that starting from 2.0, by default the NOT NULL constraint is omitted and NULLABLE construct is no longer supported; this method can also be used as a shortcut for specifying the NOT NULL constraint and assigning default field value:

    // following declarations are identical val createdAt = “createdat”.TIMESTAMP.NOTNULL.set(new Date()) val createdAt = “createdat”.TIMESTAMP.NOTNULL(new Date())

  • DEFAULT will render the DEFAULT expression in column's definition (if not overriden by dialect);
  • UNIQUE will create a UNIQUE constraint for enclosing table on the field.
  protected var _notNull: Boolean = false
  def notNull_?(): Boolean = _notNull
  def NOT_NULL(): this.type = {
    _notNull = true
    return this
  }
  def NOT_NULL(initialValue: T): this.type = NOT_NULL().set(initialValue)

  protected var _unique: Boolean = false
  def unique_?(): Boolean = _unique
  def UNIQUE(): this.type = {
    _unique = true
    return this
  }

  protected var _defaultExpression: Option[String] = None
  def defaultExpression: Option[String] = _defaultExpression
  def DEFAULT(expr: String): this.type = {
    _defaultExpression = Some(expr)
    return this
  }

Methods from Option

Since ValueHolder is just a wrapper around Option, we provide some methods to work with your values in functional style (they delegate to their equivalents in Option).

  def map[B](f: T => B): Option[B] =
    value.map(f)
  def flatMap[B](f: T => Option[B]): Option[B] =
    value.flatMap(f)
  def orElse[B >: T](alternative: => Option[B]): Option[B] =
    value.orElse(alternative)

Equality & Others

Two fields are considered equal if they belong to the same type of records and share the same name.

The hashCode calculation is consistent with equals definition.

The canEqual method indicates whether the two fields belong to the same type of records.

Finally, toString returns the qualified name of relation which it belongs to followed by a dot and the field name.

  override def equals(that: Any): Boolean = that match {
    case that: ValueHolder[_, _] => this.canEqual(that) &&
      this.name == that.name
    case _ => false
  }
  override lazy val hashCode: Int =  record.relation.qualifiedName.hashCode * 31 +
      name.hashCode
  def canEqual(that: Any): Boolean = that match {
    case that: ValueHolder[_, _] => this.record.canEqual(that.record)
    case _ => false
  }
  override def toString: String = record.relation.qualifiedName + "." + name
}