Last active
September 18, 2019 14:57
-
-
Save lukaseder/ebc7c9d9a2eb178939b2149014e75581 to your computer and use it in GitHub Desktop.
A simple proof of concept for a new Query Object Model in Scala, using sealed traits and case classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Q {} | |
trait QField[T] extends Q {} | |
trait QTable extends Q {} | |
trait QSchema extends Q {} | |
trait QCondition extends Q {} | |
trait QName extends Q {} | |
trait QQuery extends Q {} | |
final case class CVal[T](value: T) extends QField[T] | |
final case class CIdent(name: String) extends QName | |
final case class CFieldRef[T](table: QTable, ident: CIdent) extends QField[T] | |
final case class CTableRef(schema: QSchema, ident: CIdent) extends QTable | |
final case class CJoin(lhs: QTable, rhs: QTable, on: QCondition) extends QTable | |
final case class CSchemaRef(ident: CIdent) extends QSchema | |
final case class CEq[T](lhs: QField[T], rhs: QField[T]) extends QCondition | |
final case class CIn[T](lhs: QField[T], rhs: List[QField[T]]) extends QCondition | |
final case class CAnd(lhs: QCondition, rhs: QCondition) extends QCondition | |
final case class CSelect(select: List[QField[_]], from: QTable, where: QCondition) extends QQuery | |
object QOM { | |
val st = CTableRef(CSchemaRef(CIdent("S")), CIdent("T")) | |
val stc = CFieldRef[Int](st, CIdent("C")) | |
val su = CTableRef(CSchemaRef(CIdent("S")), CIdent("U")) | |
val suc = CFieldRef[Int](su, CIdent("C")) | |
def main(args: Array[String]): Unit = { | |
val list = List( | |
CSelect( | |
select = List(stc, suc), | |
from = CJoin(st, su, CEq(stc, suc)), | |
where = CAnd( | |
CEq(stc, CVal(2)), | |
CEq(suc, CVal(2)) | |
) | |
) | |
) | |
list.foreach(println) | |
println | |
list.map(render).foreach(println) | |
println | |
list.map(map).foreach(println) | |
println | |
list.map(map).map(render).foreach(println) | |
println | |
} | |
def map[T <: Q](q: T): T = { | |
q match { | |
case CVal(value) => CVal(value).asInstanceOf[T] | |
case CIdent(name) => CIdent(name).asInstanceOf[T] | |
case CFieldRef(table, ident) => CFieldRef(map(table), map(ident)).asInstanceOf[T] | |
case CTableRef(schema, ident) => | |
CTableRef( | |
map(schema), | |
if (ident.name == "T") CIdent("X") else ident | |
).asInstanceOf[T] | |
case CJoin(lhs, rhs, on) => CJoin(map(lhs), map(rhs), map(on)).asInstanceOf[T] | |
case CSchemaRef(ident) => CSchemaRef(map(ident)).asInstanceOf[T] | |
case CEq(lhs, rhs) => CEq(map(lhs), map(rhs)).asInstanceOf[T] | |
case CAnd(lhs, rhs) => CAnd(map(lhs), map(rhs)).asInstanceOf[T] | |
case CSelect(select, from, where) => | |
CSelect( | |
select.map(map), | |
map(from), | |
if (contains(from, su)) | |
CAnd(map(where), CIn(suc, List(CVal(1), CVal(2), CVal(3)))) | |
else | |
map(where) | |
).asInstanceOf[T] | |
} | |
} | |
def contains(q: QTable, t: CTableRef): Boolean = { | |
q match { | |
case CTableRef(schema, ident) => t.schema == schema && t.ident == ident | |
case CJoin(lhs, rhs, _) => contains(lhs, t) || contains(rhs, t) | |
case _ => false | |
} | |
} | |
def render(q: Q): String = render(q, StringBuilder.newBuilder).toString | |
def render(q: Q, sb: StringBuilder): StringBuilder = { | |
q match { | |
case CVal(value) => sb.append(value) | |
case CIdent(name) => sb.append(name) | |
case CFieldRef(table, ident) => { | |
render(table, sb) | |
sb.append(".") | |
render(ident, sb) | |
} | |
case CTableRef(schema, ident) => { | |
render(schema, sb) | |
sb.append(".") | |
render(ident, sb) | |
} | |
case CJoin(lhs, rhs, on) => { | |
render(lhs, sb) | |
sb.append(" JOIN ") | |
render(rhs, sb) | |
sb.append(" ON ") | |
render(on, sb); | |
} | |
case CSchemaRef(ident) => render(ident, sb) | |
case CEq(lhs, rhs) => { | |
render(lhs, sb) | |
sb.append(" = ") | |
render(rhs, sb) | |
} | |
case CIn(lhs, rhs) => { | |
render(lhs, sb) | |
sb.append(" IN (") | |
rhs.zipWithIndex.foreach(t => { | |
if (t._2 > 0) | |
sb.append(", ") | |
render(t._1, sb); | |
}) | |
sb.append(")") | |
} | |
case CAnd(lhs, rhs) => { | |
render(lhs, sb) | |
sb.append(" AND ") | |
render(rhs, sb) | |
} | |
case CSelect(select, from, where) => { | |
sb.append("SELECT ") | |
select.zipWithIndex.foreach(t => { | |
if (t._2 > 0) | |
sb.append(", ") | |
render(t._1, sb); | |
}) | |
sb.append(" FROM ") | |
render(from, sb); | |
sb.append(" WHERE ") | |
render(where, sb); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment