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

org.squeryl.Table.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;

import dsl.ast._
import dsl.{CompositeKey, QueryDsl}
import internals._
import java.sql.Statement
import logging.StackMarker
import collection.mutable.ArrayBuffer

//private [squeryl] object DummySchema extends Schema

class Table[T] private[squeryl] (
  n: String,
  c: Class[T],
  val schema: Schema,
  _prefix: Option[String],
  ked: Option[KeyedEntityDef[T, _]],
  optionalFieldsInfo: Option[Map[String, Class[_]]]
) extends View[T](n, c, schema, _prefix, ked, optionalFieldsInfo) {

  private def _dbAdapter = Session.currentSession.databaseAdapter

  /**
   * @throws SquerylSQLException When a database error occurs or the insert
   * does not result in 1 row
   */
  def insert(t: T): T = StackMarker.lastSquerylStackFrame {

    val o = _callbacks.beforeInsert(t.asInstanceOf[AnyRef])
    val sess = Session.currentSession
    val sw = new StatementWriter(_dbAdapter)
    _dbAdapter.writeInsert(o.asInstanceOf[T], this, sw)

    val st =
      (_dbAdapter.supportsAutoIncrementInColumnDeclaration, posoMetaData.primaryKey) match {
        case (true, a: Any) => sess.connection.prepareStatement(sw.statement, Statement.RETURN_GENERATED_KEYS)
        case (false, Some(Left(pk: FieldMetaData))) => {
          val autoIncPk = new Array[String](1)
          autoIncPk(0) = pk.columnName
          sess.connection.prepareStatement(sw.statement, autoIncPk)
        }
        case a: Any => sess.connection.prepareStatement(sw.statement)
      }

    try {
      val cnt = _dbAdapter.executeUpdateForInsert(sess, sw, st)

      if (cnt != 1)
        throw SquerylSQLException("failed to insert.  Expected 1 row, got " + cnt)

      posoMetaData.primaryKey match {
        case Some(Left(pk: FieldMetaData)) =>
          if (pk.isAutoIncremented) {
            val rs = st.getGeneratedKeys
            try {
              assert(
                rs.next,
                "getGeneratedKeys returned no rows for the auto incremented\n" +
                  " primary key of table '" + name + "' JDBC3 feature might not be supported, \n or" +
                  " column might not be defined as auto increment"
              )
              pk.setFromResultSet(o, rs, 1)
            } finally {
              rs.close
            }
          }
        case a: Any => {}
      }
    } finally {
      st.close
    }

    val r = _callbacks.afterInsert(o).asInstanceOf[T]

    _setPersisted(r)

    r
  }

//  def insert(t: Query[T]) = org.squeryl.internals.Utils.throwError("not implemented")

  def insert(e: Iterable[T]): Unit =
    _batchedUpdateOrInsert(
      e,
      t => posoMetaData.fieldsMetaData.filter(fmd => !fmd.isAutoIncremented && fmd.isInsertable),
      true,
      false
    )

  /**
   * isInsert if statement is insert otherwise update
   */
  private def _batchedUpdateOrInsert(
    e: Iterable[T],
    fmdCallback: T => Iterable[FieldMetaData],
    isInsert: Boolean,
    checkOCC: Boolean
  ): Unit = {

    val it = e.iterator

    if (it.hasNext) {

      val e0 = it.next()
      val fmds = fmdCallback(e0)
      val sess = Session.currentSession
      val dba = _dbAdapter
      val sw = new StatementWriter(dba)
      val forAfterUpdateOrInsert = new ArrayBuffer[AnyRef]

      if (isInsert) {
        val z = _callbacks.beforeInsert(e0.asInstanceOf[AnyRef])
        forAfterUpdateOrInsert += z
        dba.writeInsert(z.asInstanceOf[T], this, sw)
      } else {
        val z = _callbacks.beforeUpdate(e0.asInstanceOf[AnyRef])
        forAfterUpdateOrInsert += z
        dba.writeUpdate(z.asInstanceOf[T], this, sw, checkOCC)
      }

      if (sess.isLoggingEnabled)
        sess.log("Performing batched " + (if (isInsert) "insert" else "update") + " with " + sw.statement)

      val st = sess.connection.prepareStatement(sw.statement)

      try {
        dba.fillParamsInto(sw.params, st)
        st.addBatch

        var updateCount = 1

        while (it.hasNext) {
          val eN0 = it.next().asInstanceOf[AnyRef]
          val eN =
            if (isInsert)
              _callbacks.beforeInsert(eN0)
            else
              _callbacks.beforeUpdate(eN0)

          forAfterUpdateOrInsert += eN

          var idx = 1
          fmds.foreach(fmd => {
            dba.setParamInto(st, FieldStatementParam(eN, fmd), idx)
            idx += 1
          })
          st.addBatch
          updateCount += 1
        }

        val execResults = st.executeBatch

        if (checkOCC)
          for (b <- execResults)
            if (b == 0) {
              val updateOrInsert = if (isInsert) "insert" else "update"
              throw new StaleUpdateException(
                "Attempted to " + updateOrInsert + " stale object under optimistic concurrency control"
              )
            }
      } finally {
        st.close
      }

      for (a <- forAfterUpdateOrInsert)
        if (isInsert) {
          _setPersisted(_callbacks.afterInsert(a).asInstanceOf[T])
        } else
          _callbacks.afterUpdate(a)

    }
  }

  /**
   * Updates without any Optimistic Concurrency Control check
   * @throws SquerylSQLException When a database error occurs or the update
   * does not result in 1 row
   */
  def forceUpdate[K](o: T)(implicit ked: KeyedEntityDef[T, _]) =
    _update(o, false, ked)

  /**
   * @throws SquerylSQLException When a database error occurs or the update
   * does not result in 1 row
   */
  def update(o: T)(implicit ked: KeyedEntityDef[T, _]): Unit =
    _update(o, true, ked)

  def update(o: Iterable[T])(implicit ked: KeyedEntityDef[T, _]): Unit =
    _update(o, ked.isOptimistic)

  def forceUpdate(o: Iterable[T])(implicit ked: KeyedEntityDef[T, _]): Unit =
    _update(o, ked.isOptimistic)

  private def _update(o: T, checkOCC: Boolean, ked: KeyedEntityDef[T, _]) = {

    val dba = Session.currentSession.databaseAdapter
    val sw = new StatementWriter(dba)
    val o0 = _callbacks.beforeUpdate(o.asInstanceOf[AnyRef]).asInstanceOf[T]
    dba.writeUpdate(o0, this, sw, checkOCC)

    val cnt = dba.executeUpdateAndCloseStatement(Session.currentSession, sw)

    if (cnt != 1) {
      if (checkOCC && posoMetaData.isOptimistic) {
        val version = posoMetaData.optimisticCounter.get.getNativeJdbcValue(o.asInstanceOf[AnyRef])
        throw new StaleUpdateException(
          "Object " + prefixedName + "(id=" + ked.getId(o) + ", occVersionNumber=" + version +
            ") has become stale, it cannot be updated under optimistic concurrency control"
        )
      } else
        throw SquerylSQLException("failed to update.  Expected 1 row, got " + cnt)
    }

    _callbacks.afterUpdate(o0.asInstanceOf[AnyRef])
  }

  private def _update(e: Iterable[T], checkOCC: Boolean): Unit = {

    def buildFmds(t: T): Iterable[FieldMetaData] = {
      val pkList = posoMetaData.primaryKey
        .getOrElse(
          org.squeryl.internals.Utils
            .throwError("method was called with " + posoMetaData.clasz.getName + " that is not a KeyedEntity[]")
        )
        .fold(
          pkMd => List(pkMd),
          pkGetter => {
            // Just for side-effect...
            var fields: Option[List[FieldMetaData]] = None
            Utils.createQuery4WhereClause(
              this,
              (t0: T) => {
                val ck = pkGetter.invoke(t0).asInstanceOf[CompositeKey]

                fields = Some(ck._fields.toList)

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

            fields getOrElse (internals.Utils.throwError("No PK fields found"))
          }
        )

      List(
        posoMetaData.fieldsMetaData
          .filter(fmd => !fmd.isIdFieldOfKeyedEntity && !fmd.isOptimisticCounter && fmd.isUpdatable)
          .toList,
        pkList,
        posoMetaData.optimisticCounter.toList
      ).flatten
    }

    _batchedUpdateOrInsert(e, buildFmds _, false, checkOCC)
  }

  def update(s: T => UpdateStatement): Int = {

    val vxn = new ViewExpressionNode(this)
    vxn.sample = posoMetaData.createSample(FieldReferenceLinker.createCallBack(vxn))
    val us = s(vxn.sample)
    vxn.parent = Some(us)

    var idGen = 0
    us.visitDescendants((node, parent, i) => {

      if (node.parent.isEmpty)
        node.parent = parent

      node match {
        case nxn: UniqueIdInAliaseRequired =>
          nxn.uniqueId = Some(idGen)
          idGen += 1
        case _ =>
      }
    })

    vxn.uniqueId = Some(idGen)

    val dba = _dbAdapter
    val sw = new StatementWriter(dba)
    dba.writeUpdate(this, us, sw)
    dba.executeUpdateAndCloseStatement(Session.currentSession, sw)
  }

  def delete(q: Query[T]): Int = {

    val queryAst = q.ast.asInstanceOf[QueryExpressionElements]
    queryAst.inhibitAliasOnSelectElementReference = true

    val sw = new StatementWriter(_dbAdapter)
    _dbAdapter.writeDelete(this, queryAst.whereClause, sw)

    _dbAdapter.executeUpdateAndCloseStatement(Session.currentSession, sw)
  }

  def deleteWhere(whereClause: T => LogicalBoolean)(implicit dsl: QueryDsl): Int =
    delete(dsl.from(this)(t => dsl.where(whereClause(t)).select(t)))

  def delete[K](k: K)(implicit ked: KeyedEntityDef[T, K], dsl: QueryDsl, toCanLookup: K => CanLookup): Boolean = {
    import dsl._
    val q = from(this)(a =>
      dsl.where {
        FieldReferenceLinker
          .createEqualityExpressionWithLastAccessedFieldReferenceAndConstant(ked.getId(a), k, toCanLookup(k))
      } select (a)
    )

    lazy val z = q.headOption

    if (_callbacks.hasBeforeDelete) {
      z.map(x => _callbacks.beforeDelete(x.asInstanceOf[AnyRef]))
    }

    val deleteCount = this.delete(q)

    if (_callbacks.hasAfterDelete) {
      z.map(x => _callbacks.afterDelete(x.asInstanceOf[AnyRef]))
    }

    if (Session.currentSessionOption forall { ses => ses.databaseAdapter.verifyDeleteByPK })
      assert(
        deleteCount <= 1,
        "Query :\n" + q.dumpAst + "\nshould have deleted at most 1 row but has deleted " + deleteCount
      )
    deleteCount > 0
  }

  /**
   * @throws SquerylSQLException When a database error occurs or the operation
   * does not result in 1 row
   */
  def insertOrUpdate(o: T)(implicit ked: KeyedEntityDef[T, _]): T = {
    if (ked.isPersisted(o))
      update(o)
    else
      insert(o)
    o
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy