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

org.scalaquery.ql.extended.DerbyDriver.scala Maven / Gradle / Ivy

package org.scalaquery.ql.extended

import org.scalaquery.SQueryException
import org.scalaquery.ql._
import org.scalaquery.ql.basic._
import org.scalaquery.util._

/**
 * ScalaQuery driver for Derby/JavaDB.
 *
 * 

This driver implements the ExtendedProfile with the following * limitations:

*
    *
  • Functions.database is not available in Derby. ScalaQuery * will return an empty string instead.
  • *
  • Sequence.curr to get the current value of a sequence is * not supported by Derby. Trying to generate SQL code which uses this * feature throws a SQueryException.
  • *
  • Sequence cycling is supported but does not conform to SQL:2008 * semantics. Derby cycles back to the START value instead of MINVALUE or * MAXVALUE.
  • *
* * @author szeiger */ class DerbyDriver extends ExtendedProfile { self => type ImplicitT = ExtendedImplicitConversions[DerbyDriver] type TypeMapperDelegatesT = BasicTypeMapperDelegates val Implicit = new ExtendedImplicitConversions[DerbyDriver] { implicit val scalaQueryDriver = self } val typeMapperDelegates = new DerbyTypeMapperDelegates override def createQueryBuilder(query: Query[_], nc: NamingContext) = new DerbyQueryBuilder(query, nc, None, this) override def buildTableDDL(table: AbstractBasicTable[_]): DDL = new DerbyDDLBuilder(table, this).buildDDL override def buildSequenceDDL(seq: Sequence[_]): DDL = new DerbySequenceDDLBuilder(seq, this).buildDDL } object DerbyDriver extends DerbyDriver class DerbyTypeMapperDelegates extends BasicTypeMapperDelegates { import DerbyTypeMapperDelegates._ override val booleanTypeMapperDelegate = new BooleanTypeMapperDelegate } object DerbyTypeMapperDelegates { /* Derby does not have a proper BOOLEAN type. The suggested workaround is * SMALLINT with constants 1 and 0 for TRUE and FALSE. */ class BooleanTypeMapperDelegate extends BasicTypeMapperDelegates.BooleanTypeMapperDelegate { override def sqlTypeName = "SMALLINT" override def valueToSQLLiteral(value: Boolean) = if(value) "1" else "0" } } class DerbyQueryBuilder(_query: Query[_], _nc: NamingContext, parent: Option[BasicQueryBuilder], profile: DerbyDriver) extends BasicQueryBuilder(_query, _nc, parent, profile) { import ExtendedQueryOps._ import profile.sqlUtils._ override type Self = DerbyQueryBuilder protected def createSubQueryBuilder(query: Query[_], nc: NamingContext) = new DerbyQueryBuilder(query, nc, Some(this), profile) override protected def innerBuildSelectNoRewrite(b: SQLBuilder, rename: Boolean) { query.typedModifiers[TakeDrop] match { case TakeDrop(Some(0), _) :: _ => /* Derby does not allow fetching 0 rows, so we use this workaround to * force the query to return no results */ b += "SELECT * FROM (" super.innerBuildSelectNoRewrite(b, rename) b += ") t0 WHERE 1=0" case _ => super.innerBuildSelectNoRewrite(b, rename) } } override protected def expr(c: Node, b: SQLBuilder, rename: Boolean, topLevel: Boolean): Unit = { c match { /* Convert proper BOOLEANs which should be returned from a SELECT * statement into pseudo-boolean SMALLINT values 1 and 0 */ case c: Column[_] if topLevel && !rename && b == selectSlot && c.typeMapper(profile) == profile.typeMapperDelegates.booleanTypeMapperDelegate => b += "case when " innerExpr(c, b) b += " then 1 else 0 end" case _ => super.expr(c, b, rename, topLevel) } } override protected def innerExpr(c: Node, b: SQLBuilder): Unit = c match { /* Create TRUE and FALSE values because Derby lacks boolean literals */ case c @ ConstColumn(true) => b += "(1=1)" case c @ ConstColumn(false) => b += "(1=0)" /* Convert pseudo-booleans from tables and subqueries to real booleans */ case n: NamedColumn[_] if n.typeMapper(profile) == profile.typeMapperDelegates.booleanTypeMapperDelegate => b += "("; super.innerExpr(c, b); b += " != 0)" case c @ SubqueryColumn(pos, sq, tm) if tm(profile) == profile.typeMapperDelegates.booleanTypeMapperDelegate => b += "("; super.innerExpr(c, b); b += " != 0)" case a @ ColumnOps.AsColumnOf(ch, name) => /* Derby does not support {fn convert}, so we use the SQL CAST syntax */ b += "cast("; expr(ch, b); b += " as " += name.getOrElse(mapTypeName(a.typeMapper(profile))) += ")" case ColumnOps.IfNull(l, r) => r match { /* Derby does not support IFNULL so we use COALESCE instead, * and it requires NULLs to be casted to a suitable type */ case c: Column[_] => b += "coalesce(cast(" expr(l, b) b += " as " += mapTypeName(c.typeMapper(profile)) += ")," expr(r, b); b += ")" case _ => throw new SQueryException("Cannot determine type of right-hand side for ifNull") } case fk: ForeignKey[_] => /* Derby does not support row value constructor syntax (tuple syntax), * so we need to untuple and compare the individual columns (which * should be safe because they may not contain NULLs). */ val cols = untupleColumn(fk.left) zip untupleColumn(fk.right) b += "(" for((l,r) <- b.sep(cols, " and ")) { expr(l, b); b += "="; expr(r, b); } b += ")" case c @ BindColumn(v) if b == selectSlot => /* The Derby embedded driver has a bug (DERBY-4671) which results in a * NullPointerException when using bind variables in a SELECT clause. * This should be fixed in Derby 10.6.1.1. The workaround is to add an * explicit type annotation (in the form of a CAST expression). */ val tmd = c.typeMapper(profile) b += "cast(" b +?= { (p, param) => tmd.setValue(v, p) } b += " as " += mapTypeName(tmd) += ")" /* I guess NEXTVAL was too short */ case Sequence.Nextval(seq) => b += "(next value for " += quoteIdentifier(seq.name) += ")" case Sequence.Currval(seq) => throw new SQueryException("Derby does not support CURRVAL") case SimpleFunction("database", true) => b += "''" case _ => super.innerExpr(c, b) } override protected def insertFromClauses() { super.insertFromClauses() /* At least it makes more sense than calling it "DUAL"... */ if(fromSlot.isEmpty) fromSlot += " FROM sysibm.sysdummy1" } override protected def appendClauses(b: SQLBuilder): Unit = { super.appendClauses(b) appendLimitClause(b) } protected def appendLimitClause(b: SQLBuilder): Unit = query.typedModifiers[TakeDrop].lastOption.foreach { /* Derby uses the SQL:2008 syntax for skip/limit */ case TakeDrop(Some(0), _) => // handled above in innerBuildSelectNoRewrite case TakeDrop(Some(t), Some(d)) => b += " OFFSET " += d += " ROW FETCH NEXT " += t += " ROW ONLY" case TakeDrop(Some(t), None) => b += " FETCH NEXT " += t += " ROW ONLY" case TakeDrop(None, Some(d)) => b += " OFFSET " += d += " ROW" case _ => } override protected def table(t: Node, name: String, b: SQLBuilder): Unit = t match { /* Derby requires columns of UNION parts to have the same names. If my * understanding of SQL:2008 is correct, this is a bug. This behavior * would be correct if the CORRESPONDING keyword was used for a UNION. * The workaround is to rename all parts with the same auto-generated * column names. */ case Subquery(Union(all, sqs), rename) => b += "(" for(sq <- b.sep(sqs, (if(all) " UNION ALL " else " UNION "))) subQueryBuilderFor(sq).innerBuildSelect(b, rename) b += ") " += quoteIdentifier(name) case _ => super.table(t, name, b) } } class DerbyDDLBuilder(table: AbstractBasicTable[_], profile: DerbyDriver) extends BasicDDLBuilder(table, profile) { import profile.sqlUtils._ protected class DerbyColumnDDLBuilder(column: NamedColumn[_]) extends BasicColumnDDLBuilder(column) { override protected def appendOptions(sb: StringBuilder) { if(defaultLiteral ne null) sb append " DEFAULT " append defaultLiteral if(notNull) sb append " NOT NULL" if(primaryKey) sb append " PRIMARY KEY" if(autoIncrement) sb append " GENERATED BY DEFAULT AS IDENTITY" } } override protected def createColumnDDLBuilder(c: NamedColumn[_]) = new DerbyColumnDDLBuilder(c) override protected def createIndex(idx: Index) = { if(idx.unique) { /* Create a UNIQUE CONSTRAINT (with an automatically generated backing * index) because Derby does not allow a FOREIGN KEY CONSTRAINT to * reference columns which have a UNIQUE INDEX but not a nominal UNIQUE * CONSTRAINT. */ val sb = new StringBuilder append "ALTER TABLE " append quoteIdentifier(table.tableName) append " ADD " sb append "CONSTRAINT " append quoteIdentifier(idx.name) append " UNIQUE(" addIndexColumnList(idx.on, sb, idx.table.tableName) sb append ")" sb.toString } else super.createIndex(idx) } } class DerbySequenceDDLBuilder[T](seq: Sequence[T], profile: DerbyDriver) extends BasicSequenceDDLBuilder(seq, profile) { import profile.sqlUtils._ override def buildDDL: DDL = { import seq.integral._ val increment = seq._increment.getOrElse(one) val desc = increment < zero val b = new StringBuilder append "CREATE SEQUENCE " append quoteIdentifier(seq.name) /* Set the START value explicitly because it defaults to the data type's * min/max value instead of the more conventional 1/-1. */ b append " START WITH " append seq._start.getOrElse(if(desc) -1 else 1) seq._increment.foreach { b append " INCREMENT BY " append _ } seq._maxValue.foreach { b append " MAXVALUE " append _ } seq._minValue.foreach { b append " MINVALUE " append _ } /* Cycling is supported but does not conform to SQL:2008 semantics. Derby * cycles back to the START value instead of MINVALUE or MAXVALUE. No good * workaround available AFAICT. */ if(seq._cycle) b append " CYCLE" new DDL { val createPhase1 = Iterable(b.toString) val createPhase2 = Nil val dropPhase1 = Nil val dropPhase2 = Iterable("DROP SEQUENCE " + quoteIdentifier(seq.name)) } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy