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
+}