Skip to content

Commit

Permalink
[jdbc-scala] Regex extractor for executed statement, usable with rich…
Browse files Browse the repository at this point in the history
… pattern:

```scala
e/* : QueryExecution */ match {
  case ~(ExecutedStatement("^SELECT"), (sql, parameters)) => // ...
}
```
  • Loading branch information
cchantep committed Mar 1, 2014
1 parent 7bc9b54 commit 65a7e33
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 106 deletions.
38 changes: 3 additions & 35 deletions jdbc-scala/src/main/scala/Acolyte.scala
Expand Up @@ -8,7 +8,6 @@ import java.sql.{ Connection ⇒ SqlConnection, Statement, SQLWarning }
import scala.language.implicitConversions import scala.language.implicitConversions
import scala.collection.JavaConversions import scala.collection.JavaConversions


import acolyte.ParameterMetaData.ParameterDef
import acolyte.StatementHandler.Parameter import acolyte.StatementHandler.Parameter
import acolyte.AbstractCompositeHandler.{ QueryHandler, UpdateHandler } import acolyte.AbstractCompositeHandler.{ QueryHandler, UpdateHandler }
import acolyte.RowList.{ Column Col } import acolyte.RowList.{ Column Col }
Expand All @@ -22,8 +21,8 @@ import acolyte.RowList.{ Column ⇒ Col }
* *
* connection { * connection {
* handleStatement.withQueryDetection("..."). * handleStatement.withQueryDetection("...").
* withQueryHandler({ e: Execution => ... }). * withQueryHandler({ e: QueryExecution => ... }).
* withUpdateHandler({ e: Execution => ... }) * withUpdateHandler({ e: UpdateExecution => ... })
* } * }
* }}} * }}}
*/ */
Expand Down Expand Up @@ -129,36 +128,6 @@ object Implicits extends ScalaRowLists with CompositeHandlerImplicits {


} }


sealed trait Execution

case class QueryExecution(
sql: String,
parameters: List[ExecutedParameter]) extends Execution

case class UpdateExecution(
sql: String,
parameters: List[ExecutedParameter]) extends Execution

trait ExecutedParameter {
def value: Any
}

case class DefinedParameter(
value: Any,
definition: ParameterDef) extends ExecutedParameter {

override lazy val toString = s"Param($value, ${definition.sqlTypeName})"
}

object ParameterVal {
def apply(v: Any): ExecutedParameter = new ExecutedParameter {
val value = v
override lazy val toString = s"Param($value)"
}

def unapply(p: ExecutedParameter): Option[Any] = Some(p.value)
}

final class ScalaCompositeHandler(qd: Array[Pattern], qh: QueryHandler, uh: UpdateHandler) extends AbstractCompositeHandler[ScalaCompositeHandler](qd, qh, uh) { final class ScalaCompositeHandler(qd: Array[Pattern], qh: QueryHandler, uh: UpdateHandler) extends AbstractCompositeHandler[ScalaCompositeHandler](qd, qh, uh) {


/** /**
Expand Down Expand Up @@ -247,8 +216,7 @@ trait CompositeHandlerImplicits { srl: ScalaRowLists ⇒
* ScalaCompositeHandler.empty withUpdateHandler { exec ⇒ 1/*count*/ } * ScalaCompositeHandler.empty withUpdateHandler { exec ⇒ 1/*count*/ }
* }}} * }}}
*/ */
implicit def IntUpdateResult(updateCount: Int): UpdateResult = implicit def IntUpdateResult(updateCount: Int) = new UpdateResult(updateCount)
new UpdateResult(updateCount)


/** /**
* Allows to directly use row list as query result. * Allows to directly use row list as query result.
Expand Down
53 changes: 53 additions & 0 deletions jdbc-scala/src/main/scala/Execution.scala
@@ -0,0 +1,53 @@
package acolyte

import acolyte.ParameterMetaData.ParameterDef

/** Execution information */
sealed trait Execution {
/** Executed SQL */
def sql: String

/** Execution parameters */
def parameters: List[ExecutedParameter]
}

case class QueryExecution(
sql: String, parameters: List[ExecutedParameter] = Nil) extends Execution

