Skip to content
Permalink
Browse files

Try for SQL String Indentation.

Hi.

At first, My English is not good.

This work is SQL-Indentation.
This work doesn't Commercial Quality Indentation.
However, When debug, if there are any indentation features, I think that it is better than nothing.

My original purpose was doing indentation per each session.
Let's assume that there is a the sqlIndent flag in scala.slick.backend.SessionDef.
I couldn't know how to pass this flag status to scala.slick.compiler.CompilerState.
So, In this work, this indentation flag is configured in each JdbcStatementBuilderComponent instances.
So, If you create a driver instance, you can't change this flag status with the instance.
If you want a different flag status, you should create new driver instance.

Usage is like below.

trait NewH2Driver extends H2Driver { driver =>

  override val sqlIndent: Boolean = true
...
}

object NewH2Driver extends NewH2Driver

By the way, this Pull Request's the sqlIndent flag's default value is true.
Because, I wanted to show that this feature doesn't make any side-effect about every calculations and results.
And, I couldn't know how to change indentation flag for only Test drivers.

Thanks.
  • Loading branch information
imcharsi committed Jul 11, 2014
1 parent 2b5e2e1 commit 7a58f7f77d1440091bb70762daf62d0d8876f3b1
@@ -44,6 +44,7 @@ testkit {
${testPackage}.TemplateTest
${testPackage}.TransactionTest
${testPackage}.UnionTest
${testPackage}.SqlIndentTest
]
}

@@ -0,0 +1,97 @@
package com.typesafe.slick.testkit.tests

import java.util.logging.Logger

import com.typesafe.slick.testkit.util.{JdbcTestDB, TestkitTest}
import org.junit.Assert._

import scala.slick.driver.MySQLDriver

class SqlIndentTest extends TestkitTest[JdbcTestDB] {

import tdb.profile.simple._

override val reuseInstance = true

class Managers(tag: Tag) extends Table[(Int, String, String)](tag, "managers") {
def id = column[Int]("id")
def name = column[String]("name")
def department = column[String]("department")
def * = (id, name, department)
}

lazy val managers = TableQuery[Managers]

def testBasic {
(managers.ddl).create

val expectedOne = Some( """select x2.x3, x2.x4, x2.x5, x2.x6
|from (
| select x7."id" as x3, x7."name" as x4, x7."department" as x5, cast((
| select sum(x8."id")
| from "managers" x8
| where x8."id" = x7."id"
| ) as INTEGER) as x6
| from "managers" x7
| where (x7."id" < 10) and (x7."id" in (
| select x9."id"
| from "managers" x9
| ))
|) x2
|order by x2.x6""").map(_.stripMargin).map(_.toLowerCase)
val mysqlExpectedOne = Some( """select x2.x3, x2.x4, x2.x5, x2.x6
|from (
| select x7.`id` as x3, x7.`name` as x4, x7.`department` as x5, {fn convert(
| select sum(x8.`id`)
| from `managers` x8
| where x8.`id` = x7.`id`
| ,INTEGER)} as x6
| from `managers` x7
| where (x7.`id` < 10) and (x7.`id` in (
| select x9.`id`
| from `managers` x9
| ))
|) x2
|order by x2.x6""").map(_.stripMargin).map(_.toLowerCase)
val expectedTwo = Some( """select x2.x3, x2.x4, x2.x5
|from (
| select x6."id" as x3, x6."name" as x4, x6."department" as x5
| from "managers" x6
|) x2
|inner join (
| select x7."id" as x8, x7."name" as x9, x7."department" as x10
| from "managers" x7
|) x11
|on x2.x3 = x11.x8
|group by x2.x3, x2.x4, x2.x5""").map(_.stripMargin).map(_.toLowerCase)
val mysqlExpectedTwo = Some( """select x2.x3, x2.x4, x2.x5
|from (
| select x6.`id` as x3, x6.`name` as x4, x6.`department` as x5
| from `managers` x6
|) x2
|inner join (
| select x7.`id` as x8, x7.`name` as x9, x7.`department` as x10
| from `managers` x7
|) x11
|on x2.x3 = x11.x8
|group by x2.x3, x2.x4, x2.x5""").map(_.stripMargin).map(_.toLowerCase)

val resultOne = managers.
filter(t => t.id < 10 && t.id.in(managers.map(_.id))).
map(t => (t, managers.filter(x => x.id === t.id).map(_.id).sum.asColumnOf[Option[Int]])).
sortBy(t => t._2).selectStatement
val resultTwo = managers.
join(managers).on(_.id === _.id).
groupBy { case (a, b) => a}.map { case (g, _) => g}.selectStatement
if (tdb.driver == MySQLDriver) {
assertEquals(mysqlExpectedOne.get, resultOne.toLowerCase)
assertEquals(mysqlExpectedTwo.get, resultTwo.toLowerCase)
}
else {
assertEquals(expectedOne.get, resultOne.toLowerCase)
assertEquals(expectedTwo.get, resultTwo.toLowerCase)
}

(managers.ddl).drop
}
}
@@ -77,9 +77,9 @@ trait H2Driver extends JdbcDriver { driver =>
}

override protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = (fetch, offset) match {
case (Some(t), Some(d)) => b" limit $t offset $d"
case (Some(t), None ) => b" limit $t"
case (None, Some(d) ) => b" limit -1 offset $d"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}limit $t offset $d"
case (Some(t), None ) => b"${sqlIndentPrefix}limit $t"
case (None, Some(d) ) => b"${sqlIndentPrefix}limit -1 offset $d"
case _ =>
}
}
@@ -82,9 +82,9 @@ trait HsqldbDriver extends JdbcDriver { driver =>
}

override protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = (fetch, offset) match {
case (Some(t), Some(d)) => b" limit $t offset $d"
case (Some(t), None ) => b" limit $t"
case (None, Some(d) ) => b" offset $d"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}limit $t offset $d"
case (Some(t), None ) => b"${sqlIndentPrefix}limit $t"
case (None, Some(d) ) => b"${sqlIndentPrefix}offset $d"
case _ =>
}
}
@@ -28,6 +28,8 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
def createColumnDDLBuilder(column: FieldSymbol, table: Table[_]): ColumnDDLBuilder = new ColumnDDLBuilder(column)
def createSequenceDDLBuilder(seq: Sequence[_]): SequenceDDLBuilder = new SequenceDDLBuilder(seq)

val sqlIndent: Boolean = true

class JdbcCompiledInsert(source: Node) {
class Artifacts(val compiled: Node, val converter: ResultConverter[JdbcResultConverterDomain, Any], val ibr: InsertBuilderResult) {
def table: TableNode = ibr.table
@@ -103,7 +105,7 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
protected val pi = "3.1415926535897932384626433832795"

// Mutable state accessible to subclasses
protected val b = new SQLBuilder
protected val b = new SQLBuilder(sqlIndent)
protected var currentPart: StatementPart = OtherPart
protected val symbolName = new QuotingSymbolNamer(Some(state.symbolNamer))
protected val joins = new HashMap[Symbol, Join]
@@ -145,7 +147,7 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
scanJoins(c.from)
buildSelectClause(c)
buildFromClause(c.from)
if(limit0) b" where 1=0"
if(limit0) b"${sqlIndentPrefix}where 1=0"
else buildWhereClause(c.where)
buildGroupByClause(c.groupBy)
buildOrderByClause(c.orderBy)
@@ -181,46 +183,56 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
}
}

protected def sqlIndentPrefix: String = {
if (sqlIndent) {
b.newLine()
""
}
else " "
}

protected def buildFromClause(from: Seq[(Symbol, Node)]) = building(FromPart) {
if(from.isEmpty) scalarFrom.foreach { s => b" from $s" }
if(from.isEmpty) scalarFrom.foreach { s => b"${sqlIndentPrefix}from $s" }
else {
b" from "
b.sep(from, ", ") { case (sym, n) => buildFrom(n, Some(sym)) }
b"${sqlIndentPrefix}from "
b.sep(from, f", ") { case (sym, n) => buildFrom(n, Some(sym)) }
}
}

protected def buildWhereClause(where: Seq[Node]) = building(WherePart) {
if(!where.isEmpty) {
b" where "
b"${sqlIndentPrefix}where "
expr(where.reduceLeft((a, b) => Library.And.typed[Boolean](a, b)), true)
}
}

protected def buildGroupByClause(groupBy: Option[Node]) = building(OtherPart) {
groupBy.foreach { e => b" group by !$e" }
groupBy.foreach { e => b"${sqlIndentPrefix}group by !$e" }
}

protected def buildOrderByClause(order: Seq[(Node, Ordering)]) = building(OtherPart) {
if(!order.isEmpty) {
b" order by "
b"${sqlIndentPrefix}order by "
b.sep(order, ", "){ case (n, o) => buildOrdering(n, o) }
}
}

protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = building(OtherPart) {
(fetch, offset) match {
/* SQL:2008 syntax */
case (Some(t), Some(d)) => b" offset $d row fetch next $t row only"
case (Some(t), None) => b" fetch next $t row only"
case (None, Some(d)) => b" offset $d row"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}offset $d row fetch next $t row only"
case (Some(t), None) => b"${sqlIndentPrefix}fetch next $t row only"
case (None, Some(d)) => b"${sqlIndentPrefix}offset $d row"
case _ =>
}
}

