Full SELECT FROM

main
Oliver Kennedy 2023-11-01 22:40:21 -04:00
parent f17a02d6e1
commit 1834a81929
Signed by: okennedy
GPG Key ID: 3E5F9B3ABD3FDB60
10 changed files with 360 additions and 26 deletions

View File

@ -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))
}
}

View File

@ -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")
}
}

View File

@ -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(_) }

View File

@ -0,0 +1,7 @@
package net.okennedy.hackdb
trait Plan
{
def schema: Seq[(String, Type)]
def read: Iterator[Seq[Constant]]
}

View File

@ -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) }
}
}

View File

@ -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
)
}
}

View File

@ -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")
}

View File

@ -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
)
}
}

View File

@ -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
}
}
}
}

5
test.csv Normal file
View File

@ -0,0 +1,5 @@
a,b,c
1,2,3
4,5,6
1,3,9
2,5,8
1 a b c
2 1 2 3
3 4 5 6
4 1 3 9
5 2 5 8