case class UpdateExecution(
sql: String, parameters: List[ExecutedParameter] = Nil) extends Execution

/** Statement extractor */
case class ExecutedStatement(
/** Statement pattern */
val p: String) {

val re = p.r

def unapply(x: Execution): Option[(String, List[ExecutedParameter])] =
re.findFirstIn(x.sql).map(_ (x.sql -> x.parameters))
}

sealed trait ExecutedParameter { def value: Any }

/** Executed parameter companion */
object ExecutedParameter {
def apply(v: Any): ExecutedParameter =
new ExecutedParameter { val value = v }

def unapply(p: ExecutedParameter): Option[Any] = Some(p.value)
}

/** Parameter along with its definition. */
case class DefinedParameter(
value: Any, definition: ParameterDef) extends ExecutedParameter {

override lazy val toString = s"Param($value, ${definition.sqlTypeName})"
}

/** Value extractor for execution parameter. */
@deprecated(
message = "Use [[acolyte.ExecutedParameter]] extractor", since = "1.0.15")
object ParameterVal {
def unapply(p: ExecutedParameter): Option[Any] = ExecutedParameter.unapply(p)
}
81 changes: 81 additions & 0 deletions jdbc-scala/src/test/scala/acolyte/ExecutionSpec.scala
@@ -0,0 +1,81 @@
package acolyte

import acolyte.{ ExecutedParameter XP }

