vendor.scala

package ru.circumflex.orm

import java.sql._

Vendor-specific SQL dialects

Following vendors are currently supported by Circumflex ORM:

  • PostgreSQL 8.3+;
  • MySQL 5.7+;
  • H2 database.

We also provide limited support for Oracle, MS SQL Server and DB2. We would appreciate any commits for better vendors support.

class H2Dialect extends Dialect {
  override def driverClass = "org.h2.Driver"
  override def textType = "VARCHAR"
  override def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.unique_?) result += "UNIQUE "
    result += "INDEX " + idx.name + " ON " + idx.relation.qualifiedName +
        " (" + idx.expression + ")"
    if (idx.where != EmptyPredicate)
      result += " WHERE " + idx.where.toInlineSql
    return result
  }
  override def dropSchema(schema: Schema) = "DROP SCHEMA " + schema.name
}

class PostgreSQLDialect extends Dialect {
  override def driverClass = "org.postgresql.Driver"
  override def timestampType = "TIMESTAMPTZ"

}

class MySQLDialect extends Dialect {
  override def supportsSchema_? = false
  override def driverClass = "com.mysql.jdbc.Driver"

  override def initializeField[R <: Record[_, R]](field: Field[_, R]): Unit = {
    // do nothing -- for MySQL you don't need to create manually a sequence for auto-incrementable fields
  }

  override def defaultExpression[R <: Record[_, R]](field: Field[_, R]): String =
    field match {
      case a: AutoIncrementable[_, _] if (a.autoIncrement_?) =>" AUTO_INCREMENT"
      case _ => field.defaultExpression.map(" DEFAULT " + _).getOrElse("")
    }

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

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

  override def sequenceNextValQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    throw new UnsupportedOperationException("This operation is unsupported in MySQL dialect.")

  override def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.unique_?) result += "UNIQUE "
    result += "INDEX " + idx.name + " USING " + idx.using +
        " ON " + idx.relation.qualifiedName + " (" + idx.expression + ")"
    if (idx.where != EmptyPredicate)
      ORM_LOG.warn("Ignoring WHERE clause of INDEX " + idx.name +
          ": predicates are not supported.")
    return result
  }
}

class OracleDialect extends Dialect {
  override def driverClass = "oracle.jdbc.driver.OracleDriver"

  override def fkNoAction = "SET_NULL"
  override def fkRestrict = "SET NULL"
  override def fkSetDefault = "SET NULL"

  override def numericType(precision: Int, scale: Int): String =
    "NUMBER" + (if (precision == -1) "" else "(" + precision + "," + scale + ")")
  override def textType = "VARCHAR2(4000)"
  override def varcharType(length: Int): String =
    "VARCHAR2" + (if (length == -1) "" else "(" + length + ")")
  override def booleanType = "NUMBER(1)"

  override def supportsSchema_? = false

  override def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.unique_?) result += "UNIQUE "
    result += "INDEX " + idx.name +
        " ON " + idx.relation.qualifiedName + " (" + idx.expression + ")"
    if (idx.where != EmptyPredicate)
      ORM_LOG.warn("Ignoring WHERE clause of INDEX " + idx.name +
          ": predicates are not supported.")
    return result
  }

  override def defaultExpression[R <: Record[_, R]](field: Field[_, R]): String =
    field.defaultExpression.map(" DEFAULT " + _).getOrElse("")

  override def sequenceNextValQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    new Select(expr[PK](sequenceName(node.relation.PRIMARY_KEY) + ".nextval FROM dual"))
  override def identityLastIdPredicate[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): Predicate =
    throw new UnsupportedOperationException("This operation is unsupported in Oracle dialect.")
  override def identityLastIdQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    throw new UnsupportedOperationException("This operation is unsupported in Oracle dialect.")

}

class DB2Dialect extends Dialect {
  override def driverClass = "com.ibm.db2.jcc.DB2Driver"

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

  override def textType = "VARCHAR(4000)"
  override def booleanType = "SMALLINT"

  override def defaultExpression[R <: Record[_, R]](field: Field[_, R]): String =
    field match {
      case a: AutoIncrementable[_, _] if (a.autoIncrement_?) => " GENERATED BY DEFAULT AS IDENTITY"
      case _ => field.defaultExpression.map(" DEFAULT " + _).getOrElse("")
    }

  override def sequenceNextValQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    new NativeSQLQuery[PK](expr[PK]("NEXTVAL FOR " + sequenceName(node.relation.PRIMARY_KEY)),
      prepareExpr("SELECT {*} FROM SYSIBM.SYSDUMMY1"))
  override def identityLastIdPredicate[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): Predicate =
    new SimpleExpression(node.alias + "." + node.relation.PRIMARY_KEY.name + " = IDENTITY_VAL_LOCAL()", Nil)
  override def identityLastIdQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    new NativeSQLQuery[PK](expr[PK]("IDENTITY_VAL_LOCAL()"),
      prepareExpr("SELECT {*}  FROM SYSIBM.SYSDUMMY1"))

  override def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.unique_?) result += "UNIQUE "
    result += "INDEX " + idx.name +
        " ON " + idx.relation.qualifiedName + " (" + idx.expression + ")"
    if (idx.where != EmptyPredicate)
      ORM_LOG.warn("Ignoring WHERE clause of INDEX " + idx.name +
          ": predicates are not supported.")
    return result
  }

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

  override 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 += " VALUES DEFAULT"
    return result
  }

}

class MSSQLDialect extends Dialect {
  override def driverClass = "com.microsoft.sqlserver.jdbc.SQLServerDriver"

  override def booleanType = "BIT"

  override def supportsSchema_? = false

  override def defaultExpression[R <: Record[_, R]](field: Field[_, R]): String =
    field match {
      case a: AutoIncrementable[_, _] if (a.autoIncrement_?) =>" IDENTITY"
      case _ => field.defaultExpression.map(" DEFAULT " + _).getOrElse("")
    }

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

  override def createIndex(idx: Index): String = {
    var result = "CREATE "
    if (idx.unique_?) result += "UNIQUE "
    result += "INDEX " + idx.name + " ON " + idx.relation.qualifiedName +
        " (" + idx.expression + ")"
    if (idx.where != EmptyPredicate)
      result += " WHERE " + idx.where.toInlineSql
    return result
  }

  override def sequenceNextValQuery[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): SQLQuery[PK] =
    throw new UnsupportedOperationException("This operation is unsupported in MS SQL Server dialect.")

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

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

}