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

org.squeryl.internals.DatabaseAdapter.scala Maven / Gradle / Ivy

Go to download

A Scala ORM and DSL for talking with Databases using minimum verbosity and maximum type safety

The newest version!
/*******************************************************************************
 * Copyright 2010 Maxime Lévesque
 * 
 * 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 org.squeryl.internals

import org.squeryl.dsl.ast._
import org.squeryl._
import dsl.CompositeKey
import org.squeryl.{Schema, Session, Table}
import java.sql._
import java.time._
import java.util.UUID

trait DatabaseAdapter {

  class Zip[T](val element: T, val isLast: Boolean, val isFirst: Boolean)

  class ZipIterable[T](iterable: Iterable[T]) {
    val count = iterable.size
    def foreach[U](f: Zip[T] => U): Unit = {
      var c = 1
      for (i <- iterable) {
        f(new Zip(i, c == count, c == 1))
        c += 1
      }
    }

    def zipi = this
  }

  implicit def zipIterable[T](i: Iterable[T]): ZipIterable[T] = new ZipIterable(i)

  def writeQuery(qen: QueryExpressionElements, sw: StatementWriter): Unit =
    writeQuery(qen, sw, false, None)

  /**
   * Should we verify that when we delete by primary key the JDBC driver reports
   * that no more than one row was affected?  MonetDB in particular seems to
   * act badly here
   * @return true if we should throw an exception if the driver reports more than 1 affected row
   */
  def verifyDeleteByPK: Boolean = true

  protected[squeryl] def writeCteReference(sw: StatementWriter, q: QueryExpressionElements): Unit = {
    sw.write(quoteName("cte_" + q.alias))
  }

  protected def writeQuery(
    qen: QueryExpressionElements,
    sw: StatementWriter,
    inverseOrderBy: Boolean,
    topHint: Option[String]
  ): Unit = {

    if (supportsCommonTableExpressions && qen.commonTableExpressions.nonEmpty) {
      sw.write("With")
      for (z <- qen.commonTableExpressions.zipi) {
        sw.write(" ")
        writeCteReference(sw, z.element)
        sw.write(" As ")
        sw.write("(")
        writeQuery(z.element, sw)
        sw.write(")")
        if (!z.isLast) {
          sw.write(",")
        }
        sw.nextLine
      }
    }

    sw.write("Select")

    topHint.foreach(" " + sw.write(_) + " ")

    if (qen.selectDistinct)
      sw.write(" distinct")

    sw.nextLine
    sw.writeIndented {
      sw.writeNodesWithSeparator(qen.selectList.filter(e => !e.inhibited), ",", true)
    }
    sw.nextLine
    sw.write("From")
    sw.nextLine

    if (!qen.isJoinForm) {
      sw.writeIndented {
        for (z <- qen.tableExpressions.zipi) {
          z.element.write(sw)
          sw.write(" ")
          sw.write(sw.quoteName(z.element.alias))
          if (!z.isLast) {
            sw.write(",")
            sw.nextLine
          }
        }
        sw.pushPendingNextLine
      }
    } else {
      val singleNonJoinTableExpression = qen.tableExpressions.filter(!_.isMemberOfJoinList)
      assert(
        singleNonJoinTableExpression.size == 1,
        "join query must have exactly one FROM argument, got : " + qen.tableExpressions
      )
      val firstJoinExpr = singleNonJoinTableExpression.head
      val restOfJoinExpr = qen.tableExpressions.filter(_.isMemberOfJoinList)
      firstJoinExpr.write(sw)
      sw.write(" ")
      sw.write(sw.quoteName(firstJoinExpr.alias))
      sw.nextLine

      for (z <- restOfJoinExpr.zipi) {
        writeJoin(z.element, sw)
        if (z.isLast)
          sw.unindent
        sw.pushPendingNextLine
      }
    }

    writeEndOfFromHint(qen, sw)

    if (qen.hasUnInhibitedWhereClause) {
      sw.write("Where")
      sw.nextLine
      sw.writeIndented {
        qen.whereClause.get.write(sw)
      }
      sw.pushPendingNextLine
    }

    if (qen.groupByClause.exists(e => !e.inhibited)) {
      sw.write("Group By")
      sw.nextLine
      sw.writeIndented {
        sw.writeNodesWithSeparator(qen.groupByClause.filter(e => !e.inhibited), ",", true)
      }
      sw.pushPendingNextLine
    }

    if (qen.havingClause.exists(e => !e.inhibited)) {
      sw.write("Having")
      sw.nextLine
      sw.writeIndented {
        sw.writeNodesWithSeparator(qen.havingClause.filter(e => !e.inhibited), ",", true)
      }
      sw.pushPendingNextLine
    }

    if (qen.orderByClause.exists(e => !e.inhibited)) {
      sw.write("Order By")
      sw.nextLine
      val ob0 = qen.orderByClause.filter(e => !e.inhibited)
      val ob = if (inverseOrderBy) ob0.map(_.asInstanceOf[OrderByExpression].inverse) else ob0
      sw.writeIndented {
        sw.writeNodesWithSeparator(ob, ",", true)
      }
      sw.pushPendingNextLine
    }

    writePaginatedQueryDeclaration(() => qen.page, qen, sw)

    writeEndOfQueryHint(() => qen.isForUpdate, qen, sw)
  }

  def writeUnionQueryOptions(qen: QueryExpressionElements, sw: StatementWriter): Unit = {
    if (!supportsUnionQueryOptions)
      Utils.throwError("Database adapter does not support query options on a union query")

    writeEndOfQueryHint(() => qen.unionIsForUpdate, qen, sw)
    writePaginatedQueryDeclaration(() => qen.unionPage, qen, sw)
  }

  def writeEndOfQueryHint(isForUpdate: () => Boolean, qen: QueryExpressionElements, sw: StatementWriter) =
    if (isForUpdate()) {
      sw.write("for update")
      sw.pushPendingNextLine
    }

  def writeEndOfFromHint(qen: QueryExpressionElements, sw: StatementWriter) = {}

  def writePaginatedQueryDeclaration(
    page: () => Option[(Int, Int)],
    qen: QueryExpressionElements,
    sw: StatementWriter
  ): Unit =
    page().foreach(p => {
      sw.write("limit ")
      sw.write(p._2.toString)
      sw.write(" offset ")
      sw.write(p._1.toString)
      sw.pushPendingNextLine
    })

  def writeJoin(queryableExpressionNode: QueryableExpressionNode, sw: StatementWriter) = {
    sw.write(queryableExpressionNode.joinKind.get._1)
    sw.write(" ")
    sw.write(queryableExpressionNode.joinKind.get._2)
    sw.write(" join ")
    queryableExpressionNode.write(sw)
    sw.write(" as ")
    sw.write(sw.quoteName(queryableExpressionNode.alias))
    sw.write(" on ")
    queryableExpressionNode.joinExpression.get.write(sw)
  }

  def intTypeDeclaration = "int"
  def stringTypeDeclaration = "varchar"
  def stringTypeDeclaration(length: Int) = "varchar(" + length + ")"
  def booleanTypeDeclaration = "boolean"
  def doubleTypeDeclaration = "double"
  def dateTypeDeclaration = "date"
  def localDateTypeDeclaration = "date"
  def localTimeTypeDeclaration = "time"
  def longTypeDeclaration = "bigint"
  def floatTypeDeclaration = "real"
  def bigDecimalTypeDeclaration = "decimal"
  def bigDecimalTypeDeclaration(precision: Int, scale: Int) = "decimal(" + precision + "," + scale + ")"
  def timestampTypeDeclaration = "timestamp"
  def localDateTimeTypeDeclaration = "timestamp"
  def offsetTimeTypeDeclaration = "time"
  def offsetDateTimeTypeDeclaration = "timestamp"
  def binaryTypeDeclaration = "binary"
  def uuidTypeDeclaration = "char(36)"
  def intArrayTypeDeclaration = intTypeDeclaration + "[]"
  def longArrayTypeDeclaration = longTypeDeclaration + "[]"
  def doubleArrayTypeDeclaration = doubleTypeDeclaration + "[]"

  def stringArrayTypeDeclaration = stringTypeDeclaration + "[]"
  def jdbcIntArrayCreationType = intTypeDeclaration
  def jdbcLongArrayCreationType = longTypeDeclaration
  def jdbcDoubleArrayCreationType = doubleTypeDeclaration
  def jdbcStringArrayCreationType = stringTypeDeclaration

  final def arrayCreationType(ptype: Class[_]): String = {
    val rv = ptype.getName() match {
      case "java.lang.Integer" => jdbcIntArrayCreationType
      case "java.lang.Double" => jdbcDoubleArrayCreationType
      case "java.lang.Long" => jdbcLongArrayCreationType
      case "java.lang.String" => jdbcStringArrayCreationType
      case _ => ""
    }
    rv
  }

  /*
  private val _declarationHandler = new FieldTypeHandler[String] {

    def handleIntType = intTypeDeclaration
    def handleStringType  = stringTypeDeclaration
    def handleBooleanType = booleanTypeDeclaration
    def handleDoubleType = doubleTypeDeclaration
    def handleDateType = dateTypeDeclaration
    def handleLongType = longTypeDeclaration
    def handleFloatType = floatTypeDeclaration
    def handleBigDecimalType = bigDecimalTypeDeclaration
    def handleTimestampType = timestampTypeDeclaration
    def handleBinaryType = binaryTypeDeclaration
    def handleUuidType = uuidTypeDeclaration
    def handleEnumerationValueType = intTypeDeclaration
    def handleUnknownType(c: Class[_]) =
      org.squeryl.internals.Utils.throwError("don't know how to map field type " + c.getName)
  }
   */
  def databaseTypeFor(fmd: FieldMetaData): String =
    fmd.explicitDbTypeDeclaration.getOrElse(
      fmd.schema.columnTypeFor(fmd, fmd.parentMetaData.viewOrTable.asInstanceOf[Table[_]]).getOrElse {
        val nativeJdbcType = fmd.nativeJdbcType

        if (classOf[String].isAssignableFrom(nativeJdbcType))
          stringTypeDeclaration(fmd.length)
        else if (classOf[BigDecimal].isAssignableFrom(nativeJdbcType))
          bigDecimalTypeDeclaration(fmd.length, fmd.scale)
        else
          databaseTypeFor(fmd.schema.fieldMapper, nativeJdbcType)
      }
    )

  def writeColumnDeclaration(fmd: FieldMetaData, isPrimaryKey: Boolean, schema: Schema): String = {

    val dbTypeDeclaration = databaseTypeFor(fmd)

    val sb = new java.lang.StringBuilder(128)

    sb.append("  ")
    sb.append(quoteName(fmd.columnName))
    sb.append(" ")
    sb.append(dbTypeDeclaration)

    for (d <- fmd.defaultValue) {
      sb.append(" default ")

      val v = convertToJdbcValue(d.value.asInstanceOf[AnyRef])
      if (v.isInstanceOf[String])
        sb.append("'" + v + "'")
      else
        sb.append(v)
    }

    if (isPrimaryKey)
      sb.append(" primary key")

    if (!fmd.isOption)
      sb.append(" not null")

    if (supportsAutoIncrementInColumnDeclaration && fmd.isAutoIncremented)
      sb.append(" auto_increment")

    sb.toString
  }

  def supportsAutoIncrementInColumnDeclaration: Boolean = true

  def supportsUnionQueryOptions = true

  def supportsCommonTableExpressions = true

  def writeCreateTable[T](t: Table[T], sw: StatementWriter, schema: Schema) = {

    sw.write("create table ")
    sw.write(quoteName(t.prefixedName))
    sw.write(" (\n");
    sw.writeIndented {
      sw.writeLinesWithSeparator(
        t.posoMetaData.fieldsMetaData.map(fmd => writeColumnDeclaration(fmd, fmd.declaredAsPrimaryKeyInSchema, schema)),
        ","
      )
    }
    sw.write(")")
  }

  def fillParamsInto(params: Iterable[StatementParam], s: PreparedStatement): Unit = {
    var i = 1;
    for (p <- params) {
      setParamInto(s, p, i)
      i += 1
    }
  }

  def setParamInto(s: PreparedStatement, p: StatementParam, i: Int) =
    p match {
      case ConstantStatementParam(constantTypedExpression) =>
        s.setObject(i, convertToJdbcValue(constantTypedExpression.nativeJdbcValue))
      case FieldStatementParam(o, fieldMetaData) =>
        s.setObject(i, convertToJdbcValue(fieldMetaData.getNativeJdbcValue(o)))
      case ConstantExpressionNodeListParam(v, constantExpressionNodeList) => s.setObject(i, convertToJdbcValue(v))
    }

  private def _exec[A](
    s: AbstractSession,
    sw: StatementWriter,
    block: Iterable[StatementParam] => A,
    args: Iterable[StatementParam]
  ): A =
    try {
      if (s.isLoggingEnabled)
        s.log(sw.toString)
      block(args)
    } catch {
      case e: SQLException =>
        throw SquerylSQLException(
          "Exception while executing statement : " + e.getMessage +
            "\nerrorCode: " +
            e.getErrorCode + ", sqlState: " + e.getSQLState + "\n" +
            sw.statement + "\njdbcParams:" +
            args.mkString("[", ",", "]"),
          e
        )
    }

  def failureOfStatementRequiresRollback = false

  /**
   * Some methods like 'dropTable' issue their statement, and will silence the exception.
   * For example dropTable will silence when isTableDoesNotExistException(theExceptionThrown).
   * It must be used carefully, and an exception should not be silenced unless identified.
   */
  protected def execFailSafeExecute(sw: StatementWriter, silenceException: SQLException => Boolean): Unit = {
    val s = Session.currentSession
    val c = s.connection
    val stat = createStatement(c)
    val sp =
      if (failureOfStatementRequiresRollback) Some(c.setSavepoint)
      else None

    try {
      if (s.isLoggingEnabled)
        s.log(sw.toString)
      stat.execute(sw.statement)
    } catch {
      case e: SQLException =>
        if (silenceException(e))
          sp.foreach(c.rollback(_))
        else
          throw SquerylSQLException(
            "Exception while executing statement,\n" +
              "SQLState:" + e.getSQLState + ", ErrorCode:" + e.getErrorCode + "\n :" +
              sw.statement,
            e
          )
    } finally {
      sp.foreach(c.releaseSavepoint(_))
      Utils.close(stat)
    }
  }

  implicit def string2StatementWriter(s: String): StatementWriter = {
    val sw = new StatementWriter(this)
    sw.write(s)
    sw
  }

  protected def exec[A](s: AbstractSession, sw: StatementWriter)(block: Iterable[StatementParam] => A): A = {
    _exec[A](s, sw, block, sw.params)
  }

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

  protected def createStatement(conn: Connection): Statement =
    conn.createStatement()

  def executeQuery(s: AbstractSession, sw: StatementWriter) = exec(s, sw) { params =>
    val st = prepareStatement(s.connection, sw.statement)
    fillParamsInto(params, st)
    (st.executeQuery, st)
  }

  def executeUpdate(s: AbstractSession, sw: StatementWriter): (Int, PreparedStatement) = exec(s, sw) { params =>
    val st = prepareStatement(s.connection, sw.statement)
    fillParamsInto(params, st)
    (st.executeUpdate, st)
  }

  def executeUpdateAndCloseStatement(s: AbstractSession, sw: StatementWriter): Int = exec(s, sw) { params =>
    val st = prepareStatement(s.connection, sw.statement)
    fillParamsInto(params, st)
    try {
      st.executeUpdate
    } finally {
      st.close
    }
  }

  def executeUpdateForInsert(s: AbstractSession, sw: StatementWriter, ps: PreparedStatement) = exec(s, sw) { params =>
    fillParamsInto(params, ps)
    ps.executeUpdate
  }

  protected def getInsertableFields(fmd: Iterable[FieldMetaData]) =
    fmd.filter(fmd => !fmd.isAutoIncremented && fmd.isInsertable)

  def writeInsert[T](o: T, t: Table[T], sw: StatementWriter): Unit = {

    val o_ = o.asInstanceOf[AnyRef]
    val f = getInsertableFields(t.posoMetaData.fieldsMetaData)

    sw.write("insert into ");
    sw.write(quoteName(t.prefixedName));
    sw.write(" (");
    sw.write(f.map(fmd => quoteName(fmd.columnName)).mkString(", "));
    sw.write(") values ");
    sw.write(f.map(fmd => writeValue(o_, fmd, sw)).mkString("(", ",", ")"));
  }

  /**
   * Converts field instances so they can be fed, and understood by JDBC
   * will not do conversion from None/Some, so @arg r should be a java primitive type or
   * a CustomType
   */
  def convertToJdbcValue(r: AnyRef): AnyRef = {

    if (r == null)
      return r

    var v = r

    v match {
      case product: Product1[_] =>
        v = product._1.asInstanceOf[AnyRef]
      case _ =>
    }

    v match {
      case x: java.util.Date if (!v.isInstanceOf[java.sql.Date] && !v.isInstanceOf[Timestamp]) =>
        v = new java.sql.Date(x.getTime)
      case x: scala.math.BigDecimal =>
        v = x.bigDecimal
      case x: scala.Enumeration#Value =>
        v = x.id.asInstanceOf[AnyRef]
      case x: java.util.UUID =>
        v = convertFromUuidForJdbc(x)
      case _ =>
    }

    v
  }

