Full SELECT FROM
parent
f17a02d6e1
commit
1834a81929
|
@ -2,31 +2,72 @@ package net.okennedy.hackdb
|
|||
|
||||
sealed trait Expression
|
||||
{
|
||||
def eval: Constant
|
||||
def eval(scope: Map[Variable, Constant]): Constant
|
||||
def printString: String
|
||||
|
||||
def children: Seq[Expression]
|
||||
def rebuild(c: Seq[Expression]): Expression
|
||||
|
||||
def transformUp( op: PartialFunction[Expression, Expression] ): Expression =
|
||||
{
|
||||
val v = rebuild(children.map { _.transformUp(op) })
|
||||
op.lift(v).getOrElse(v)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
sealed trait Constant extends Expression
|
||||
sealed trait Constant(val dataType: Type) extends Expression
|
||||
{
|
||||
def eval = this
|
||||
val value: Any
|
||||
def eval(scope: Map[Variable, Constant]) = this
|
||||
def printString = value.toString
|
||||
def children: Seq[Expression] = Seq.empty
|
||||
def rebuild(c: Seq[Expression]): Expression = this
|
||||
}
|
||||
|
||||
object Constant {
|
||||
case class Int(value: java.lang.Integer) extends Constant
|
||||
case class Double(value: java.lang.Double) extends Constant
|
||||
case class String(value: java.lang.String) extends Constant
|
||||
case class Int(value: java.lang.Integer) extends Constant(Type.Int)
|
||||
case class Double(value: java.lang.Double) extends Constant(Type.Double)
|
||||
case class String(value: java.lang.String) extends Constant(Type.String)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
case class Variable(name: String) extends Expression
|
||||
{
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
scope.get(this) match {
|
||||
case Some(c) => c
|
||||
case None =>
|
||||
throw new Exception(
|
||||
s"$name is not defined in ${scope.keys.mkString(", ")}"
|
||||
)
|
||||
}
|
||||
|
||||
def printString = name
|
||||
def children: Seq[Expression] = Seq.empty
|
||||
def rebuild(c: Seq[Expression]): Expression = this
|
||||
|
||||
override def hashCode = name.toLowerCase().hashCode()
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
case Variable(o) => o.equalsIgnoreCase(name)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
object Expression {
|
||||
|
||||
|
||||
case class Add(a: Expression, b: Expression) extends Expression
|
||||
{
|
||||
def eval: Constant =
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
{
|
||||
(a.eval, b.eval) match {
|
||||
(a.eval(scope), b.eval(scope)) match {
|
||||
case (Constant.Int(i), Constant.Int(j)) => Constant.Int(i+j)
|
||||
case (Constant.Double(i), Constant.Double(j)) => Constant.Double(i+j)
|
||||
case (Constant.String(i), Constant.String(j)) => Constant.String(i+j)
|
||||
|
@ -34,52 +75,89 @@ object Expression {
|
|||
}
|
||||
}
|
||||
|
||||
def printString = s"${a.printString} + ${b.printString}"
|
||||
def children: Seq[Expression] = Seq(a, b)
|
||||
def rebuild(c: Seq[Expression]): Expression = copy(a = c(0), b = c(1))
|
||||
|
||||
override def toString: String =
|
||||
s"($a + $b)"
|
||||
}
|
||||
|
||||
case class Sub(a: Expression, b: Expression) extends Expression
|
||||
{
|
||||
def eval: Constant =
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
{
|
||||
(a.eval, b.eval) match {
|
||||
(a.eval(scope), b.eval(scope)) match {
|
||||
case (Constant.Int(i), Constant.Int(j)) => Constant.Int(i-j)
|
||||
case (Constant.Double(i), Constant.Double(j)) => Constant.Double(i-j)
|
||||
case (i, j) => throw new Exception(s"Invalid expression: $a - $b")
|
||||
}
|
||||
}
|
||||
|
||||
def printString = s"${a.printString} - ${b.printString}"
|
||||
def children: Seq[Expression] = Seq(a, b)
|
||||
def rebuild(c: Seq[Expression]): Expression = copy(a = c(0), b = c(1))
|
||||
|
||||
override def toString: String =
|
||||
s"($a - $b)"
|
||||
}
|
||||
|
||||
case class Mult(a: Expression, b: Expression) extends Expression
|
||||
{
|
||||
def eval: Constant =
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
{
|
||||
(a.eval, b.eval) match {
|
||||
(a.eval(scope), b.eval(scope)) match {
|
||||
case (Constant.Int(i), Constant.Int(j)) => Constant.Int(i*j)
|
||||
case (Constant.Double(i), Constant.Double(j)) => Constant.Double(i*j)
|
||||
case (i, j) => throw new Exception(s"Invalid expression: $a * $b")
|
||||
}
|
||||
}
|
||||
|
||||
def printString = s"${a.printString} * ${b.printString}"
|
||||
def children: Seq[Expression] = Seq(a, b)
|
||||
def rebuild(c: Seq[Expression]): Expression = copy(a = c(0), b = c(1))
|
||||
|
||||
override def toString: String =
|
||||
s"($a * $b)"
|
||||
}
|
||||
|
||||
case class Div(a: Expression, b: Expression) extends Expression
|
||||
{
|
||||
def eval: Constant =
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
{
|
||||
(a.eval, b.eval) match {
|
||||
(a.eval(scope), b.eval(scope)) match {
|
||||
case (Constant.Int(i), Constant.Int(j)) => Constant.Int(i / j)
|
||||
case (Constant.Double(i), Constant.Double(j)) => Constant.Double(i / j)
|
||||
case (i, j) => throw new Exception(s"Invalid expression: $a / $b")
|
||||
}
|
||||
}
|
||||
|
||||
def printString = s"${a.printString} / ${b.printString}"
|
||||
def children: Seq[Expression] = Seq(a, b)
|
||||
def rebuild(c: Seq[Expression]): Expression = copy(a = c(0), b = c(1))
|
||||
|
||||
override def toString: String =
|
||||
s"($a / $b)"
|
||||
}
|
||||
|
||||
case class Cast(a: Expression, t: Type) extends Expression
|
||||
{
|
||||
def eval(scope: Map[Variable, Constant]): Constant =
|
||||
(t, a.eval(scope)) match {
|
||||
case (Type.Int, c:Constant.Int) => c
|
||||
case (Type.Int, Constant.Double(c)) => Constant.Int(c.toInt)
|
||||
case (Type.Int, Constant.String(c)) => Constant.Int(c.toInt)
|
||||
case (Type.Double, c:Constant.Double) => c
|
||||
case (Type.Double, Constant.Int(c)) => Constant.Double(c.toDouble)
|
||||
case (Type.Double, Constant.String(c)) => Constant.Double(c.toDouble)
|
||||
case (Type.String, c:Constant.String) => c
|
||||
case (Type.String, Constant.Int(c)) => Constant.String(c.toString)
|
||||
case (Type.String, Constant.Double(c)) => Constant.String(c.toString)
|
||||
}
|
||||
|
||||
override def toString = s"CAST(${a} AS $t)"
|
||||
override def printString = s"CAST(${a.printString} AS $t)"
|
||||
def children: Seq[Expression] = Seq(a)
|
||||
def rebuild(c: Seq[Expression]): Expression = copy(a = c(0))
|
||||
}
|
||||
}
|
|
@ -9,8 +9,7 @@ object HackDB
|
|||
{
|
||||
Parser(q) match {
|
||||
case Parsed.Success(expr, _) =>
|
||||
println(s"Parsed: ${expr}")
|
||||
println(s"Value: ${expr.eval}")
|
||||
println(s"${expr.eval}")
|
||||
println("")
|
||||
case Parsed.Failure(expected, pos, msg) =>
|
||||
println(s"Parse Failed [$msg; $pos]")
|
||||
|
@ -30,5 +29,7 @@ object HackDB
|
|||
query("SELECT 'foo'")
|
||||
query("SELECT 'foo'+'bar'")
|
||||
query("SELECT 5*4 FROM test.csv")
|
||||
query("SELECT a, b FROM test.csv")
|
||||
query("SELECT a, b, a+b FROM test.csv")
|
||||
}
|
||||
}
|
|
@ -1,22 +1,47 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
import fastparse._, SingleLineWhitespace._
|
||||
import fastparse._, NoWhitespace._
|
||||
|
||||
object Parser
|
||||
{
|
||||
def apply(str: String) =
|
||||
parse(str, query(_))
|
||||
|
||||
def query[$: P]: P[Expression] = P(
|
||||
IgnoreCase("select") ~
|
||||
expression
|
||||
|
||||
def ws[$: P]: P[Unit] = CharIn(" \r\n\t").rep
|
||||
|
||||
///////////////////// Query Bits ////////////////////////
|
||||
def query[$: P]: P[Query] = P(
|
||||
IgnoreCase("select") ~ ws ~
|
||||
expressionList ~ ws ~
|
||||
from.?
|
||||
).map { (exprs, table) =>
|
||||
Query(exprs = exprs, table = table)
|
||||
}
|
||||
|
||||
def from[$: P]: P[Table] = P(
|
||||
IgnoreCase("from") ~/ ws ~
|
||||
table
|
||||
)
|
||||
|
||||
def table[$: P]: P[Table] = P(
|
||||
(
|
||||
((!".") ~ AnyChar).rep(1) ~ ".".! ~
|
||||
AnyChar.rep(1)
|
||||
).!.map { Table.fromFile(_) }
|
||||
)
|
||||
|
||||
///////////////////// Expressions ///////////////////////
|
||||
|
||||
def expressionList[$: P]: P[Seq[Expression]] = P(
|
||||
expression ~ ("," ~/ ws ~ expression).rep
|
||||
).map { (h, r) => h +: r }
|
||||
|
||||
def expression[$: P]: P[Expression] = P(addSub)
|
||||
|
||||
def addSub[$: P]: P[Expression] = P(
|
||||
multDiv ~
|
||||
(CharIn("+\\-").! ~/ multDiv).rep
|
||||
multDiv ~ ws ~
|
||||
(CharIn("+\\-").! ~/ ws ~ multDiv).rep
|
||||
).map { case (head, rest) =>
|
||||
rest.foldLeft(head:Expression) {
|
||||
case (accum, ("+", nextTerm)) =>
|
||||
|
@ -24,14 +49,15 @@ object Parser
|
|||
case (accum, ("-", nextTerm)) =>
|
||||
Expression.Sub(accum, nextTerm)
|
||||
case (accum, (_, nextTerm)) =>
|
||||
assert(false, "Invalid add/sub op")
|
||||
// this should never happen.
|
||||
assert(false, "Invalid addition or subtraction")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def multDiv[$: P]: P[Expression] = P(
|
||||
constant ~
|
||||
(CharIn("*/").! ~/ constant).rep
|
||||
expressionLeaf ~ ws ~
|
||||
(CharIn("*/").! ~/ ws ~ expressionLeaf).rep
|
||||
).map { case (head, rest) =>
|
||||
rest.foldLeft(head:Expression) {
|
||||
case (accum, ("*", nextTerm)) =>
|
||||
|
@ -39,11 +65,22 @@ object Parser
|
|||
case (accum, ("/", nextTerm)) =>
|
||||
Expression.Div(accum, nextTerm)
|
||||
case (accum, (_, nextTerm)) =>
|
||||
assert(false, "Invalid add/sub op")
|
||||
// this should never happen.
|
||||
assert(false, "Invalid multiplication or division")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def expressionLeaf[$: P]: P[Expression] = P(
|
||||
constant | variable
|
||||
)
|
||||
|
||||
def variable[$: P]: P[Expression] = P(
|
||||
(
|
||||
CharIn("a-zA-Z").rep(1)
|
||||
).!.map { v => Variable(v) }
|
||||
)
|
||||
|
||||
def constant[$: P]: P[Constant] = P(
|
||||
quotedString.map { Constant.String(_) }
|
||||
| double.map { Constant.Double(_) }
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
trait Plan
|
||||
{
|
||||
def schema: Seq[(String, Type)]
|
||||
def read: Iterator[Seq[Constant]]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
case class Project(exprs: Seq[Expression], child: Plan) extends Plan
|
||||
{
|
||||
def schema = exprs.map { e => (e.printString, Type.of(e, vars.toMap)) }
|
||||
|
||||
lazy val vars: Seq[(Variable, Type)] =
|
||||
child.schema.map { x => (Variable(x._1), x._2) }
|
||||
|
||||
def read: Iterator[Seq[Constant]] =
|
||||
child.read.map { row =>
|
||||
exprs.map { _.eval(vars.map { _._1 }.zip(row).toMap) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
case class Query(
|
||||
exprs: Seq[Expression],
|
||||
table: Option[Table]
|
||||
)
|
||||
{
|
||||
lazy val plan: Plan =
|
||||
{
|
||||
var ret:Plan =
|
||||
table.getOrElse { Table(Seq(), Seq(Seq())) }
|
||||
|
||||
ret = Project(exprs, ret)
|
||||
|
||||
println(ret.toString)
|
||||
|
||||
/* return */ ret
|
||||
}
|
||||
|
||||
def eval: Result =
|
||||
{
|
||||
Result(
|
||||
plan.schema,
|
||||
plan.read.toSeq
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
case class Result(header: Seq[(String, Type)], rows: Seq[Seq[Constant]])
|
||||
{
|
||||
lazy val widths =
|
||||
rows.foldLeft( header.toSeq.map { _._1.size } )
|
||||
{ (accum, row) =>
|
||||
val len = Math.max(accum.size, row.size)
|
||||
row.map { _.printString.length }
|
||||
.padTo(len, 0)
|
||||
.zip(accum.padTo(len, 0))
|
||||
.map { x => Math.max(x._1, x._2) }
|
||||
}
|
||||
|
||||
private def formatRow(row: Iterable[String]): String =
|
||||
row.toSeq
|
||||
.padTo(widths.length, "")
|
||||
.zip(widths)
|
||||
.map { (column, width) =>
|
||||
column + (" " * (width - column.length))
|
||||
}
|
||||
.mkString(" | ")
|
||||
|
||||
override def toString =
|
||||
" " + formatRow(header.map { _._1 }) + "\n" +
|
||||
"-" + widths.map { w => "-"*w }.mkString("-+-") + "-\n" +
|
||||
rows.map { row => " " + formatRow(row.map { _.printString }) }
|
||||
.mkString("\n")
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
import scala.io.Source
|
||||
|
||||
case class Table(schema: Seq[(String, Type)], data: Seq[Seq[Constant]]) extends Plan
|
||||
{
|
||||
def read: Iterator[Seq[Constant]] =
|
||||
data.iterator
|
||||
}
|
||||
|
||||
object Table
|
||||
{
|
||||
def fromFile(file: String): Table =
|
||||
{
|
||||
val contents =
|
||||
Source.fromFile(file)
|
||||
.getLines()
|
||||
.map { _.split(",") }
|
||||
|
||||
val header = contents.next()
|
||||
val data = contents.toSeq
|
||||
|
||||
val types = data.foldLeft(header.map { _ => Type.Int:Type }) {
|
||||
(types, row) =>
|
||||
row.map { Type.guess(_) }
|
||||
.zip(types)
|
||||
.map {
|
||||
case (Type.Int, Type.Int) => Type.Int
|
||||
case (Type.Double, Type.Int) => Type.Double
|
||||
case (Type.Int, Type.Double) => Type.Double
|
||||
case (Type.Double, Type.Double) => Type.Double
|
||||
case (Type.String, _) => Type.String
|
||||
case (_, Type.String) => Type.String
|
||||
}
|
||||
}
|
||||
|
||||
Table(
|
||||
header.zip(types).toSeq,
|
||||
data.map {
|
||||
_.zip(types)
|
||||
.map {
|
||||
case (a, Type.Int) => Constant.Int(a.toInt)
|
||||
case (a, Type.Double) => Constant.Double(a.toDouble)
|
||||
case (a, Type.String) => Constant.String(a)
|
||||
}
|
||||
.toSeq
|
||||
}.toSeq
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package net.okennedy.hackdb
|
||||
|
||||
sealed trait Type
|
||||
|
||||
object Type
|
||||
{
|
||||
case object Int extends Type
|
||||
case object Double extends Type
|
||||
case object String extends Type
|
||||
|
||||
def assertSame(expected: Type, got: Type): Type =
|
||||
{
|
||||
assert(expected == got)
|
||||
expected
|
||||
}
|
||||
|
||||
def assertSame(elems: Type*): Type =
|
||||
elems.tail.foldLeft(elems.head) { assertSame(_, _) }
|
||||
|
||||
def assertIn(expected: Type*)(got: Type): Type =
|
||||
{
|
||||
assert(expected contains got)
|
||||
got
|
||||
}
|
||||
|
||||
|
||||
def of(expr: Expression, scope: Map[Variable, Type]): Type =
|
||||
expr match {
|
||||
case c:Constant => c.dataType
|
||||
case a:Variable => scope(a)
|
||||
case Expression.Add(a, b) => assertIn(Type.Int, Type.Double, Type.String)(
|
||||
assertSame(of(a, scope), of(b, scope)))
|
||||
case Expression.Sub(a, b) => assertIn(Type.Int, Type.Double)(
|
||||
assertSame(of(a, scope), of(b, scope)))
|
||||
case Expression.Mult(a, b) => assertIn(Type.Int, Type.Double)(
|
||||
assertSame(of(a, scope), of(b, scope)))
|
||||
case Expression.Div(a, b) => assertIn(Type.Int, Type.Double)(
|
||||
assertSame(of(a, scope), of(b, scope)))
|
||||
|
||||
case Expression.Cast(a, t) => t
|
||||
}
|
||||
|
||||
def fixBin(a: Expression, b: Expression, scope: Map[Variable, Type]): (Expression, Expression) =
|
||||
{
|
||||
import Expression._
|
||||
(of(a, scope), of(b, scope)) match {
|
||||
case (Int, Int) => (a, b)
|
||||
case (Int, Double) => (Cast(a, Double), b)
|
||||
case (Int, String) => (Cast(a, String), b)
|
||||
case (Double, Int) => (a, Cast(b, Double))
|
||||
case (String, Int) => (a, Cast(b, String))
|
||||
|
||||
case (Double, Double) => (a, b)
|
||||
case (Double, String) => (Cast(a, String), b)
|
||||
case (String, Double) => (a, Cast(b, String))
|
||||
|
||||
case (String, String) => (a, b)
|
||||
}
|
||||
}
|
||||
|
||||
def fix(expr: Expression, scope: Map[Variable, Type]): Expression =
|
||||
{
|
||||
import Expression._
|
||||
expr.transformUp {
|
||||
case Add(a, b) => val (x, y) = fixBin(a, b, scope); Add(x, y)
|
||||
case Sub(a, b) => val (x, y) = fixBin(a, b, scope); Sub(x, y)
|
||||
case Mult(a, b) => val (x, y) = fixBin(a, b, scope); Mult(x, y)
|
||||
case Div(a, b) => val (x, y) = fixBin(a, b, scope); Div(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
def guess(datum: String): Type =
|
||||
{
|
||||
try {
|
||||
datum.toInt; Int
|
||||
} catch {
|
||||
case _:NumberFormatException =>
|
||||
try {
|
||||
datum.toDouble; Double
|
||||
} catch {
|
||||
case _:NumberFormatException =>
|
||||
String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue