org.squeryl.Table.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of squeryl_2.13 Show documentation
Show all versions of squeryl_2.13 Show documentation
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
}
}