protected def buildSelectPart(n: Node): Unit = n match {
case c: Comprehension =>
b"("
b.addIndentLevel().newLine()
buildComprehension(c)
b.removeIndentLevel().newLine()
b")"
case n =>
expr(n, true)
@@ -234,23 +246,27 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
addAlias
case j @ Join(leftGen, rightGen, left, right, jt, on) =>
buildFrom(left, Some(leftGen))
b" ${jt.sqlName} join "
b"${sqlIndentPrefix}${jt.sqlName} join "
buildFrom(right, Some(rightGen))
on match {
case LiteralNode(true) =>
if(!supportsEmptyJoinConditions) b" on 1=1"
case _ => b" on !$on"
if(!supportsEmptyJoinConditions) b"${sqlIndentPrefix}on 1=1"
case _ => b"${sqlIndentPrefix}on !$on"
}
case Union(left, right, all, _, _) =>
b"\("
b.addIndentLevel().newLine()
buildFrom(left, None, true)
if(all) b" union all " else b" union "
if(all) b"${sqlIndentPrefix}union all " else b"${sqlIndentPrefix}union "
buildFrom(right, None, true)
b.removeIndentLevel().newLine()
b"\)"
addAlias
case n =>
b"\("
b.addIndentLevel().newLine()
buildComprehension(toComprehension(n, true))
b.removeIndentLevel().newLine()
b"\)"
addAlias
}
@@ -370,7 +386,9 @@ trait JdbcStatementBuilderComponent { driver: JdbcDriver =>
case OptionApply(ch) => expr(ch, skipParens)
case n => // try to build a sub-query
b"\("
b.addIndentLevel().newLine()
buildComprehension(toComprehension(n))
b.removeIndentLevel().newLine()
b"\)"
}

@@ -121,9 +121,9 @@ trait MySQLDriver extends JdbcDriver { driver =>
}

override protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = (fetch, offset) match {
case (Some(t), Some(d)) => b" limit $d,$t"
case (Some(t), None ) => b" limit $t"
case (None, Some(d)) => b" limit $d,18446744073709551615"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}limit $d,$t"
case (Some(t), None ) => b"${sqlIndentPrefix}limit $t"
case (None, Some(d)) => b"${sqlIndentPrefix}limit $d,18446744073709551615"
case _ =>
}

@@ -87,9 +87,9 @@ trait PostgresDriver extends JdbcDriver { driver =>
override protected val supportsEmptyJoinConditions = false

override protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = (fetch, offset) match {
case (Some(t), Some(d)) => b" limit $t offset $d"
case (Some(t), None ) => b" limit $t"
case (None, Some(d)) => b" offset $d"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}limit $t offset $d"
case (Some(t), None ) => b"${sqlIndentPrefix}limit $t"
case (None, Some(d)) => b"${sqlIndentPrefix}offset $d"
case _ =>
}

@@ -110,9 +110,9 @@ trait SQLiteDriver extends JdbcDriver { driver =>
}

override protected def buildFetchOffsetClause(fetch: Option[Node], offset: Option[Node]) = (fetch, offset) match {
case (Some(t), Some(d)) => b" limit $d,$t"
case (Some(t), None ) => b" limit $t"
case (None, Some(d)) => b" limit $d,-1"
case (Some(t), Some(d)) => b"${sqlIndentPrefix}limit $d,$t"
case (Some(t), None ) => b"${sqlIndentPrefix}limit $t"
case (None, Some(d)) => b"${sqlIndentPrefix}limit $d,-1"
case _ =>
}

@@ -3,7 +3,7 @@ package scala.slick.util
import java.sql.PreparedStatement
import scala.collection.mutable.ArrayBuffer

final class SQLBuilder extends SQLBuilder.Segment { self =>
final class SQLBuilder(val sqlIndent: Boolean = false) extends SQLBuilder.Segment { self =>
import SQLBuilder._

private val segments = new ArrayBuffer[Segment]
@@ -43,7 +43,7 @@ final class SQLBuilder extends SQLBuilder.Segment { self =>
def isEmpty = ss.sb.isEmpty

def createSlot = {
val s = new SQLBuilder
val s = new SQLBuilder(sqlIndent)
segments += s
currentStringSegment = null
s
@@ -58,6 +58,35 @@ final class SQLBuilder extends SQLBuilder.Segment { self =>
appendTo(sb, setters)
Result(sb.toString, new CombinedSetter(setters))
}

private var currentIndentLevel: Int = 0

def addIndentLevel(): SQLBuilder = {
if (sqlIndent) currentIndentLevel += 1
this
}

def removeIndentLevel(): SQLBuilder = {
if (sqlIndent) currentIndentLevel -= 1
this
}

def getIndentLevel: Int = currentIndentLevel

def newLine(): SQLBuilder = {
if (sqlIndent) {
this.+=("\n")
if (1 <= currentIndentLevel) 1.to(currentIndentLevel).foreach(_ => this.+=(" "))
}
this
}

def newLineStr: String = {
if ((sqlIndent) && (1 <= currentIndentLevel)) {
"\n" + 1.to(currentIndentLevel).map(_ => this.+=(" "))
}
else " "
}
}

object SQLBuilder {

0 comments on commit 7a58f7f

Please sign in to comment.
You can’t perform that action at this time.