diff --git a/README.textile b/README.textile index 0569742..dd0368a 100644 --- a/README.textile +++ b/README.textile @@ -3,9 +3,9 @@ h3. Cascal - Cassandra Simplified |_. Primary Author|Chris Shorrock| |_. Home Page|"http://wiki.github.com/shorrockin/cascal/":http://wiki.github.com/shorrockin/cascal/| |_. API Doc|"http://shorrockin.com/cascal/scaladocs/":http://shorrockin.com/cascal/scaladocs/| -|_. Stable Version|1.2| +|_. Stable Version|1.2 (Scala 2.7.7)| |_. Snapshot Version|1.3-SNAPSHOT| -|_. Scala Version|2.7.7| +|_. Scala Version|2.8.0| |_. Cassandra Version|0.6.1| @@ -30,7 +30,7 @@ h3. Maven Information com.shorrockin cascal - 1.0 + 1.2-Scala.2.8.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index 84818f9..d8c76eb 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 1.5 - 2.7.7 + 2.8.0 @@ -40,6 +40,7 @@ org.scala-tools maven-scala-plugin + 2.14 scala-compile-first diff --git a/project/build.properties b/project/build.properties index 0e49732..9097b4b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -4,5 +4,5 @@ project.organization=com.shorrockin project.name=cascal sbt.version=0.7.3 project.version=N/A -build.scala.versions=2.7.7 +build.scala.versions=2.8.0 project.initialize=false diff --git a/src/main/scala/com/shorrockin/cascal/model/Column.scala b/src/main/scala/com/shorrockin/cascal/model/Column.scala index 32d8abb..fb914a3 100644 --- a/src/main/scala/com/shorrockin/cascal/model/Column.scala +++ b/src/main/scala/com/shorrockin/cascal/model/Column.scala @@ -5,6 +5,8 @@ import org.apache.cassandra.thrift.{ColumnPath, ColumnOrSuperColumn} import org.apache.cassandra.thrift.{Column => CassColumn} import org.apache.cassandra.thrift.{SuperColumn => CassSuperColumn} import com.shorrockin.cascal.utils.Conversions +import com.shorrockin.cascal.utils.Utils.now + /** * a column is the child component of a super column or a @@ -18,8 +20,8 @@ case class Column[Owner](val name:Array[Byte], val time:Long, val owner:Owner) extends Gettable[Column[Owner]] { - def this(name:Array[Byte], value:Array[Byte], owner:Owner) = this(name, value, System.currentTimeMillis, owner) - def this(name:Array[Byte], owner:Owner) = this(name, null, System.currentTimeMillis, owner) + def this(name:Array[Byte], value:Array[Byte], owner:Owner) = this(name, value, now, owner) + def this(name:Array[Byte], owner:Owner) = this(name, null, now, owner) def this(name:Array[Byte], value:Array[Byte], date:Date, owner:Owner) = this(name, value, date.getTime, owner) diff --git a/src/main/scala/com/shorrockin/cascal/model/SuperColumn.scala b/src/main/scala/com/shorrockin/cascal/model/SuperColumn.scala index f707d44..3a99b4f 100644 --- a/src/main/scala/com/shorrockin/cascal/model/SuperColumn.scala +++ b/src/main/scala/com/shorrockin/cascal/model/SuperColumn.scala @@ -1,7 +1,6 @@ package com.shorrockin.cascal.model import org.apache.cassandra.thrift.{ColumnPath, ColumnParent, ColumnOrSuperColumn} -import scala.collection.jcl.Conversions.convertList /** * a super standard key the key who's parent is a super key. It acts in much @@ -24,6 +23,10 @@ case class SuperColumn(val value:Array[Byte], val key:SuperKey) extends Gettable def ::(other:SuperColumn):List[SuperColumn] = other :: this :: Nil + private def convertList[T](v:java.util.List[T]):List[T] = { + scala.collection.JavaConversions.asBuffer(v).toList + } + /** * given the returned object from the get request, convert * to our return type. diff --git a/src/main/scala/com/shorrockin/cascal/model/SuperKey.scala b/src/main/scala/com/shorrockin/cascal/model/SuperKey.scala index 76d0cc4..56a0b77 100644 --- a/src/main/scala/com/shorrockin/cascal/model/SuperKey.scala +++ b/src/main/scala/com/shorrockin/cascal/model/SuperKey.scala @@ -1,6 +1,5 @@ package com.shorrockin.cascal.model -import scala.collection.jcl.Conversions.convertList import org.apache.cassandra.thrift.{ColumnOrSuperColumn} case class SuperKey(val value:String, val family:SuperColumnFamily) extends Key[SuperColumn, Seq[(SuperColumn, Seq[Column[SuperColumn]])]] { @@ -21,5 +20,9 @@ case class SuperKey(val value:String, val family:SuperColumnFamily) extends Key[ } } + private def convertList[T](v:java.util.List[T]):List[T] = { + scala.collection.JavaConversions.asBuffer(v).toList + } + override def toString = "%s \\ SuperKey(value = %s)".format(family.toString, value) } \ No newline at end of file diff --git a/src/main/scala/com/shorrockin/cascal/session/Operation.scala b/src/main/scala/com/shorrockin/cascal/session/Operation.scala index 756eb43..871bc50 100644 --- a/src/main/scala/com/shorrockin/cascal/session/Operation.scala +++ b/src/main/scala/com/shorrockin/cascal/session/Operation.scala @@ -2,6 +2,7 @@ package com.shorrockin.cascal.session import org.apache.cassandra.thrift.{Deletion, Mutation} import com.shorrockin.cascal.model._ +import com.shorrockin.cascal.utils.Utils.now /** * defines an operation that can be executed in parallel with a collection @@ -50,7 +51,7 @@ class Delete(val container:ColumnContainer[_, _], val predicate:Predicate) exten lazy val mutation = { val out = new Mutation val del = new Deletion - del.setTimestamp(System.currentTimeMillis) + del.setTimestamp(now) predicate match { case EmptyPredicate => /* do nothing */ diff --git a/src/main/scala/com/shorrockin/cascal/session/Session.scala b/src/main/scala/com/shorrockin/cascal/session/Session.scala index c153413..0490c7c 100644 --- a/src/main/scala/com/shorrockin/cascal/session/Session.scala +++ b/src/main/scala/com/shorrockin/cascal/session/Session.scala @@ -2,19 +2,17 @@ package com.shorrockin.cascal.session import org.apache.thrift.protocol.TBinaryProtocol -import collection.jcl.Buffer import org.apache.cassandra.thrift.{Mutation, Cassandra, NotFoundException, ConsistencyLevel} import java.util.{Map => JMap, List => JList, HashMap, ArrayList} - - -import collection.jcl.Conversions._ import com.shorrockin.cascal.utils.Conversions._ +import com.shorrockin.cascal.utils.Utils.now import com.shorrockin.cascal.model._ import org.apache.thrift.transport.{TFramedTransport, TSocket} import collection.immutable.HashSet +import java.util.concurrent.atomic.AtomicLong /** * a cascal session is the entry point for interacting with the @@ -22,12 +20,11 @@ import collection.immutable.HashSet * * @author Chris Shorrock */ -class Session(val host: Host, val defaultConsistency: Consistency, val framedTransport: Boolean) extends SessionTemplate { - def this(host: String, port: Int, timeout: Int, defaultConsistency: Consistency, framedTransport: Boolean) = this (Host(host, port, timeout), defaultConsistency, framedTransport) - - def this(host: String, port: Int, timeout: Int, defaultConsistency: Consistency) = this (host, port, timeout, defaultConsistency, false) +class Session(val host:Host, val defaultConsistency:Consistency, val framedTransport:Boolean) extends SessionTemplate { - def this(host: String, port: Int, timeout: Int) = this (host, port, timeout, Consistency.One, false) + def this(host:String, port:Int, timeout:Int, defaultConsistency:Consistency, framedTransport:Boolean) = this(Host(host, port, timeout), defaultConsistency, framedTransport) + def this(host:String, port:Int, timeout:Int, defaultConsistency:Consistency) = this(host, port, timeout, defaultConsistency, false) + def this(host:String, port:Int, timeout:Int) = this(host, port, timeout, Consistency.One, false) private val sock = { if (framedTransport) new TFramedTransport(new TSocket(host.address, host.port, host.timeout)) @@ -91,12 +88,11 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra lazy val keyspaces: Seq[String] = Buffer(client.get_string_list_property("keyspaces")) /** - * returns the + * returns the descriptors for all keyspaces */ - lazy val keyspaceDescriptors: Set[Tuple3[String, String, String]] = { var keyspaceDesc: Set[Tuple3[String, String, String]] = new HashSet[Tuple3[String, String, String]] - client.describe_keyspaces foreach { + convertSet(client.describe_keyspaces) foreach { space => val familyMap = client.describe_keyspace(space) familyMap.keySet foreach { @@ -129,6 +125,7 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra } } + /** * returns the column value for the specified column */ @@ -247,11 +244,10 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra def locate(key: String) = (containers.find {_.key.value.equals(key)}).get - results.map { - (tuple) => - val key = locate(tuple._1) - val value = key.convertListResult(tuple._2) - (key -> value) + convertMap(results).map { (tuple) => + val key = locate(tuple._1) + val value = key.convertListResult(tuple._2) + (key -> value) }.toSeq } else { throw new IllegalArgumentException("must provide at least 1 container for a list(keys, predicate, consistency) call") @@ -280,10 +276,9 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra val results = client.get_range_slices(family.keyspace.value, family.columnParent, predicate.slicePredicate, range.cassandraRange, consistency) var map = Map[Key[ColumnType, ListType], ListType]() - results.foreach { - (keyslice) => - val key = (family \ keyslice.key) - map = map + (key -> key.convertListResult(keyslice.columns)) + convertList(results).foreach { (keyslice) => + val key = (family \ keyslice.key) + map = map + (key -> key.convertListResult(keyslice.columns)) } map } @@ -352,13 +347,6 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra */ private implicit def toThriftConsistency(c: Consistency): ConsistencyLevel = c.thriftValue - - /** - * retuns the current time in milliseconds - */ - private def now = System.currentTimeMillis - - /** * all calls which access the session should be wrapped within this method, * it will catch any exceptions and make sure the session is then removed @@ -370,4 +358,19 @@ class Session(val host: Host, val defaultConsistency: Consistency, val framedTra case t: Throwable => lastError = Some(t); throw t } -} \ No newline at end of file + private def Buffer[T](v:java.util.List[T]) = { + scala.collection.JavaConversions.asBuffer(v) + } + + implicit private def convertList[T](v:java.util.List[T]):List[T] = { + scala.collection.JavaConversions.asBuffer(v).toList + } + + implicit private def convertMap[K,V](v:java.util.Map[K,V]): scala.collection.mutable.Map[K,V] = { + scala.collection.JavaConversions.asMap(v) + } + + implicit private def convertSet[T](s:java.util.Set[T]):scala.collection.mutable.Set[T] = { + scala.collection.JavaConversions.asSet(s) + } +} diff --git a/src/main/scala/com/shorrockin/cascal/testing/CassandraTestPool.scala b/src/main/scala/com/shorrockin/cascal/testing/CassandraTestPool.scala index ccda4ab..e182497 100644 --- a/src/main/scala/com/shorrockin/cascal/testing/CassandraTestPool.scala +++ b/src/main/scala/com/shorrockin/cascal/testing/CassandraTestPool.scala @@ -5,8 +5,8 @@ import org.apache.cassandra.config.DatabaseDescriptor import java.io.File import java.net.ConnectException import org.apache.thrift.transport.{TTransportException, TSocket} -import session._ -import utils.{Utils, Logging} +import com.shorrockin.cascal.session._ +import com.shorrockin.cascal.utils.{Utils, Logging} /** * trait which mixes in the functionality necessary to embed * cassandra into a unit test diff --git a/src/main/scala/com/shorrockin/cascal/utils/Logging.scala b/src/main/scala/com/shorrockin/cascal/utils/Logging.scala index 5b9ea5d..d824a62 100644 --- a/src/main/scala/com/shorrockin/cascal/utils/Logging.scala +++ b/src/main/scala/com/shorrockin/cascal/utils/Logging.scala @@ -8,5 +8,5 @@ import org.apache.commons.logging.LogFactory * @author Chris Shorrock */ trait Logging { - @transient @volatile lazy val log = LogFactory.getLog(this.getClass.getName) + lazy val log = LogFactory.getLog(this.getClass.getName) } diff --git a/src/main/scala/com/shorrockin/cascal/utils/Utils.scala b/src/main/scala/com/shorrockin/cascal/utils/Utils.scala index 4f73a0f..298571d 100755 --- a/src/main/scala/com/shorrockin/cascal/utils/Utils.scala +++ b/src/main/scala/com/shorrockin/cascal/utils/Utils.scala @@ -2,6 +2,7 @@ package com.shorrockin.cascal.utils import _root_.scala.io.Source import java.io.{FileWriter, InputStream, FileOutputStream, File} +import java.util.concurrent.TimeUnit /** * common utility functions that don't fit elsewhere. @@ -51,7 +52,7 @@ object Utils extends Logging { * file in the source file. */ def replace(file:File, replacements:(String, String)*):File = { - val contents = Source.fromFile(file).getLines.toList.map { (line) => + val contents = Source.fromFile(file).getLines().toList.map { (line) => var current = line replacements.foreach { (r) => current = current.replace(r._1, r._2) } current @@ -82,4 +83,33 @@ object Utils extends Logging { closeable.foreach { (c) => ignore(c.close()) } } } + + private val epocBaseMicros = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis) + private val runBaseNanos = System.nanoTime + + def currentTimeMicros = epocBaseMicros + TimeUnit.NANOSECONDS.toMicros(System.nanoTime-runBaseNanos) + + var COMPENSATE_FOR_LOW_PRECISION_SYSTEM_TIME = System.getProperty("com.shorrockin.cascal.COMPENSATE_FOR_LOW_PRESCISION_SYSTEM_TIME", "false") == "true" + + private var previousNow = System.currentTimeMillis + + /** + * retuns the current time in micro seconds + */ + def now = { + + var rc = currentTimeMicros + + // It's very possible the platform can issue repetitive calls to now faster than + // the the platforms timer can change. + if( COMPENSATE_FOR_LOW_PRECISION_SYSTEM_TIME ) { + Utils.synchronized { + if( rc <= previousNow ) { + rc = previousNow + 1 + } + previousNow = rc + } + } + rc + } } \ No newline at end of file diff --git a/src/test/scala/com/shorrockin/cascal/TestInsertRemoveLoop.scala b/src/test/scala/com/shorrockin/cascal/TestInsertRemoveLoop.scala new file mode 100755 index 0000000..dad0b8d --- /dev/null +++ b/src/test/scala/com/shorrockin/cascal/TestInsertRemoveLoop.scala @@ -0,0 +1,38 @@ +package com.shorrockin.cascal + +import testing._ +import org.junit.{Assert, Test} +import com.shorrockin.cascal.utils.Utils + +/** + * tests a looping insert remove. Stresses out the precision of + * system time. + */ +class TestInsertRemoveLoop extends CassandraTestPool { + import com.shorrockin.cascal.utils.Conversions._ + import Assert._ + + @Test def testInsertRemoveLoop = borrow { session => + + def checkLowResolution = { + var onLowPrecisionSystem = false + for( i <- 1L to 100L ) { + session.remove("Test" \ "Standard" \ "Test") + session.insert("Test" \ "Standard" \ "Test" \ (i, "hello:"+i)) + if( session.get("Test" \ "Standard" \ "Test" \ i) == None ) { + onLowPrecisionSystem = true + } + } + onLowPrecisionSystem + } + + if( checkLowResolution ) { + println("You have low resolution timer on this system") + Utils.COMPENSATE_FOR_LOW_PRECISION_SYSTEM_TIME = true + assertFalse("setting Utils.COMPENSATE_FOR_LOW_PRECISION_SYSTEM_TIME = true did not work around the low resolution timer problems.", checkLowResolution); + } else { + println("You have high resolution timer on this system") + } + } + +} \ No newline at end of file diff --git a/src/test/scala/com/shorrockin/cascal/TestSerialization.scala b/src/test/scala/com/shorrockin/cascal/TestSerialization.scala index a3a7d4a..d0794f2 100755 --- a/src/test/scala/com/shorrockin/cascal/TestSerialization.scala +++ b/src/test/scala/com/shorrockin/cascal/TestSerialization.scala @@ -11,16 +11,21 @@ case class MappedStandard(@Key val a:Long, @Value("Column-B") val b:Date, @Value @Keyspace("Test") @Family("Standard") case class DynamicMappedStandard(@Key val key:Long, - @Columns { val name=classOf[String], val value=classOf[Int]} values:Seq[(String, Int)]) + @Columns(name=classOf[String], value=classOf[Int]) values:Seq[(String, Int)]) @Keyspace("Test") @Family("Super") @Super case class MappedSuper(@Key val a:String, @SuperColumn val s:String, @Value("Column-B") val b:Date, @Value("Column-C") val c:Long) +@Keyspace("Test") @Family("Super") @Super +case class MappedSuperWithCols(@Key val a:String, @SuperColumn val s:String, @Columns(name=classOf[String], value=classOf[Long]) raw:Seq[(String, Long)]) { + val values = raw.map { _._1 } +} + @Keyspace("Test") @Family("Standard") -case class MappedOptionStandard(@Optional { val column="Column", val as=classOf[Long] } val value:Option[Long]) +case class MappedOptionStandard(@Optional(column="Column", as=classOf[Long]) val value:Option[Long]) @Keyspace("Test") @Family("Super") @Super -case class MappedOptionSuper(@Optional { val column="C", val as=classOf[String] } val value:Option[String]) +case class MappedOptionSuper(@Optional(column="C", as=classOf[String]) val value:Option[String]) class TestSerialization { import Conversions._ @@ -87,12 +92,20 @@ class TestSerialization { val sc = key \ "Super Column Value" val colb = sc \ "Column-B" \ now val colc = sc \ "Column-C" \ 12L + val cold = sc \ "Column-D" \ 13L val obj = Converter[MappedSuper](colc :: colb) assertEquals("Hello", obj.a) assertEquals("Super Column Value", obj.s) assertEquals(now, obj.b) assertEquals(12L, obj.c) + + val obj2 = Converter[MappedSuperWithCols](colc :: cold) + assertEquals("Hello", obj.a) + assertEquals("Super Column Value", obj.s) + assertTrue(obj2.values.contains("Column-C")); + assertTrue(obj2.values.contains("Column-D")); + } @@ -103,4 +116,4 @@ class TestSerialization { @Test def testCanConvertObjectToSuperColumnList() { } -} \ No newline at end of file +}