object ExecutionSpec extends org.specs2.mutable.Specification {
"Execution" title

"Query execution" >> {
val (q1, q2) = (
QueryExecution("SELECT * FROM Test WHERE id = ?", XP("x") :: Nil),
QueryExecution("EXEC reindex"))

"Query #1" should {
"match case class pattern" in {
q1 aka "q1" must beLike {
case QueryExecution("SELECT * FROM Test WHERE id = ?",
XP("x") :: Nil) ok
}
}

"not match case class pattern" in {
q1 aka "q1" must not(beLike {
case QueryExecution("EXEC reindex", Nil) ok
})
}

"match statement extractor #1" in {
q1 aka "q1" must beLike {
case ~(ExecutedStatement("FROM Test"), (sql, XP("x") :: Nil))
sql aka "matching SQL" must_== "SELECT * FROM Test WHERE id = ?"
}
}

"match statement extractor #2" in {
q1 aka "q1" must beLike {
case ~(ExecutedStatement("^SELECT"), (sql, _)) ok
}
}

"not match statement extractor" in {
q1 aka "q1" must not(beLike {
case ~(ExecutedStatement(" reindex$"), (_, XP("x") :: Nil)) ok
})
}
}

"Query #2" should {
"match case class pattern" in {
q2 aka "q2" must beLike {
case QueryExecution("EXEC reindex", Nil) ok
}
}

"not match case class pattern" in {
q2 aka "q2" must not(beLike {
case QueryExecution("SELECT * FROM Test WHERE id = ?",
XP("x") :: Nil) ok
})
}

"match statement extractor #1" in {
q2 aka "q2" must beLike {
case ~(ExecutedStatement("EXEC"), (sql, Nil))
sql aka "matching SQL" must_== "EXEC reindex"
}
}

"match statement extractor #2" in {
q2 aka "q2" must beLike {
case ~(ExecutedStatement(" reindex$"), (sql, _)) ok
}
}

"not match statement extractor" in {
q2 aka "q2" must not(beLike {
case ~(ExecutedStatement("^SELECT"), (_, Nil)) ok
})
}
}
}
}
15 changes: 7 additions & 8 deletions jdbc-scala/src/test/scala/acolyte/ScalaUseCases.scala
Expand Up @@ -28,13 +28,12 @@ object ScalaUseCases {
withQueryDetection( withQueryDetection(
"^SELECT ", // regex test from beginning "^SELECT ", // regex test from beginning
"EXEC that_proc"). // second detection regex "EXEC that_proc"). // second detection regex
withUpdateHandler { e: UpdateExecution withUpdateHandler {
if (e.sql.startsWith("DELETE ")) { _ match {
// Process deletion ... case ~(ExecutedStatement("^DELETE "), (sql, ps))
/* deleted = */ 2 /* Process deletion ... deleted = */ 2
} else {
// ... Process ... case _ /* ... Process ... count = */ 1
/* count = */ 1
} }
} withQueryHandler { e: QueryExecution } withQueryHandler { e: QueryExecution
if (e.sql.startsWith("SELECT ")) { if (e.sql.startsWith("SELECT ")) {
Expand Down Expand Up @@ -95,7 +94,7 @@ object ScalaUseCases {
val handler: ScalaCompositeHandler = handleStatement. val handler: ScalaCompositeHandler = handleStatement.
withQueryDetection("^SELECT ") withQueryHandler { withQueryDetection("^SELECT ") withQueryHandler {
_ match { // QueryExecution _ match { // QueryExecution
case QueryExecution(s, ParameterVal("id") :: Nil) case QueryExecution(s, ExecutedParameter("id") :: Nil)
(stringList :+ "useCase_3a").asResult (stringList :+ "useCase_3a").asResult


case QueryExecution(s, case QueryExecution(s,
Expand Down
7 changes: 6 additions & 1 deletion project/Acolyte.scala
Expand Up @@ -2,14 +2,15 @@ import sbt._
import Keys._ import Keys._


// Multi-module project build // Multi-module project build
object Acolyte extends Build object Acolyte extends Build with Dependencies
with JdbcDriver with ScalacPlugin with JdbcScala with Studio { with JdbcDriver with ScalacPlugin with JdbcScala with Studio {


lazy val root = Project(id = "acolyte", base = file(".")). lazy val root = Project(id = "acolyte", base = file(".")).
aggregate(jdbcDriver, scalacPlugin, jdbcScala, studio). aggregate(jdbcDriver, scalacPlugin, jdbcScala, studio).
settings( settings(
organization in ThisBuild := "org.eu.acolyte", organization in ThisBuild := "org.eu.acolyte",
version in ThisBuild := "1.0.15", version in ThisBuild := "1.0.15",
javaOptions in ThisBuild ++= Seq("-source", "1.6", "-target", "1.6"),
scalaVersion in ThisBuild := "2.10.3", scalaVersion in ThisBuild := "2.10.3",
publishTo in ThisBuild := Some(Resolver.file("file", publishTo in ThisBuild := Some(Resolver.file("file",
new File(Path.userHome.absolutePath+"/.m2/repository"))), new File(Path.userHome.absolutePath+"/.m2/repository"))),
Expand Down Expand Up @@ -46,3 +47,7 @@ object Acolyte extends Build
</developer> </developer>
</developers>)) </developers>))
} }

trait Dependencies {
val specs2Test = "org.specs2" %% "specs2" % "2.3.8" % "test"
}
5 changes: 2 additions & 3 deletions project/JdbcDriver.scala
@@ -1,7 +1,7 @@
import sbt._ import sbt._
import Keys._ import Keys._


trait JdbcDriver { trait JdbcDriver { deps: Dependencies
lazy val jdbcDriver = lazy val jdbcDriver =
Project(id = "jdbc-driver", base = file("jdbc-driver")).settings( Project(id = "jdbc-driver", base = file("jdbc-driver")).settings(
name := "jdbc-driver", name := "jdbc-driver",
Expand All @@ -10,8 +10,7 @@ trait JdbcDriver {
scalacOptions += "-feature", scalacOptions += "-feature",
resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/", resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.apache.commons" % "commons-lang3" % "3.1", "org.apache.commons" % "commons-lang3" % "3.1", specs2Test),
"org.specs2" %% "specs2" % "2.3.2" % "test"),
crossPaths := false, crossPaths := false,
sourceGenerators in Compile <+= (baseDirectory in Compile) zip (sourceManaged in Compile) map (dirs { sourceGenerators in Compile <+= (baseDirectory in Compile) zip (sourceManaged in Compile) map (dirs {
val (base, managed) = dirs val (base, managed) = dirs
Expand Down
67 changes: 21 additions & 46 deletions project/JdbcScala.scala
@@ -1,56 +1,31 @@
import sbt._ import sbt._
import Keys._ import Keys._


trait JdbcScala { trait JdbcScala { deps: Dependencies
// Dependencies
def jdbcDriver: Project def jdbcDriver: Project
def scalacPlugin: Project


lazy val jdbcScala = lazy val jdbcScala =
Project(id = "jdbc-scala", base = file("jdbc-scala")).settings( Project(id = "jdbc-scala", base = file("jdbc-scala")).settings(
name := "jdbc-scala", name := "jdbc-scala",
javaOptions ++= Seq("-source", "1.6", "-target", "1.6"), javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"), scalacOptions in Test <++= (version in ThisBuild).
scalacOptions ++= Seq("-feature", "-deprecation"), zip(baseDirectory in (scalacPlugin, Compile)).
resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/", zip(name in (scalacPlugin, Compile)) map { d =>
libraryDependencies ++= Seq( val ((v, b), n) = d
"org.eu.acolyte" % "jdbc-driver" % "1.0.14", val j = b / "target" / "scala-2.10" / "%s_2.10-%s.jar".format(n, v)
"org.scalaz" % "scalaz-core_2.10" % "7.0.5",
"org.specs2" %% "specs2" % "2.3.2" % "test"), Seq("-feature", "-deprecation",
sourceGenerators in Compile <+= (baseDirectory in Compile) zip (sourceManaged in Compile) map (dirs { "-Xplugin:%s".format(j.getAbsolutePath))
val (base, managed) = dirs },
generateRowClasses(base, managed) resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
}), libraryDependencies ++= Seq(
pomExtra := ( "org.eu.acolyte" % "jdbc-driver" % "1.0.15", specs2Test),
<url>https://github.com/cchantep/acolyte/</url> sourceGenerators in Compile <+= (baseDirectory in Compile) zip (sourceManaged in Compile) map (dirs {
<licenses> val (base, managed) = dirs
<license> generateRowClasses(base, managed)
<name>GNU Lesser General Public License, Version 2.1</name> })).dependsOn(scalacPlugin, jdbcDriver)
<url>
https://raw.github.com/cchantep/acolyte/master/LICENSE.txt
</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:cchantep/acolyte.git</connection>
<developerConnection>
scm:git:git@github.com:cchantep/acolyte.git
</developerConnection>
<url>git@github.com:cchantep/acolyte.git</url>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/cchantep/acolyte/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/cchantep/acolyte</url>
</ciManagement>
<developers>
<developer>
<id>cchantep</id>
<name>Cedric Chantepie</name>
</developer>
</developers>)).dependsOn(jdbcDriver)


// Source generator // Source generator
private def generateRowClasses(base: File, managed: File): Seq[File] = { private def generateRowClasses(base: File, managed: File): Seq[File] = {
Expand Down
8 changes: 3 additions & 5 deletions project/ScalacPlugin.scala
@@ -1,19 +1,17 @@
import sbt._ import sbt._
import Keys._ import Keys._


trait ScalacPlugin { trait ScalacPlugin { deps: Dependencies
lazy val scalacPlugin = lazy val scalacPlugin =
Project(id = "scalac-plugin", base = file("scalac-plugin")).settings( Project(id = "scalac-plugin", base = file("scalac-plugin")).settings(
name := "scalac-plugin", name := "scalac-plugin",
javaOptions ++= Seq("-source", "1.6", "-target", "1.6"),
javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"), javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
scalacOptions ++= Seq("-feature", "-deprecation"),
resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/", resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.scala-lang" % "scala-compiler" % "2.10.3", "org.scala-lang" % "scala-compiler" % "2.10.3", specs2Test),
"org.specs2" %% "specs2" % "2.3.2" % "test"),
compile in Test <<= (compile in Test).dependsOn( compile in Test <<= (compile in Test).dependsOn(
packageBin in Compile/* make sure plugin.jar is available */), packageBin in Compile/* make sure plugin.jar is available */),
scalacOptions in Compile ++= Seq("-feature", "-deprecation"),
scalacOptions in Test <++= (version in ThisBuild). scalacOptions in Test <++= (version in ThisBuild).
zip(baseDirectory in Compile). zip(baseDirectory in Compile).
zip(name in Compile) map { d => zip(name in Compile) map { d =>
Expand Down

0 comments on commit 65a7e33

Please sign in to comment.