//  see comment in def convertFromBooleanForJdbc
//    if(v.isInstanceOf[java.lang.Boolean])
//      v = convertFromBooleanForJdbc(v)

  // TODO: move to StatementWriter ?
  protected def writeValue(o: AnyRef, fmd: FieldMetaData, sw: StatementWriter): String =
    if (sw.isForDisplay) {
      val v = fmd.getNativeJdbcValue(o)
      if (v != null)
        v.toString
      else
        "null"
    } else {
      sw.addParam(FieldStatementParam(o, fmd))
      "?"
    }

//  protected def writeValue(sw: StatementWriter, v: AnyRef):String =
//    if(sw.isForDisplay) {
//      if(v != null)
//        v.toString
//      else
//        "null"
//    }
//    else {
//      sw.addParam(convertToJdbcValue(v))
//      "?"
//    }

  /**
   * When @arg printSinkWhenWriteOnlyMode is not None, the adapter will not execute any statement, but only silently give it to the String=>Unit closure
   */
  def postCreateTable(t: Table[_], printSinkWhenWriteOnlyMode: Option[String => Unit]) = {}

  def postDropTable(t: Table[_]) = {}

  def createSequenceName(fmd: FieldMetaData) =
    "s_" + fmd.parentMetaData.viewOrTable.name + "_" + fmd.columnName

  def writeConcatFunctionCall(fn: FunctionNode, sw: StatementWriter) = {
    sw.write(fn.name)
    sw.write("(")
    sw.writeNodesWithSeparator(fn.args, ",", false)
    sw.write(")")
  }

  def isFullOuterJoinSupported = true

  def writeUpdate[T](o: T, t: Table[T], sw: StatementWriter, checkOCC: Boolean) = {

    val o_ = o.asInstanceOf[AnyRef]

    sw.write("update ", quoteName(t.prefixedName), " set ")
    sw.nextLine
    sw.indent
    sw.writeLinesWithSeparator(
      t.posoMetaData.fieldsMetaData
        .filter(fmd => !fmd.isIdFieldOfKeyedEntity && fmd.isUpdatable)
        .map(fmd => {
          if (fmd.isOptimisticCounter)
            quoteName(fmd.columnName) + " = " + quoteName(fmd.columnName) + " + 1 "
          else
            quoteName(fmd.columnName) + " = " + writeValue(o_, fmd, sw)
        }),
      ","
    )
    sw.unindent
    sw.write("where")
    sw.nextLine
    sw.indent

    t.posoMetaData.primaryKey
      .getOrElse(
        throw new UnsupportedOperationException(
          "writeUpdate was called on an object that does not extend from KeyedEntity[]"
        )
      )
      .fold(
        pkMd => {
          val (op, vl) =
            if (pkMd.getNativeJdbcValue(o_) == null) (" is ", "null") else (" = ", writeValue(o_, pkMd, sw))
          sw.write(quoteName(pkMd.columnName), op, vl)
        },
        pkGetter => {
          Utils.createQuery4WhereClause(
            t,
            (t0: T) => {
              val ck = pkGetter.invoke(t0).asInstanceOf[CompositeKey]

              val fieldWhere = ck._fields map {
                case fmd if (fmd.getNativeJdbcValue(o_) == null) =>
                  quoteName(fmd.columnName) + " is null"
                case fmd =>
                  quoteName(fmd.columnName) + " = " + writeValue(o_, fmd, sw)
              }
              sw.write(fieldWhere.mkString(" and "))

              new EqualityExpression(
                InternalFieldMapper.intTEF.createConstant(1),
                InternalFieldMapper.intTEF.createConstant(1)
              )
            }
          )
        }
      )

    if (checkOCC)
      t.posoMetaData.optimisticCounter.foreach(occ => {
        sw.write(" and ")
        sw.write(quoteName(occ.columnName))
        sw.write(" = ")
        sw.write(writeValue(o_, occ, sw))
      })
  }

  def writeDelete[T](t: Table[T], whereClause: Option[ExpressionNode], sw: StatementWriter) = {

    sw.write("delete from ")
    sw.write(quoteName(t.prefixedName))
    if (whereClause.isDefined) {
      sw.nextLine
      sw.write("where")
      sw.nextLine
      sw.writeIndented {
        whereClause.get.write(sw)
      }
    }
  }

  /**
   * unused at the moment, since all jdbc drivers adhere to the standard that :
   *  1 == true, false otherwise. If a new driver would not adhere
   * to this, the call can be uncommented in method convertToJdbcValue
   */
  def convertFromBooleanForJdbc(b: Boolean): Boolean = b

  /**
   * unused for the same reason as def convertFromBooleanForJdbc (see comment)
   */
  def convertToBooleanForJdbc(rs: ResultSet, i: Int): Boolean = rs.getBoolean(i)

  def convertFromUuidForJdbc(u: UUID): AnyRef =
    u.toString

  def convertToUuidForJdbc(rs: ResultSet, i: Int): UUID =
    UUID.fromString(rs.getString(i))

  def writeUpdate(t: Table[_], us: UpdateStatement, sw: StatementWriter) = {

    val colsToUpdate = us.columns.iterator

    sw.write("update ")
    sw.write(quoteName(t.prefixedName))
    sw.write(" set")
    sw.indent
    sw.nextLine
    for (z <- us.values.zipi) {
      val col = colsToUpdate.next()
      sw.write(quoteName(col.columnName))
      sw.write(" = ")
      val v = z.element
      col.explicitDbTypeDeclaration match {
        case Some(dbType) if col.explicitDbTypeCast => {
          sw.write("cast(")
          v.write(sw)
          sw.write(s" as ${sw.quoteName(dbType)})")
        }
        case _ => {
          sw.write("(")
          v.write(sw)
          sw.write(")")
        }
      }
      if (!z.isLast) {
        sw.write(",")
        sw.nextLine
      }
    }

    if (t.posoMetaData.isOptimistic) {
      sw.write(",")
      sw.nextLine
      val occ = t.posoMetaData.optimisticCounter.get
      sw.write(quoteName(occ.columnName))
      sw.write(" = ")
      sw.write(quoteName(occ.columnName) + " + 1")
    }

    sw.unindent

    if (us.whereClause.isDefined) {
      sw.nextLine
      sw.write("Where")
      sw.nextLine
      sw.writeIndented {
        us.whereClause.get.write(sw)
      }
    }
  }

  def nvlToken = "coalesce"

  def writeNvlCall(left: ExpressionNode, right: ExpressionNode, sw: StatementWriter) = {
    sw.write(nvlToken)
    sw.write("(")
    left.write(sw)
    sw.write(",")
    right.write(sw)
    sw.write(")")
  }

  /**
   * Figures out from the SQLException (ex.: vendor specific error code) 
   * if it's cause is a NOT NULL constraint violation
   */
  def isNotNullConstraintViolation(e: SQLException): Boolean = false

  def foreignKeyConstraintName(foreignKeyTable: Table[_], idWithinSchema: Int) =
    foreignKeyTable.name + "FK" + idWithinSchema

  def viewAlias(vn: ViewExpressionNode[_]) =
    if (vn.view.prefix.isDefined)
      vn.view.prefix.get + "_" + vn.view.name + vn.uniqueId.get
    else
      vn.view.name + vn.uniqueId.get

  def writeForeignKeyDeclaration(
    foreignKeyTable: Table[_],
    foreignKeyColumnName: String,
    primaryKeyTable: Table[_],
    primaryKeyColumnName: String,
    referentialAction1: Option[ReferentialAction],
    referentialAction2: Option[ReferentialAction],
    fkId: Int
  ) = {

    val sb = new java.lang.StringBuilder(256)

    sb.append("alter table ")
    sb.append(quoteName(foreignKeyTable.prefixedName))
    sb.append(" add constraint ")
    sb.append(quoteName(foreignKeyConstraintName(foreignKeyTable, fkId)))
    sb.append(" foreign key (")
    sb.append(quoteName(foreignKeyColumnName))
    sb.append(") references ")
    sb.append(quoteName(primaryKeyTable.prefixedName))
    sb.append("(")
    sb.append(quoteName(primaryKeyColumnName))
    sb.append(")")

    val f = (ra: ReferentialAction) => {
      sb.append(" on ")
      sb.append(ra.event)
      sb.append(" ")
      sb.append(ra.action)
    }

    referentialAction1.foreach(f)
    referentialAction2.foreach(f)

    sb.toString
  }

  protected def currenSession =
    Session.currentSession

  def writeDropForeignKeyStatement(foreignKeyTable: Table[_], fkName: String) =
    "alter table " + quoteName(foreignKeyTable.prefixedName) + " drop constraint " + quoteName(fkName)

  def dropForeignKeyStatement(foreignKeyTable: Table[_], fkName: String, session: AbstractSession): Unit =
    execFailSafeExecute(writeDropForeignKeyStatement(foreignKeyTable, fkName), e => true)

  def isTableDoesNotExistException(e: SQLException): Boolean

  def supportsForeignKeyConstraints = true

  def writeDropTable(tableName: String) =
    "drop table " + quoteName(tableName)

  def dropTable(t: Table[_]) =
    execFailSafeExecute(writeDropTable(t.prefixedName), e => isTableDoesNotExistException(e))

  def writeCompositePrimaryKeyConstraint(t: Table[_], cols: Iterable[FieldMetaData]) =
    writeUniquenessConstraint(t, cols);

  def writeUniquenessConstraint(t: Table[_], cols: Iterable[FieldMetaData]) = {
    // ALTER TABLE TEST ADD CONSTRAINT NAME_UNIQUE UNIQUE(NAME)
    val sb = new java.lang.StringBuilder(256)

    sb.append("alter table ")
    sb.append(quoteName(t.prefixedName))
    sb.append(" add constraint ")
    sb.append(quoteName(t.prefixedName + "CPK"))
    sb.append(" unique(")
    sb.append(cols.map(_.columnName).map(quoteName(_)).mkString(","))
    sb.append(")")
    sb.toString
  }

  def writeRegexExpression(left: ExpressionNode, pattern: String, sw: StatementWriter): Unit = {
    sw.write("(")
    left.write(sw)
    sw.write(" ~ ?)")
    sw.addParam(ConstantStatementParam(InternalFieldMapper.stringTEF.createConstant(pattern)))
  }

  def writeConcatOperator(left: ExpressionNode, right: ExpressionNode, sw: StatementWriter) = {
    val binaryOpNode = new BinaryOperatorNode(left, right, "||")
    binaryOpNode.doWrite(sw)
  }

//  /**
//   * @nameOfCompositeKey when not None, the column group forms a composite key, 'nameOfCompositeKey' can be used
//   * as part of the name to create a more meaningfull name for the constraint
//   */
//  def writeUniquenessConstraint(columnDefs: collection.Seq[FieldMetaData], nameOfCompositeKey: Option[String]) = ""

  /**
   * @param name the name specified in the Schema, when not None, it  must be used as the name
   * @param nameOfCompositeKey when not None, the column group forms a composite key, 'nameOfCompositeKey' can be used
   * as part of the name to create a more meaningfull name for the constraint, when 'name' is None
   */
  def writeIndexDeclaration(
    columnDefs: collection.Seq[FieldMetaData],
    name: Option[String],
    nameOfCompositeKey: Option[String],
    isUnique: Boolean
  ) = {
    val sb = new java.lang.StringBuilder(256)
    sb.append("create ")

    if (isUnique)
      sb.append("unique ")

    sb.append("index ")

    val tableName = columnDefs.head.parentMetaData.viewOrTable.prefixedName

    if (name.isDefined)
      sb.append(quoteName(name.get))
    else if (nameOfCompositeKey.isDefined)
      sb.append(quoteName("idx" + nameOfCompositeKey.get))
    else
      sb.append(
        quoteName(
          "idx" + generateAlmostUniqueSuffixWithHash(tableName + "-" + columnDefs.map(_.columnName).mkString("-"))
        )
      )

    sb.append(" on ")

    sb.append(quoteName(tableName))

    sb.append(columnDefs.map(_.columnName).map(quoteName(_)).mkString(" (", ",", ")"))

    sb.toString
  }

  /**
   * This will create an probabilistically unique string of length no longer than 11 chars,
   * it can be used to create "almost unique" names where uniqueness is not an absolute requirement,
   * is not ,
   */
  def generateAlmostUniqueSuffixWithHash(s: String): String = {
    val a32 = new java.util.zip.Adler32
    a32.update(s.getBytes)
    a32.getValue.toHexString
  }

  def quoteIdentifier(s: String) = s

  def quoteName(s: String) = s.split('.').map(quoteIdentifier(_)).mkString(".")

  def fieldAlias(n: QueryableExpressionNode, fse: FieldSelectElement) =
    n.alias + "_" + fse.fieldMetaData.columnName

  def aliasExport(parentOfTarget: QueryableExpressionNode, target: SelectElement) =
    parentOfTarget.alias + "_" + target.aliasSegment

  def writeSelectElementAlias(se: SelectElement, sw: StatementWriter) = {
    val a = se.aliasSegment
//    if(a.length > 30)
//      org.squeryl.internals.Utils.throwError("Oracle Bust : " + a)
    sw.write(quoteName(a))
  }

  def databaseTypeFor(fieldMapper: FieldMapper, c: Class[_]): String = {
    val ar = fieldMapper.sampleValueFor(c)
    val decl: String =
      if (ar.isInstanceOf[Enumeration#Value])
        intTypeDeclaration
      else if (classOf[String].isAssignableFrom(c))
        stringTypeDeclaration
      else if (ar.isInstanceOf[java.sql.Timestamp])
        timestampTypeDeclaration
      else if (ar.isInstanceOf[LocalDateTime])
        localDateTimeTypeDeclaration
      else if (ar.isInstanceOf[OffsetTime])
        offsetTimeTypeDeclaration
      else if (ar.isInstanceOf[OffsetDateTime])
        offsetDateTimeTypeDeclaration
      else if (ar.isInstanceOf[java.util.Date])
        dateTypeDeclaration
      else if (ar.isInstanceOf[LocalDate])
        localDateTypeDeclaration
      else if (ar.isInstanceOf[LocalTime])
        localTimeTypeDeclaration
      else if (ar.isInstanceOf[java.lang.Integer])
        intTypeDeclaration
      else if (ar.isInstanceOf[java.lang.Long])
        longTypeDeclaration
      else if (ar.isInstanceOf[java.lang.Boolean])
        booleanTypeDeclaration
      else if (ar.isInstanceOf[java.lang.Double])
        doubleTypeDeclaration
      else if (ar.isInstanceOf[java.lang.Float])
        floatTypeDeclaration
      else if (ar.isInstanceOf[java.util.UUID])
        uuidTypeDeclaration
      else if (classOf[scala.Array[Byte]].isAssignableFrom(c))
        binaryTypeDeclaration
      else if (classOf[BigDecimal].isAssignableFrom(c))
        bigDecimalTypeDeclaration
      else if (classOf[scala.Array[Int]].isAssignableFrom(c))
        intArrayTypeDeclaration
      else if (classOf[scala.Array[Long]].isAssignableFrom(c))
        longArrayTypeDeclaration
      else if (classOf[scala.Array[Double]].isAssignableFrom(c))
        doubleArrayTypeDeclaration
      else if (classOf[scala.Array[String]].isAssignableFrom(c))
        stringArrayTypeDeclaration
      else
        Utils.throwError("unsupported type " + ar.getClass.getCanonicalName)

    decl
  }

  /*
  def writeCastInvocation(e: TypedExpression[_,_], sw: StatementWriter) = {
    sw.write("cast(")
    e.write(sw)

    val dbSpecificType = databaseTypeFor(e.mapper.jdbcClass)

    sw.write(" as ")
    sw.write(dbSpecificType)
    sw.write(")")
  }

  def writeCaseStatement(toMatch: Option[ExpressionNode], cases: Iterable[(ExpressionNode, TypedExpression[_,_])], otherwise: TypedExpression[_,_], sw: StatementWriter) = {

    sw.write("(case ")
    toMatch.foreach(_.write(sw))
    sw.indent
    sw.nextLine

    for(c <- cases) {
      sw.write("when ")
      c._1.write(sw)
      sw.write(" then ")
      writeCastInvocation(c._2, sw)
      sw.nextLine
    }

    sw.write("else ")
    writeCastInvocation(otherwise,sw)
    sw.nextLine
    sw.unindent
    sw.write("end)")
  }
   */

  def jdbcTypeConstantFor(c: Class[_]) =
    c.getCanonicalName match {
      case "java.lang.String" => Types.VARCHAR
      case "java.math.BigDecimal" => Types.DECIMAL
      case "java.lang.Boolean" => Types.BIT
      case "java.lang.Byte" => Types.TINYINT
      case "java.lang.Integer" => Types.INTEGER
      case "java.lang.Long" => Types.BIGINT
      case "java.lang.Float" => Types.FLOAT
      case "java.lang.Double" => Types.DOUBLE
      case "java.lang.Byte[]" => Types.BINARY
      case "byte[]" => Types.BINARY
      case "java.sql.Date" => Types.DATE
      case "java.util.Date" => Types.DATE
      case "java.sql.Timestamp" => Types.TIMESTAMP
      case "java.util.UUID" => Types.VARCHAR
      case "scala.math.BigDecimal" => Types.VARCHAR
      case s: Any =>
        throw new RuntimeException("Don't know jdbc type for " + s)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy