Skip to content

Commit 0f6708f

Browse files
author
Stephen Tu
committed
Initial import
0 parents  commit 0f6708f

File tree

14 files changed

+2059
-0
lines changed

14 files changed

+2059
-0
lines changed

README

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SQL Parser written in Scala
2+
===========================
3+
4+
This is an attempt to build a functional SQL parser which resides outside
5+
the context of a specific database system.
6+
7+
So far, only `SELECT` statements are implemented, but more is to come.

build.sbt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
libraryDependencies ++= Seq(
2+
"postgresql" % "postgresql" % "9.1-901-1.jdbc4",
3+
"org.specs2" %% "specs2" % "1.8.2" % "test"
4+
)
5+
6+
scalacOptions += "-deprecation"
7+
8+
scalacOptions += "-unchecked"

src/main/scala/ast.scala

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
trait Node extends PrettyPrinters {
2+
val ctx: Context
3+
4+
def copyWithContext(ctx: Context): Node
5+
6+
// emit sql repr of node
7+
def sql: String
8+
}
9+
10+
case class SelectStmt(projections: Seq[SqlProj],
11+
relations: Option[Seq[SqlRelation]],
12+
filter: Option[SqlExpr],
13+
groupBy: Option[SqlGroupBy],
14+
orderBy: Option[SqlOrderBy],
15+
limit: Option[Int], ctx: Context = null) extends Node {
16+
def copyWithContext(c: Context) = copy(ctx = c)
17+
def sql =
18+
Seq(Some("select"),
19+
Some(projections.map(_.sql).mkString(", ")),
20+
relations.map(x => "from " + x.map(_.sql).mkString(", ")),
21+
filter.map(x => "where " + x.sql),
22+
groupBy.map(_.sql),
23+
orderBy.map(_.sql),
24+
limit.map(x => "limit " + x.toString)).flatten.mkString(" ")
25+
}
26+
27+
trait SqlProj extends Node
28+
case class ExprProj(expr: SqlExpr, alias: Option[String], ctx: Context = null) extends SqlProj {
29+
def copyWithContext(c: Context) = copy(ctx = c)
30+
def sql = Seq(Some(expr.sql), alias).flatten.mkString(" as ")
31+
}
32+
case class StarProj(ctx: Context = null) extends SqlProj {
33+
def copyWithContext(c: Context) = copy(ctx = c)
34+
def sql = "*"
35+
}
36+
37+
trait SqlExpr extends Node {
38+
def getType: DataType = UnknownType
39+
def isLiteral: Boolean = false
40+
41+
// is the r-value of this expression a literal?
42+
def isRValueLiteral: Boolean = isLiteral
43+
44+
// (col, true if aggregate context false otherwise)
45+
// only gathers fields within this context (
46+
// wont traverse into subselects )
47+
def gatherFields: Seq[(FieldIdent, Boolean)]
48+
}
49+
50+
trait Binop extends SqlExpr {
51+
val lhs: SqlExpr
52+
val rhs: SqlExpr
53+
54+
val opStr: String
55+
56+
override def isLiteral = lhs.isLiteral && rhs.isLiteral
57+
def gatherFields = lhs.gatherFields ++ rhs.gatherFields
58+
59+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr): Binop
60+
61+
def sql = Seq("(" + lhs.sql + ")", opStr, "(" + rhs.sql + ")") mkString " "
62+
}
63+
64+
case class Or(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
65+
val opStr = "or"
66+
def copyWithContext(c: Context) = copy(ctx = c)
67+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
68+
}
69+
case class And(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
70+
val opStr = "and"
71+
def copyWithContext(c: Context) = copy(ctx = c)
72+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
73+
}
74+
75+
trait EqualityLike extends Binop
76+
case class Eq(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends EqualityLike {
77+
val opStr = "="
78+
def copyWithContext(c: Context) = copy(ctx = c)
79+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
80+
}
81+
case class Neq(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends EqualityLike {
82+
val opStr = "<>"
83+
def copyWithContext(c: Context) = copy(ctx = c)
84+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
85+
}
86+
87+
trait InequalityLike extends Binop
88+
case class Ge(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends InequalityLike {
89+
val opStr = "<="
90+
def copyWithContext(c: Context) = copy(ctx = c)
91+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
92+
}
93+
case class Gt(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends InequalityLike {
94+
val opStr = "<"
95+
def copyWithContext(c: Context) = copy(ctx = c)
96+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
97+
}
98+
case class Le(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends InequalityLike {
99+
val opStr = ">="
100+
def copyWithContext(c: Context) = copy(ctx = c)
101+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
102+
}
103+
case class Lt(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends InequalityLike {
104+
val opStr = ">"
105+
def copyWithContext(c: Context) = copy(ctx = c)
106+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
107+
}
108+
109+
case class In(elem: SqlExpr, set: Seq[SqlExpr], negate: Boolean, ctx: Context = null) extends SqlExpr {
110+
def copyWithContext(c: Context) = copy(ctx = c)
111+
override def isLiteral =
112+
elem.isLiteral && set.filter(e => !e.isLiteral).isEmpty
113+
def gatherFields =
114+
elem.gatherFields ++ set.flatMap(_.gatherFields)
115+
def sql = Seq(elem.sql, "in", "(", set.map(_.sql).mkString(", "), ")") mkString " "
116+
}
117+
case class Like(lhs: SqlExpr, rhs: SqlExpr, negate: Boolean, ctx: Context = null) extends Binop {
118+
val opStr = if (negate) "not like" else "like"
119+
def copyWithContext(c: Context) = copy(ctx = c)
120+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
121+
}
122+
123+
case class Plus(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
124+
val opStr = "+"
125+
def copyWithContext(c: Context) = copy(ctx = c)
126+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
127+
}
128+
case class Minus(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
129+
val opStr = "-"
130+
def copyWithContext(c: Context) = copy(ctx = c)
131+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
132+
}
133+
134+
case class Mult(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
135+
val opStr = "*"
136+
def copyWithContext(c: Context) = copy(ctx = c)
137+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
138+
}
139+
case class Div(lhs: SqlExpr, rhs: SqlExpr, ctx: Context = null) extends Binop {
140+
val opStr = "/"
141+
def copyWithContext(c: Context) = copy(ctx = c)
142+
def copyWithChildren(lhs: SqlExpr, rhs: SqlExpr) = copy(lhs = lhs, rhs = rhs, ctx = null)
143+
}
144+
145+
trait Unop extends SqlExpr {
146+
val expr: SqlExpr
147+
val opStr: String
148+
override def isLiteral = expr.isLiteral
149+
def gatherFields = expr.gatherFields
150+
def sql = Seq(opStr, "(", expr.sql, ")") mkString " "
151+
}
152+
153+
case class Not(expr: SqlExpr, ctx: Context = null) extends Unop {
154+
val opStr = "not"
155+
def copyWithContext(c: Context) = copy(ctx = c)
156+
}
157+
case class Exists(select: Subselect, ctx: Context = null) extends SqlExpr {
158+
def copyWithContext(c: Context) = copy(ctx = c)
159+
def gatherFields = Seq.empty
160+
def sql = Seq("exists", "(", select.sql, ")") mkString " "
161+
}
162+
163+
case class FieldIdent(qualifier: Option[String], name: String, symbol: Symbol = null, ctx: Context = null) extends SqlExpr {
164+
def copyWithContext(c: Context) = copy(symbol = null, ctx = c)
165+
def gatherFields = Seq((this, false))
166+
def sql = Seq(qualifier, Some(name)).flatten.mkString(".")
167+
}
168+
169+
case class Subselect(subquery: SelectStmt, ctx: Context = null) extends SqlExpr {
170+
def copyWithContext(c: Context) = copy(ctx = c)
171+
def gatherFields = Seq.empty
172+
def sql = "(" + subquery.sql + ")"
173+
}
174+
175+
trait SqlAgg extends SqlExpr
176+
case class CountStar(ctx: Context = null) extends SqlAgg {
177+
def copyWithContext(c: Context) = copy(ctx = c)
178+
def gatherFields = Seq.empty
179+
def sql = "count(*)"
180+
}
181+
case class CountExpr(expr: SqlExpr, distinct: Boolean, ctx: Context = null) extends SqlAgg {
182+
def copyWithContext(c: Context) = copy(ctx = c)
183+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
184+
def sql = Seq(Some("count("), if (distinct) Some("distinct ") else None, Some(expr.sql), Some(")")).flatten.mkString("")
185+
}
186+
case class Sum(expr: SqlExpr, distinct: Boolean, ctx: Context = null) extends SqlAgg {
187+
def copyWithContext(c: Context) = copy(ctx = c)
188+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
189+
def sql = Seq(Some("sum("), if (distinct) Some("distinct ") else None, Some(expr.sql), Some(")")).flatten.mkString("")
190+
}
191+
case class Avg(expr: SqlExpr, distinct: Boolean, ctx: Context = null) extends SqlAgg {
192+
def copyWithContext(c: Context) = copy(ctx = c)
193+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
194+
def sql = Seq(Some("avg("), if (distinct) Some("distinct ") else None, Some(expr.sql), Some(")")).flatten.mkString("")
195+
}
196+
case class Min(expr: SqlExpr, ctx: Context = null) extends SqlAgg {
197+
def copyWithContext(c: Context) = copy(ctx = c)
198+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
199+
def sql = "min(" + expr.sql + ")"
200+
}
201+
case class Max(expr: SqlExpr, ctx: Context = null) extends SqlAgg {
202+
def copyWithContext(c: Context) = copy(ctx = c)
203+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
204+
def sql = "max(" + expr.sql + ")"
205+
}
206+
case class GroupConcat(expr: SqlExpr, sep: String, ctx: Context = null) extends SqlAgg {
207+
def copyWithContext(c: Context) = copy(ctx = c)
208+
def gatherFields = expr.gatherFields.map(_.copy(_2 = true))
209+
def sql = Seq("group_concat(", Seq(expr.sql, _q(sep)).mkString(", "), ")").mkString("")
210+
}
211+
case class AggCall(name: String, args: Seq[SqlExpr], ctx: Context = null) extends SqlAgg {
212+
def copyWithContext(c: Context) = copy(ctx = c)
213+
def gatherFields = args.flatMap(_.gatherFields)
214+
def sql = Seq(name, "(", args.map(_.sql).mkString(", "), ")").mkString("")
215+
}
216+
217+
trait SqlFunction extends SqlExpr {
218+
val name: String
219+
val args: Seq[SqlExpr]
220+
override def isLiteral = args.foldLeft(true)(_ && _.isLiteral)
221+
def gatherFields = args.flatMap(_.gatherFields)
222+
def sql = Seq(name, "(", args.map(_.sql) mkString ", ", ")") mkString ""
223+
}
224+
225+
case class FunctionCall(name: String, args: Seq[SqlExpr], ctx: Context = null) extends SqlFunction {
226+
def copyWithContext(c: Context) = copy(ctx = c)
227+
}
228+
229+
sealed abstract trait ExtractType
230+
case object YEAR extends ExtractType
231+
case object MONTH extends ExtractType
232+
case object DAY extends ExtractType
233+
234+
case class Extract(expr: SqlExpr, what: ExtractType, ctx: Context = null) extends SqlFunction {
235+
val name = "extract"
236+
val args = Seq(expr)
237+
def copyWithContext(c: Context) = copy(ctx = c)
238+
}
239+
240+
case class Substring(expr: SqlExpr, from: Int, length: Option[Int], ctx: Context = null) extends SqlFunction {
241+
val name = "substring"
242+
val args = Seq(expr)
243+
def copyWithContext(c: Context) = copy(ctx = c)
244+
}
245+
246+
case class CaseExprCase(cond: SqlExpr, expr: SqlExpr, ctx: Context = null) extends Node {
247+
def copyWithContext(c: Context) = copy(ctx = c)
248+
def sql = Seq("when", cond.sql, "then", expr.sql) mkString " "
249+
}
250+
case class CaseExpr(expr: SqlExpr, cases: Seq[CaseExprCase], default: Option[SqlExpr], ctx: Context = null) extends SqlExpr {
251+
def copyWithContext(c: Context) = copy(ctx = c)
252+
override def isLiteral =
253+
expr.isLiteral &&
254+
cases.filter(x => !x.cond.isLiteral || !x.expr.isLiteral).isEmpty &&
255+
default.map(_.isLiteral).getOrElse(true)
256+
override def isRValueLiteral =
257+
cases.filterNot(_.expr.isRValueLiteral).isEmpty &&
258+
default.map(_.isRValueLiteral).getOrElse(true)
259+
def gatherFields =
260+
expr.gatherFields ++
261+
cases.flatMap(x => x.cond.gatherFields ++ x.expr.gatherFields) ++
262+
default.map(_.gatherFields).getOrElse(Seq.empty)
263+
def sql = Seq(Some("case"), Some(expr.sql), Some(cases.map(_.sql) mkString " "), default.map(d => "else " + d.sql), Some("end")).flatten.mkString(" ")
264+
}
265+
case class CaseWhenExpr(cases: Seq[CaseExprCase], default: Option[SqlExpr], ctx: Context = null) extends SqlExpr {
266+
def copyWithContext(c: Context) = copy(ctx = c)
267+
override def isLiteral =
268+
cases.filter(x => !x.cond.isLiteral || !x.expr.isLiteral).isEmpty &&
269+
default.map(_.isLiteral).getOrElse(true)
270+
override def isRValueLiteral =
271+
cases.filterNot(_.expr.isRValueLiteral).isEmpty &&
272+
default.map(_.isRValueLiteral).getOrElse(true)
273+
def gatherFields =
274+
cases.flatMap(x => x.cond.gatherFields ++ x.expr.gatherFields) ++
275+
default.map(_.gatherFields).getOrElse(Seq.empty)
276+
def sql = Seq(Some("case"), Some(cases.map(_.sql) mkString " "), default.map(d => "else " + d.sql), Some("end")).flatten.mkString(" ")
277+
}
278+
279+
case class UnaryPlus(expr: SqlExpr, ctx: Context = null) extends Unop {
280+
val opStr = "+"
281+
def copyWithContext(c: Context) = copy(ctx = c)
282+
}
283+
case class UnaryMinus(expr: SqlExpr, ctx: Context = null) extends Unop {
284+
val opStr = "-"
285+
def copyWithContext(c: Context) = copy(ctx = c)
286+
}
287+
288+
trait LiteralExpr extends SqlExpr {
289+
override def isLiteral = true
290+
def gatherFields = Seq.empty
291+
}
292+
case class IntLiteral(v: Long, ctx: Context = null) extends LiteralExpr {
293+
def copyWithContext(c: Context) = copy(ctx = c)
294+
def sql = v.toString
295+
}
296+
case class FloatLiteral(v: Double, ctx: Context = null) extends LiteralExpr {
297+
def copyWithContext(c: Context) = copy(ctx = c)
298+
def sql = v.toString
299+
}
300+
case class StringLiteral(v: String, ctx: Context = null) extends LiteralExpr {
301+
def copyWithContext(c: Context) = copy(ctx = c)
302+
def sql = "\"" + v.toString + "\"" // TODO: escape...
303+
}
304+
case class NullLiteral(ctx: Context = null) extends LiteralExpr {
305+
def copyWithContext(c: Context) = copy(ctx = c)
306+
def sql = "null"
307+
}
308+
case class DateLiteral(d: String, ctx: Context = null) extends LiteralExpr {
309+
def copyWithContext(c: Context) = copy(ctx = c)
310+
def sql = Seq("date", "\"" + d + "\"") mkString " "
311+
}
312+
case class IntervalLiteral(e: String, unit: ExtractType, ctx: Context = null) extends LiteralExpr {
313+
def copyWithContext(c: Context) = copy(ctx = c)
314+
def sql = Seq("interval", "\"" + e + "\"", unit.toString) mkString " "
315+
}
316+
317+
trait SqlRelation extends Node
318+
case class TableRelationAST(name: String, alias: Option[String], ctx: Context = null) extends SqlRelation {
319+
def copyWithContext(c: Context) = copy(ctx = c)
320+
def sql = Seq(Some(name), alias).flatten.mkString(" ")
321+
}
322+
case class SubqueryRelationAST(subquery: SelectStmt, alias: String, ctx: Context = null) extends SqlRelation {
323+
def copyWithContext(c: Context) = copy(ctx = c)
324+
def sql = Seq("(", subquery.sql, ")", "as", alias) mkString " "
325+
}
326+
327+
sealed abstract trait JoinType {
328+
def sql: String
329+
}
330+
case object LeftJoin extends JoinType {
331+
def sql = "left join"
332+
}
333+
case object RightJoin extends JoinType {
334+
def sql = "right join"
335+
}
336+
case object InnerJoin extends JoinType {
337+
def sql = "join"
338+
}
339+
340+
case class JoinRelation(left: SqlRelation, right: SqlRelation, tpe: JoinType, clause: SqlExpr, ctx: Context = null) extends SqlRelation {
341+
def copyWithContext(c: Context) = copy(ctx = c)
342+
def sql = Seq(left.sql, tpe.sql, right.sql, "on", "(", clause.sql, ")") mkString " "
343+
}
344+
345+
sealed abstract trait OrderType
346+
case object ASC extends OrderType
347+
case object DESC extends OrderType
348+
349+
case class SqlGroupBy(keys: Seq[SqlExpr], having: Option[SqlExpr], ctx: Context = null) extends Node {
350+
def copyWithContext(c: Context) = copy(ctx = c)
351+
def sql = Seq(Some("group by"), Some(keys.map(_.sql).mkString(", ")), having.map(e => "having " + e.sql)).flatten.mkString(" ")
352+
}
353+
case class SqlOrderBy(keys: Seq[(SqlExpr, OrderType)], ctx: Context = null) extends Node {
354+
def copyWithContext(c: Context) = copy(ctx = c)
355+
def sql = Seq("order by", keys map (x => x._1.sql + " " + x._2.toString) mkString ", ") mkString " "
356+
}

0 commit comments

Comments
 (0)