dialect.scala

package ru.circumflex
package orm

import java.sql._

Dialect

This little thingy does all dirty SQL rendering.

We are orienting the default dialect on the world's most advanced open-source database, PostgreSQL.

If you feel that some of the statements do not work with your RDBMS vendor, trace the exact method and provide it's implementation in your own class. After that, set the orm.dialect configuration parameter accordingly.

class Dialect {

  def driverClass: String =
    throw new ORMException("Missing mandatory configuration parameter 'orm.connection.driver'.")

JDBC methods

  def prepareStatement(conn: Connection, sql: String): PreparedStatement =
    conn.prepareStatement(sql)

SQL types

  def longType = "BIGINT"
  def integerType = "INTEGER"
  def numericType(precision: Int, scale: Int): String =
    "NUMERIC" + (if (precision == -1) "" else "(" + precision + "," + scale + ")")
  def textType = "TEXT"
  def varcharType(length: Int): String =
    "VARCHAR" + (if (length == -1) "" else "(" + length + ")")
  def booleanType = "BOOLEAN"
  def dateType = "DATE"
  def timeType = "TIME"
  def timestampType = "TIMESTAMP"
  def xmlType = "XML"
  def binaryType = "BYTEA"

Actions for Foreign Keys

  def fkNoAction = "NO ACTION"
  def fkCascade = "CASCADE"
  def fkRestrict = "RESTRICT"
  def fkSetNull = "SET NULL"
  def fkSetDefault = "SET DEFAULT"

Join Keywords

  def innerJoin = "INNER JOIN"
  def leftJoin = "LEFT JOIN"
  def rightJoin = "RIGHT JOIN"
  def fullJoin = "FULL JOIN"

Predicates

  def EQ(ex1: String, ex2: String = "?") = ex1 + " = " + ex2
  def NE(ex1: String, ex2: String = "?") = ex1 + " <> " + ex2
  def GT(ex1: String, ex2: String = "?") = ex1 + " > " + ex2
  def GE(ex1: String, ex2: String = "?") = ex1 + " >= " + ex2
  def LT(ex1: String, ex2: String = "?") = ex1 + " < " + ex2
  def LE(ex1: String, ex2: String = "?") = ex1 + " <= " + ex2

  def emptyPredicate = "1 = 1"
  def IS_NULL(ex: String) = ex + " IS NULL"
  def IS_NOT_NULL(ex: String) = ex + " IS NOT NULL"
  def LIKE(ex1: String, ex2: String = "?") = ex1 + " LIKE " + ex2
  def ILIKE(ex1: String, ex2: String = "?") = ex1 + " ILIKE " + ex2
  def BETWEEN(ex: String, v1: String = "?", v2: String= "?") =
    ex + " BETWEEN " + v1 + " AND " + v2
  def IN(ex: String) = ex + " IN"
  def NOT_IN(ex: String) = ex + " NOT IN"
  def parameterizedIn(ex: String, params: Iterable[String]) =
    ex + " IN (" + params.mkString(", ") + ")"

  def AND = "AND"
  def OR = "OR"
  def NOT = "NOT"

  def ALL = "ALL"
  def SOME = "SOME"

  def EXISTS = "EXISTS"
  def NOT_EXISTS = "NOT EXISTS"

Functions and others

  def NULL = "NULL"
  def DISTINCT = "DISTINCT"
  def COUNT(ex: String) = "COUNT(" + ex + ")"
  def COUNT_DISTINCT(ex: String) = "COUNT(DISTINCT " + ex + ")"
  def MAX(ex: String) = "MAX(" + ex + ")"
  def MIN(ex: String) = "MIN(" + ex + ")"
  def SUM(ex: String) = "SUM(" + ex + ")"
  def AVG(ex: String) = "AVG(" + ex + ")"

Set operations

  def UNION = "UNION"
  def UNION_ALL = "UNION ALL"
  def EXCEPT = "EXCEPT"
  def EXCEPT_ALL = "EXCEPT ALL"
  def INTERSECT = "INTERSECT"
  def INTERSECT_ALL = "INTERSECT ALL"

Order specificators

  def asc = "ASC"
  def desc = "DESC"

Param placeholders

  def placeholder = "?"
  def xmlPlaceholder = "XMLPARSE(DOCUMENT ?)"

Features Compliance

  def supportsSchema: Boolean = true
  def supportsDropConstraints: Boolean = true

Commons

  def quoteLiteral(expr: String) = "'" + expr.replace("'", "''") + "'"

  def escapeParameter(value: Any): String = value match {
    case Some(v) => escapeParameter(v)
    case None | null => "NULL"
    case v => quoteLiteral(v.toString)
  }

  def relationQualifiedName(relation: Relation[_, _]) =
    if (supportsSchema) relation.schema.name + "." + relation.relationName
    else relation.relationName

  def alias(expression: String, alias: String) =
    expression + " AS " + alias

  def qualifyColumn(vh: ValueHolder[_, _], tableAlias: String) =
    tableAlias + "." + vh.name

  def on(expression: Expression) = "ON (" + expression.toInlineSql + ")"

  def not(expression: String) = "NOT (" + expression + ")"

  def subquery(expression: String, subquery: SQLQuery[_]) =
    expression + " ( " + subquery.toSql + " )"

Data Definition Language

  def constraintDefinition(constraint: Constraint) =
    "CONSTRAINT " + constraint.constraintName + " " + constraint.sqlDefinition

  def alterTable(rel: Relation[_, _], action: String) =
    "ALTER TABLE " + rel.qualifiedName + " " + action

  def alterTableAddConstraint(constraint: Constraint) =
    alterTable(constraint.relation, "ADD " + constraintDefinition(constraint));

  def alterTableDropConstraint(constraint: Constraint) =
    alterTable(constraint.relation, "DROP CONSTRAINT " + constraint.constraintName);

  def createSchema(schema: Schema) = "CREATE SCHEMA " + schema.name

  def dropSchema(schema: Schema) = "DROP SCHEMA " + schema.name + " CASCADE"

  def createTable[PK, R <: Record[PK, R]](table: Table[PK, R]) =
    "CREATE TABLE " + table.qualifiedName + " (" +
        table.fields.map(_.toSql).mkString(", ") +
        ", PRIMARY KEY (" + table.PRIMARY_KEY.name + "))"

  def dropTable[PK, R <: Record[PK, R]](table: Table[PK, R]) =
    "DROP TABLE " + table.qualifiedName

  def createView[PK, R <: Record[PK, R]](view: View[PK, R]) =
    "CREATE VIEW " + view.qualifiedName + " (" +
        view.fields.map(_.name).mkString(", ") + ") AS " +
        view.query.toInlineSql

  def dropView[PK, R <: Record[PK, R]](view: View[PK, R]) =
    "DROP VIEW " + view.qualifiedName

  def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.isUnique) result += "UNIQUE "
    result += "INDEX " + idx.name + " ON " + idx.relation.qualifiedName +
        " USING " + idx.usingClause + " (" + idx.expression + ")"
    if (idx.whereClause != EmptyPredicate)
      result += " WHERE " + idx.whereClause.toInlineSql
    result
  }

  def dropIndex(idx: Index) =
    "DROP INDEX " + idx.relation.schema.name + "." + idx.name

  def columnDefinition[R <: Record[_, R]](field: Field[_, R]): String = {
    var result = field.name + " " + field.sqlType
    if (field.isNotNull) result += " NOT NULL"
    if (field.isUnique) result += " UNIQUE"
    result += defaultExpression(field)
    result
  }

  def compositeFieldName(names: String*): String = names.mkString(", ")

  def initializeRelation[R <: Record[_, R]](relation: Relation[_, R]) {}

  def initializeField[R <: Record[_, R]](field: Field[_, R]) = field match {
    case f: AutoIncrementable[_, _]
      if (f.isAutoIncrement && !field.record.relation.isInstanceOf[View[_, R]]) => {
      val seqName = sequenceName(f)
      val seq = new SchemaObject {
        val objectName = "SEQUENCE " + seqName
        val sqlDrop = "DROP SEQUENCE " + seqName
        val sqlCreate = "CREATE SEQUENCE " + seqName
      }
      f.record.relation.addPreAux(seq)
    }
    case _ =>
  }

  def defaultExpression[R <: Record[_, R]](field: Field[_, R]): String =
    field match {
      case a: AutoIncrementable[_, _] if (a.isAutoIncrement) =>
        " DEFAULT NEXTVAL('" + sequenceName(field) + "')"
      case _ =>
        field.defaultExpression.map(" DEFAULT " + _).getOrElse("")
    }

  def sequenceName[R <: Record[_, R]](vh: ValueHolder[_, R]) =
    vh.record.relation.schema.name + "." +
        vh.record.relation.relationName + "_" + vh.name + "_seq"

  def uniqueKeyDefinition(uniq: UniqueKey) =
    "UNIQUE (" + uniq.columns.map(_.name).mkString(", ") + ")"

  def foreignKeyDefinition(fk: ForeignKey) =
    "FOREIGN KEY (" + fk.childColumns.map(_.name).mkString(", ") +
        ") REFERENCES " + fk.parentRelation.qualifiedName + " (" +
        fk.parentColumns.map(_.name).mkString(", ") + ") " +
        "ON DELETE " + fk.onDelete.toSql + " " +
        "ON UPDATE " + fk.onUpdate.toSql

  def checkConstraintDefinition(check: CheckConstraint) =
    "CHECK (" + check.expression + ")"

Structured Query Language

  def join(j: JoinNode[_, _, _, _]): String = joinInternal(j, null)

  protected def joinInternal(node: RelationNode[_, _], on: String): String = {
    var result = ""
    node match {
      case j: JoinNode[_, _, _, _] =>
        result += joinInternal(j.left, on) +
            " " + j.joinType.toSql + " " +
            joinInternal(j.right, j.sqlOn)
      case _ =>
        result += node.toSql
        if (on != null) result += " " + on
    }
    result
  }

  def select(q: Select[_]): String = {
    var result = "SELECT "
    if (q.isDistinct)
      result += "DISTINCT "
    result += q.projections.map(_.toSql).mkString(", ")
    if (q.fromClause.size > 0)
      result += " FROM " + q.fromClause.map(_.toSql).mkString(", ")
    if (q.whereClause != EmptyPredicate)
      result += " WHERE " + q.whereClause.toSql
    if (q.groupByClause != "")
      result += " GROUP BY " + q.groupByClause
    if (q.havingClause != EmptyPredicate)
      result += " HAVING " + q.havingClause.toSql
    q.setOps.foreach {
      case (op: SetOperation, subq: SQLQuery[_]) =>
        result += " " + op.toSql + " ( " + subq.toSql + " )"
      case _ =>
    }
    if (q.orderByClause.size > 0)
      result += " ORDER BY " + q.orderByClause.map(_.toSql).mkString(", ")
    if (q.limit > -1)
      result += " LIMIT " + q.limit
    if (q.offset > 0)
      result += " OFFSET " + q.offset
    result
  }

  def identityLastIdPredicate[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): Predicate =
    new SimpleExpression(node.alias + "." + node.relation.PRIMARY_KEY.name + " = LASTVAL()", Nil)

  def identityLastIdQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    new Select(expr[PK]("LASTVAL()"))

  def sequenceNextValQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    new Select(expr[PK]("NEXTVAL('" + sequenceName(node.relation.PRIMARY_KEY) + "')"))

Data Manipulation Language

  def insert[PK, R <: Record[PK, R]](dml: Insert[PK, R]): String = {
    var result = "INSERT INTO " + dml.relation.qualifiedName
    if (dml.fields.size > 0)
      result += " (" + dml.fields.map(_.name).mkString(", ") +
          ") VALUES (" + dml.fields.map(_.placeholder).mkString(", ") + ")"
    else result += " DEFAULT VALUES"
    result
  }

  def insertSelect[PK, R <: Record[PK, R]](dml: InsertSelect[PK, R]) =
    "INSERT INTO " + dml.relation.qualifiedName + " (" +
        dml.relation.fields.map(_.name).mkString(", ") + ") " + dml.query.toSql

  def update[PK, R <: Record[PK, R]](dml: Update[PK, R]): String = {
    var result = "UPDATE " + dml.node.toSql + " SET " +
        dml.setClause.map(f => f._1.name + " = " + f._1.placeholder).mkString(", ")
    if (dml.whereClause != EmptyPredicate) result += " WHERE " + dml.whereClause.toSql
    result
  }

  def delete[PK, R <: Record[PK, R]](dml: Delete[PK, R]): String = {
    var result = "DELETE FROM " + dml.node.toSql
    if (dml.whereClause != EmptyPredicate) result += " WHERE " + dml.whereClause.toSql
    result
  }

}