Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally return more detailed exception messages when SQL statements fail #87

Merged
merged 1 commit into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions sqlest/src/main/scala/sqlest/executor/Database.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,58 @@ import scala.util.control.NonFatal
import sqlest.extractor.IndexedExtractor

object Database {
def withDataSource(dataSource: DataSource, builder: StatementBuilder): Database = new Database {
def withDataSource(
dataSource: DataSource,
builder: StatementBuilder
): Database = new Database {
def getConnection: Connection = dataSource.getConnection
val statementBuilder = builder
}

def withDataSource(dataSource: DataSource, builder: StatementBuilder, connectionDescription: Connection => String): Database = {
def withDataSource(
dataSource: DataSource,
builder: StatementBuilder,
verboseExceptionMessages: Boolean
): Database = new Database {
def getConnection: Connection = dataSource.getConnection
val statementBuilder = builder
override val verboseExceptions = verboseExceptionMessages
}

def withDataSource(
dataSource: DataSource,
builder: StatementBuilder,
connectionDescription: Connection => String
): Database = {
val inConnectionDescription = connectionDescription
new Database {
def getConnection: Connection = dataSource.getConnection
val statementBuilder = builder
override val connectionDescription = Some(inConnectionDescription)
}
}

def withDataSource(
dataSource: DataSource,
builder: StatementBuilder,
connectionDescription: Connection => String,
verboseExceptionMessages: Boolean
): Database = {
val inConnectionDescription = connectionDescription
new Database {
def getConnection: Connection = dataSource.getConnection
val statementBuilder = builder
override val verboseExceptions = verboseExceptionMessages
override val connectionDescription = Some(inConnectionDescription)
}
}
}

trait Database {
private[sqlest] def getConnection: Connection
private[sqlest] def statementBuilder: StatementBuilder
private[sqlest] def connectionDescription: Option[Connection => String] = None
private[sqlest] def verboseExceptions: Boolean = false

def withConnection[A](f: Connection => A): A =
Session(this).withConnection(f)
Expand Down Expand Up @@ -98,6 +131,12 @@ class Session(database: Database) extends Logging {
if (resultSet != null) resultSet.close
} catch { case e: SQLException => }
}
} catch {
case NonFatal(e) =>
if (!database.verboseExceptions)
throw e
else
throw new SqlestException(s"Exception running sql: ${logDetails(connection, sql, argumentLists)}", e)
} finally {
try {
if (preparedStatement != null) preparedStatement.close
Expand Down Expand Up @@ -267,6 +306,12 @@ case class Transaction(database: Database) extends Session(database) {
val endTime = new DateTime
logger.info(s"Ran sql in ${endTime.getMillis - startTime.getMillis}ms: ${logDetails(connection, sql, argumentLists)}")
result
} catch {
case NonFatal(e) =>
if (!database.verboseExceptions)
throw e
else
throw new SqlestException(s"Exception running sql: ${logDetails(connection, sql, argumentLists)}", e)
} finally {
try {
if (preparedStatement != null) preparedStatement.close
Expand All @@ -292,6 +337,12 @@ case class Transaction(database: Database) extends Session(database) {
val endTime = new DateTime
logger.info(s"Ran sql in ${endTime.getMillis - startTime.getMillis}ms: ${logDetails(connection, sql, argumentLists)}")
keys
} catch {
case NonFatal(e) =>
if (!database.verboseExceptions)
throw e
else
throw new SqlestException(s"Exception running sql: ${logDetails(connection, sql, argumentLists)}", e)
} finally {
try {
if (preparedStatement != null) preparedStatement.close
Expand Down
6 changes: 6 additions & 0 deletions sqlest/src/main/scala/sqlest/executor/SqlestException.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package sqlest.executor

case class SqlestException(
message: String,
cause: Throwable
) extends Exception(message, cause)
76 changes: 76 additions & 0 deletions sqlest/src/test/scala/sqlest/executor/ExecutorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,80 @@ class ExecutorSpec extends FlatSpec with Matchers {
insertStatement.execute
}
}

it should "return verbose exception messages when configured to do so" in {
val database = TestDatabase(testResultSet, Some(keyResultSet), shouldThrow = true, verboseExceptionMessages = true)

val selectException = intercept[SqlestException] {
database.withSession { implicit session =>
selectStatement.fetchAll
}
}

assert(selectException.message.startsWith("Exception running sql"))
selectException.cause shouldBe database.anException

val insertException = intercept[SqlestException] {
database.withTransaction { implicit transaction =>
insertStatement.execute
}
}

assert(insertException.message.startsWith("Exception running sql"))
insertException.cause shouldBe database.anException

val insertReturningKeysException = intercept[SqlestException] {
database.withTransaction { implicit transaction =>
insertStatement.executeReturningKeys[String]
}
}

assert(insertReturningKeysException.message.startsWith("Exception running sql"))
insertReturningKeysException.cause shouldBe database.anException

val updateException = intercept[SqlestException] {
database.withTransaction { implicit transaction =>
updateStatement.execute
}
}

assert(updateException.message.startsWith("Exception running sql"))
updateException.cause shouldBe database.anException
}

it should "return the underlying exception otherwise" in {
val database = TestDatabase(testResultSet, Some(keyResultSet), shouldThrow = true, verboseExceptionMessages = false)

val selectException = intercept[Exception] {
database.withSession { implicit session =>
selectStatement.fetchAll
}
}

selectException shouldBe database.anException

val insertException = intercept[Exception] {
database.withTransaction { implicit transaction =>
insertStatement.execute
}
}

insertException shouldBe database.anException

val insertReturningKeysException = intercept[Exception] {
database.withTransaction { implicit transaction =>
insertStatement.executeReturningKeys[String]
}
}

insertReturningKeysException shouldBe database.anException

val updateException = intercept[Exception] {
database.withTransaction { implicit transaction =>
updateStatement.execute
}
}

updateException shouldBe database.anException
}
}
31 changes: 17 additions & 14 deletions sqlest/src/test/scala/sqlest/executor/TestDatabase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ package sqlest.executor
import java.sql.ResultSet
import sqlest._

case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] = None) extends Database {
case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] = None, shouldThrow: Boolean = false, verboseExceptionMessages: Boolean = false) extends Database {
var preparedStatement: Option[AbstractPreparedStatement] = None
var lastConnection: Option[AbstractConnection] = None
override val verboseExceptions = verboseExceptionMessages

val anException = new Exception("Oh noes!")

def statementBuilder: StatementBuilder = sqlest.sql.H2StatementBuilder

Expand All @@ -40,14 +43,14 @@ case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] =
override def setAutoCommit(autoCommit: Boolean) = {}
override def createStatement() = new AbstractPreparedStatement {
val sql = null
override def execute(sql: String) = true
override def execute(sql: String) = if (shouldThrow) throw anException else true
}
override def prepareStatement(inSql: String, columnIndices: Array[Int]) = {
val statement = new AbstractPreparedStatement {
val sql = inSql
override def executeQuery() = resultSet
override def executeUpdate() = 1
override def executeBatch() = Array()
override def executeQuery() = if (shouldThrow) throw anException else resultSet
override def executeUpdate() = if (shouldThrow) throw anException else 1
override def executeBatch() = if (shouldThrow) throw anException else Array()
override def getGeneratedKeys(): java.sql.ResultSet = {
keyResultSet.getOrElse(null)
}
Expand All @@ -58,9 +61,9 @@ case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] =
override def prepareStatement(inSql: String, columnNames: Array[String]) = {
val statement = new AbstractPreparedStatement {
val sql = inSql
override def executeQuery() = resultSet
override def executeUpdate() = 1
override def executeBatch() = Array()
override def executeQuery() = if (shouldThrow) throw anException else resultSet
override def executeUpdate() = if (shouldThrow) throw anException else 1
override def executeBatch() = if (shouldThrow) throw anException else Array()
override def getGeneratedKeys(): java.sql.ResultSet = {
keyResultSet.getOrElse(null)
}
Expand All @@ -71,9 +74,9 @@ case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] =
override def prepareStatement(inSql: String, returnGeneratedKeys: Int) = {
val statement = new AbstractPreparedStatement {
val sql = inSql
override def executeQuery() = resultSet
override def executeUpdate() = 1
override def executeBatch() = Array()
override def executeQuery() = if (shouldThrow) throw anException else resultSet
override def executeUpdate() = if (shouldThrow) throw anException else 1
override def executeBatch() = if (shouldThrow) throw anException else Array()
override def getGeneratedKeys(): java.sql.ResultSet = {
if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS)
keyResultSet.getOrElse(null)
Expand All @@ -86,9 +89,9 @@ case class TestDatabase(resultSet: ResultSet, keyResultSet: Option[ResultSet] =
override def prepareStatement(inSql: String) = {
val statement = new AbstractPreparedStatement {
val sql = inSql
override def executeQuery() = resultSet
override def executeUpdate() = 1
override def executeBatch() = Array()
override def executeQuery() = if (shouldThrow) throw anException else resultSet
override def executeUpdate() = if (shouldThrow) throw anException else 1
override def executeBatch() = if (shouldThrow) throw anException else Array()
override def getGeneratedKeys(): java.sql.ResultSet = null
}
preparedStatement = Some(statement)
Expand Down