diff --git a/build.sbt b/build.sbt index 9936eb0..c5fd9c1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,11 +1,13 @@ name := "prequel" -version := "0.3.9" +version := "1.0-SNAPSHOT" -organization := "net.noerd" +organization := "com.gu" scalaVersion := "2.10.0" +crossScalaVersions := Seq("2.9.2", "2.10.0") + // Runtime Dependencies libraryDependencies ++= Seq( "commons-pool" % "commons-pool" % "1.5.5", diff --git a/src/main/scala/net/noerd/prequel/ColumnTypes.scala b/src/main/scala/net/noerd/prequel/ColumnTypes.scala index adbee4b..823cbcd 100644 --- a/src/main/scala/net/noerd/prequel/ColumnTypes.scala +++ b/src/main/scala/net/noerd/prequel/ColumnTypes.scala @@ -2,9 +2,11 @@ package net.noerd.prequel import java.util.Date +import java.sql.{ Date => SqlDate } +import java.sql.Timestamp + import org.joda.time.DateTime import org.joda.time.Duration -import org.joda.time.format.DateTimeFormatter // // String @@ -75,6 +77,7 @@ class DateTimeColumnType( row: ResultSetRow ) extends ColumnType[ DateTime ] { object DateTimeColumnType extends ColumnTypeFactory[ DateTime ] { def apply( row: ResultSetRow ) = new DateTimeColumnType( row ) } + class DateColumnType( row: ResultSetRow ) extends ColumnType[ Date ] { override def nextValueOption: Option[ Date ] = row.nextDate } @@ -82,6 +85,27 @@ object DateColumnType extends ColumnTypeFactory[ Date ] { def apply( row: ResultSetRow ) = new DateColumnType( row ) } +// +// Timestamp +// +class TimestampColumnType( row: ResultSetRow ) extends ColumnType[ Timestamp ] { + override def nextValueOption: Option[ Timestamp ] = row.nextTimestamp +} +object TimestampColumnType extends ColumnTypeFactory[ Timestamp ] { + def apply( row: ResultSetRow ) = new TimestampColumnType( row ) +} + +// +// java.sql.Date +// +class SqlDateColumnType( row: ResultSetRow ) extends ColumnType[ SqlDate ] { + override def nextValueOption: Option[ SqlDate ] = row.nextSqlDate +} +object SqlDateColumnType extends ColumnTypeFactory[ SqlDate ] { + def apply( row: ResultSetRow ) = new SqlDateColumnType( row ) +} + + // // Duration // diff --git a/src/main/scala/net/noerd/prequel/Database.scala b/src/main/scala/net/noerd/prequel/Database.scala index 3e27c4f..8ea2401 100644 --- a/src/main/scala/net/noerd/prequel/Database.scala +++ b/src/main/scala/net/noerd/prequel/Database.scala @@ -1,6 +1,6 @@ package net.noerd.prequel -import java.sql.{ SQLException, Connection } +import java.sql.Connection import java.util.Properties import scala.collection.mutable.{ Map => MMap } diff --git a/src/main/scala/net/noerd/prequel/Formattables.scala b/src/main/scala/net/noerd/prequel/Formattables.scala index 16968f2..f00c73a 100644 --- a/src/main/scala/net/noerd/prequel/Formattables.scala +++ b/src/main/scala/net/noerd/prequel/Formattables.scala @@ -4,7 +4,7 @@ import java.util.{Locale, Date} import org.joda.time.DateTime import org.joda.time.Duration -import org.joda.time.format.DateTimeFormatter +import java.sql.{Timestamp, Date => SqlDate} /** * Wrap your optional value in NullComparable to compare with null if None. @@ -154,6 +154,46 @@ object DateTimeFormattable{ } } + +// +// Timestamp +// +class TimestampFormattable( val value: Timestamp ) + extends Formattable { + override def escaped( formatter: SQLFormatter ): String = { + formatter.toSQLString( formatter.timeStampFormatter.print( value.getTime ) ) + } + override def addTo( statement: ReusableStatement ): Unit = { + statement.addTimestamp( value ) + } +} + +object TimestampFormattable { + def apply( value: Timestamp ) = { + new TimestampFormattable( value ) + } +} + + + +class SqlDateFormattable( val value: SqlDate ) + extends Formattable { + override def escaped( formatter: SQLFormatter ): String = { + formatter.toSQLString( value.toString ) + } + override def addTo( statement: ReusableStatement ): Unit = { + statement.addSqlDate( value ) + } +} + +object SqlDateFormattable { + def apply( value: SqlDate ) = { + new SqlDateFormattable( value ) + } +} + + + // // Duration // diff --git a/src/main/scala/net/noerd/prequel/ResultSetRow.scala b/src/main/scala/net/noerd/prequel/ResultSetRow.scala index 7d34e70..63c098a 100644 --- a/src/main/scala/net/noerd/prequel/ResultSetRow.scala +++ b/src/main/scala/net/noerd/prequel/ResultSetRow.scala @@ -2,13 +2,10 @@ package net.noerd.prequel import java.util.Date -import java.sql.ResultSet -import java.sql.ResultSetMetaData +import java.sql.{Timestamp, ResultSet, Date => SqlDate} import scala.collection.mutable.ArrayBuffer -import org.joda.time.DateTime -import org.joda.time.Duration /** * Wraps a ResultSet in a row context. The ResultSetRow gives access @@ -27,9 +24,11 @@ class ResultSetRow( val rs: ResultSet ) { def nextDouble: Option[ Double ] = nextValueOption( rs.getDouble ) def nextString: Option[ String ] = nextValueOption( rs.getString ) def nextDate: Option[ Date ] = nextValueOption( rs.getTimestamp ) + def nextSqlDate: Option[ SqlDate ] = nextValueOption( rs.getDate ) + def nextTimestamp: Option[ Timestamp ] = nextValueOption( rs.getTimestamp ) def nextObject: Option[ AnyRef ] = nextValueOption( rs.getObject ) def nextBinary: Option[ Array[Byte] ] = nextValueOption( rs.getBytes ) - + def columnNames: Seq[ String ]= { val columnNames = ArrayBuffer.empty[ String ] val metaData = rs.getMetaData @@ -84,6 +83,8 @@ object ResultSetRowImplicits { implicit def row2String( row: ResultSetRow ) = StringColumnType( row ).nextValue implicit def row2Date( row: ResultSetRow ) = DateColumnType( row ).nextValue implicit def row2DateTime( row: ResultSetRow ) = DateTimeColumnType( row ).nextValue + implicit def row2Timestamp( row: ResultSetRow ) = TimestampColumnType( row ).nextValue + implicit def row2SqlDate( row: ResultSetRow ) = SqlDateColumnType( row ).nextValue implicit def row2Duration( row: ResultSetRow ) = DurationColumnType( row ).nextValue implicit def row2Binary( row: ResultSetRow ) = BinaryColumnType( row ).nextValue @@ -95,6 +96,8 @@ object ResultSetRowImplicits { implicit def row2StringOption( row: ResultSetRow ) = StringColumnType( row ).nextValueOption implicit def row2DateOption( row: ResultSetRow ) = DateColumnType( row ).nextValueOption implicit def row2DateTimeOption( row: ResultSetRow ) = DateTimeColumnType( row ).nextValueOption + implicit def row2TimestampOption( row: ResultSetRow ) = TimestampColumnType( row ).nextValueOption + implicit def row2SqlDateOption( row: ResultSetRow ) = SqlDateColumnType( row ).nextValueOption implicit def row2DurationOption( row: ResultSetRow ) = DurationColumnType( row ).nextValueOption implicit def row2BinaryOption( row: ResultSetRow ) = BinaryColumnType( row ).nextValueOption -} \ No newline at end of file +} diff --git a/src/main/scala/net/noerd/prequel/ReusableStatement.scala b/src/main/scala/net/noerd/prequel/ReusableStatement.scala index f6a669b..ca13f98 100644 --- a/src/main/scala/net/noerd/prequel/ReusableStatement.scala +++ b/src/main/scala/net/noerd/prequel/ReusableStatement.scala @@ -1,9 +1,7 @@ package net.noerd.prequel -import java.sql.Connection import java.sql.PreparedStatement -import java.sql.Timestamp -import java.sql.Types +import java.sql.{Timestamp, Types, Date => SqlDate} import org.joda.time.DateTime @@ -62,16 +60,27 @@ private class ReusableStatement( val wrapped: PreparedStatement, formatter: SQLF def addString( value: String ) = addValue(wrapped.setString( parameterIndex, formatter.escapeString( value ) ) ) - + /** * Add a Date to the current parameter index. This is done by setTimestamp which * looses the Timezone information of the DateTime */ def addDateTime( value: DateTime ): Unit = - addValue( wrapped.setTimestamp( parameterIndex, new Timestamp( value.getMillis ) ) - ) + addValue( wrapped.setTimestamp( parameterIndex, new Timestamp( value.getMillis ) ) ) - /** + /** + * Add a Timestamp to the current parameter index. + */ + def addTimestamp( value: Timestamp ): Unit = + addValue( wrapped.setTimestamp( parameterIndex, value ) ) + + /** + * Add a java.sql.Date to the current parameter index. + */ + def addSqlDate( value: SqlDate ): Unit = + addValue( wrapped.setDate( parameterIndex, value) ) + + /** * Add Binary (array of bytes) to the current parameter index */ def addBinary( value: Array[Byte] ): Unit = addValue( wrapped.setBytes( parameterIndex, value ) ) diff --git a/src/main/scala/net/noerd/prequel/SQLFormatter.scala b/src/main/scala/net/noerd/prequel/SQLFormatter.scala index e86a5d2..a4613c0 100644 --- a/src/main/scala/net/noerd/prequel/SQLFormatter.scala +++ b/src/main/scala/net/noerd/prequel/SQLFormatter.scala @@ -9,6 +9,7 @@ import org.joda.time.Duration import org.joda.time.format.DateTimeFormat import org.joda.time.format.DateTimeFormatter import org.joda.time.format.ISODateTimeFormat +import java.sql.{Timestamp, Date => SqlDate} /** * Currently a private class responsible for formatting SQL used in @@ -84,6 +85,8 @@ object SQLFormatterImplicits { implicit def float2Formattable( wrapped: Float ) = FloatFormattable( wrapped ) implicit def double2Formattable( wrapped: Double ) = DoubleFormattable( wrapped ) implicit def dateTime2Formattable( wrapped: DateTime ) = DateTimeFormattable( wrapped ) + implicit def sqlDate2Formattable( wrapped: SqlDate ) = SqlDateFormattable( wrapped ) + implicit def timeStamp2Formattable( wrapped: Timestamp ) = TimestampFormattable( wrapped ) implicit def date2Formattable( wrapped: Date ) = DateTimeFormattable( wrapped ) implicit def duration2Formattable( wrapped: Duration ) = new DurationFormattable( wrapped ) implicit def binary2Formattable( wrapped: Array[Byte] ) = new BinaryFormattable( wrapped ) diff --git a/src/main/scala/net/noerd/prequel/Transaction.scala b/src/main/scala/net/noerd/prequel/Transaction.scala index d91d29f..f5cbec7 100644 --- a/src/main/scala/net/noerd/prequel/Transaction.scala +++ b/src/main/scala/net/noerd/prequel/Transaction.scala @@ -1,8 +1,6 @@ package net.noerd.prequel import java.sql.Connection -import java.sql.Statement -import java.sql.ResultSet import scala.collection.mutable.ArrayBuffer diff --git a/src/test/scala/net/noerd/prequel/ColumnTypesSpec.scala b/src/test/scala/net/noerd/prequel/ColumnTypesSpec.scala index caeda2c..dd535e0 100644 --- a/src/test/scala/net/noerd/prequel/ColumnTypesSpec.scala +++ b/src/test/scala/net/noerd/prequel/ColumnTypesSpec.scala @@ -5,9 +5,7 @@ import org.joda.time.Duration import org.scalatest.FunSpec import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterEach -import net.noerd.prequel.SQLFormatterImplicits._ trait ColumnTypeSpec[ T ] extends FunSpec with ShouldMatchers { diff --git a/src/test/scala/net/noerd/prequel/ConnectionPoolsSpec.scala b/src/test/scala/net/noerd/prequel/ConnectionPoolsSpec.scala index b741320..3a4b16e 100644 --- a/src/test/scala/net/noerd/prequel/ConnectionPoolsSpec.scala +++ b/src/test/scala/net/noerd/prequel/ConnectionPoolsSpec.scala @@ -1,6 +1,5 @@ package net.noerd.prequel -import java.sql.SQLException import org.scalatest.FunSpec import org.scalatest.matchers.ShouldMatchers diff --git a/src/test/scala/net/noerd/prequel/FormattablesSpec.scala b/src/test/scala/net/noerd/prequel/FormattablesSpec.scala index 4c8d490..d97ec55 100644 --- a/src/test/scala/net/noerd/prequel/FormattablesSpec.scala +++ b/src/test/scala/net/noerd/prequel/FormattablesSpec.scala @@ -4,7 +4,7 @@ import org.joda.time.Duration import org.scalatest.FunSpec import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterEach +import java.sql.{Timestamp, Date => SqlDate} import java.util.Locale class FormattablesSpec extends FunSpec with ShouldMatchers { @@ -33,6 +33,14 @@ class FormattablesSpec extends FunSpec with ShouldMatchers { DateTimeFormattable( formatter.timeStampFormatter.parseDateTime( "2010-03-13 13:00:00.0000" ) ), "'2010-03-13 13:00:00.0000'" ), + ( "TimeStampFormattable should escape 2010-03-13 13:00:00.0040", + TimestampFormattable( Timestamp.valueOf( "2010-03-13 13:00:00.0040" ) ), + "'2010-03-13 13:00:00.0040'" + ), + ( "TimeStampFormattable should escape 2010-03-13", + SqlDateFormattable( SqlDate.valueOf( "2010-03-13" ) ), + "'2010-03-13'" + ), ( "DurationFormattable should escape an Duration object", DurationFormattable( Duration.standardHours( 2 ) ), "7200000" ), @@ -85,4 +93,4 @@ class FormattablesSpec extends FunSpec with ShouldMatchers { Locale.setDefault(oldDefaultLocale) } } -} \ No newline at end of file +} diff --git a/src/test/scala/net/noerd/prequel/InTransactionSpec.scala b/src/test/scala/net/noerd/prequel/InTransactionSpec.scala index d353e2f..4faddbf 100644 --- a/src/test/scala/net/noerd/prequel/InTransactionSpec.scala +++ b/src/test/scala/net/noerd/prequel/InTransactionSpec.scala @@ -7,7 +7,6 @@ import org.scalatest.matchers.ShouldMatchers import org.scalatest.BeforeAndAfterEach import net.noerd.prequel.SQLFormatterImplicits._ -import net.noerd.prequel.ResultSetRowImplicits._ class InTransactionSpec extends FunSpec with ShouldMatchers with BeforeAndAfterEach { diff --git a/src/test/scala/net/noerd/prequel/ResultSetRowSpec.scala b/src/test/scala/net/noerd/prequel/ResultSetRowSpec.scala index ac8999b..fed3453 100644 --- a/src/test/scala/net/noerd/prequel/ResultSetRowSpec.scala +++ b/src/test/scala/net/noerd/prequel/ResultSetRowSpec.scala @@ -1,18 +1,13 @@ package net.noerd.prequel import java.util.Date -import java.sql.SQLException +import java.sql.{Timestamp, Date => SqlDate} import org.scalatest.FunSpec import org.scalatest.matchers.ShouldMatchers import org.scalatest.BeforeAndAfterEach -import org.joda.time.DateTime -import org.joda.time.Duration -import org.joda.time.format.DateTimeFormat -import org.joda.time.format.DateTimeFormatter import net.noerd.prequel.SQLFormatterImplicits._ -import net.noerd.prequel.ResultSetRowImplicits._ class ResultSetRowSpec extends FunSpec with ShouldMatchers with BeforeAndAfterEach { @@ -87,7 +82,30 @@ class ResultSetRowSpec extends FunSpec with ShouldMatchers with BeforeAndAfterEa } } } - it( "should return a Float" ) { database.transaction { tx => + it( "should return a Timestamp" ) { database.transaction { tx => + val value1 = Some( Timestamp.valueOf( "2014-03-26 00:00:00" ) ) + tx.execute( "create table timestamp_table(c1 timestamp, c2 timestamp)" ) + tx.execute( "insert into timestamp_table values(?, null)", value1.get ) + tx.select( "select c1, c2 from timestamp_table" ) { row => + row.nextTimestamp.get.getTime should equal (value1.get.getTime) + row.nextTimestamp should equal ( None ) + } + } } + + it( "should return a SqlDate" ) { database.transaction { tx => + val now = ( new Date ).getTime + val value1 = Some( new SqlDate( now) ) + + tx.execute( "create table sqldate_table(c1 date, c2 date)" ) + tx.execute( "insert into sqldate_table values(?, null)", value1.get ) + tx.select( "select c1, c2 from sqldate_table" ) { row => + val retrievedDate = row.nextSqlDate.get + retrievedDate.toString should equal (value1.get.toString) + row.nextSqlDate should equal ( None ) + } + } } + + it( "should return a Float" ) { database.transaction { tx => val value1 = Some(1.5f) val value2 = None tx.execute( "create table float_table(c1 real, c2 real)" ) diff --git a/src/test/scala/net/noerd/prequel/SQLFormatterSpec.scala b/src/test/scala/net/noerd/prequel/SQLFormatterSpec.scala index 73fc6d2..acf8864 100644 --- a/src/test/scala/net/noerd/prequel/SQLFormatterSpec.scala +++ b/src/test/scala/net/noerd/prequel/SQLFormatterSpec.scala @@ -7,7 +7,6 @@ import org.scalatest.matchers.ShouldMatchers import net.noerd.prequel.SQLFormatter.DefaultSQLFormatter import net.noerd.prequel.SQLFormatterImplicits._ -import net.noerd.prequel.ResultSetRowImplicits._ class SQLFormatterSpec extends FunSpec with ShouldMatchers {