forked from twitter/storehaus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from twitter/develop
Merge from twitter/storehaus
- Loading branch information
Showing
8 changed files
with
410 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
storehaus-core/src/main/scala/com/twitter/storehaus/PivotedReadableStore.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2013 Twitter Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. You may obtain | ||
* a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.twitter.storehaus | ||
|
||
import com.twitter.bijection.Injection | ||
import com.twitter.util.{ Future, Time } | ||
|
||
import scala.util.{ Failure, Success } | ||
|
||
/** | ||
* ReadableStore enrichment for ReadableStore[OuterK, ReadableStore[InnerK, V]] | ||
* on top of a ReadableStore[K, V] | ||
* | ||
* @author Ruban Monu | ||
*/ | ||
object PivotedReadableStore { | ||
|
||
def fromMap[K, OuterK, InnerK, V](m: Map[K, V])(implicit inj: Injection[(OuterK, InnerK), K]) = | ||
new PivotedReadableStore[K, OuterK, InnerK, V](ReadableStore.fromMap(m))(inj) | ||
|
||
def fromReadableStore[K, OuterK, InnerK, V](store: ReadableStore[K, V])(implicit inj: Injection[(OuterK, InnerK), K]) = | ||
new PivotedReadableStore[K, OuterK, InnerK, V](store)(inj) | ||
} | ||
|
||
class PivotedReadableStore[K, -OuterK, InnerK, +V](store: ReadableStore[K, V])(implicit inj: Injection[(OuterK, InnerK), K]) | ||
extends ReadableStore[OuterK, ReadableStore[InnerK, V]] { | ||
|
||
override def get(outerK: OuterK) : Future[Option[ReadableStore[InnerK, V]]] = | ||
Future.value(Some(new ReadableStore[InnerK, V]() { | ||
override def get(innerK: InnerK) = store.get(inj((outerK, innerK))) | ||
})) | ||
|
||
override def close(time: Time) = store.close(time) | ||
} | ||
|
70 changes: 70 additions & 0 deletions
70
storehaus-core/src/test/scala/com/twitter/storehaus/PivotedReadableStoreProperties.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Copyright 2013 Twitter Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. You may obtain | ||
* a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.twitter.storehaus | ||
|
||
import com.twitter.bijection.Injection | ||
import com.twitter.storehaus.testing.SelfAggregatingCloseableCleanup | ||
import com.twitter.util.Await | ||
|
||
import org.scalacheck.Properties | ||
|
||
import scala.util.Try | ||
|
||
object PivotedReadableStoreProperties extends Properties("PivotedReadableStore") | ||
with SelfAggregatingCloseableCleanup[PivotedReadableStore[String, String, Int, String]] { | ||
|
||
// (prefix, num) => "prefix/num" | ||
implicit object PivotInjection extends Injection[(String, Int), String] { | ||
def apply(pair: (String, Int)): String = pair._1 + "/" + pair._2.toString | ||
override def invert(s: String) = { | ||
val parts = s.split('/') | ||
Try((parts(0), parts(1).toInt)) | ||
} | ||
} | ||
|
||
def getStoreTest(store: PivotedReadableStore[String, String, Int, String]) = { | ||
val innerStore1 = Await.result(store.get("prefix1")).get | ||
val innerStore2 = Await.result(store.get("prefix2")).get | ||
(0 until 100).toList.forall { case n => | ||
Await.result(innerStore1.get(n)).get == "value1" + n.toString | ||
Await.result(innerStore2.get(n)).get == "value2" + n.toString | ||
} | ||
} | ||
|
||
def multiGetStoreTest(store: PivotedReadableStore[String, String, Int, String]) = { | ||
val innerStores = store.multiGet(Set("prefix1", "prefix2")) | ||
val innerStore1 = Await.result(innerStores.get("prefix1").get).get | ||
val innerStore2 = Await.result(innerStores.get("prefix2").get).get | ||
(0 until 100).toList.forall { case n => | ||
Await.result(innerStore1.get(n)).get == "value1" + n.toString | ||
Await.result(innerStore2.get(n)).get == "value2" + n.toString | ||
} | ||
} | ||
|
||
property("PivotedReadableStore gets over MapStore") = { | ||
val map1 : Map[String, String] = (0 until 100).toList.map { case n => | ||
(PivotInjection(("prefix1", n)), "value1" + n.toString) | ||
}.toMap | ||
val map2 : Map[String, String] = (0 until 100).toList.map { case n => | ||
(PivotInjection(("prefix2", n)), "value2" + n.toString) | ||
}.toMap | ||
|
||
val store = PivotedReadableStore.fromMap[String, String, Int, String](map1 ++ map2) | ||
|
||
getStoreTest(store) && multiGetStoreTest(store) | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
storehaus-mysql/src/main/scala/com/twitter/storehaus/mysql/MergeableMySqlStore.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Copyright 2013 Twitter Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. You may obtain | ||
* a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.twitter.storehaus.mysql | ||
|
||
import com.twitter.algebird.Semigroup | ||
import com.twitter.bijection.Injection | ||
import com.twitter.finagle.exp.mysql.Client | ||
import com.twitter.storehaus.algebra.MergeableStore | ||
import com.twitter.storehaus.ConvertedStore | ||
import com.twitter.storehaus.FutureOps | ||
import com.twitter.util.{ Future, Throw } | ||
|
||
/** | ||
* @author Ruban Monu | ||
*/ | ||
|
||
/** | ||
* Mergeable MySQL store that performs merge inside a transaction. | ||
*/ | ||
class MergeableMySqlStore[V](underlying: MySqlStore)(implicit inj: Injection[V, MySqlValue], | ||
override val semigroup: Semigroup[V]) | ||
extends ConvertedStore[MySqlValue, MySqlValue, MySqlValue, V](underlying)(identity) | ||
with MergeableStore[MySqlValue, V] { | ||
|
||
// Merges multiple keys inside a transaction. | ||
// 1. existing keys are fetched using multiGet (SELECT query) | ||
// 2. new keys are added using INSERT query | ||
// 3. existing keys are merged using UPDATE query | ||
// NOTE: merge on a single key also in turn calls this | ||
override def multiMerge[K1 <: MySqlValue](kvs: Map[K1, V]): Map[K1, Future[Option[V]]] = { | ||
val mergeResult : Future[Map[K1, Option[V]]] = underlying.startTransaction.flatMap { u: Unit => | ||
FutureOps.mapCollect(multiGet(kvs.keySet)).flatMap { result: Map[K1, Option[V]] => | ||
val existingKeys = result.filter { !_._2.isEmpty }.keySet | ||
val newKeys = result.filter { _._2.isEmpty }.keySet | ||
|
||
// handle inserts for new keys | ||
val insertF = newKeys.isEmpty match { | ||
case true => Future.Unit | ||
case false => | ||
val insertKvs = newKeys.map { k => k -> kvs.get(k).get } | ||
insertKvs.isEmpty match { | ||
case true => Future.Unit | ||
case false => underlying.executeMultiInsert(insertKvs.toMap.mapValues { v => inj(v) }) | ||
} | ||
} | ||
|
||
// handle update/merge for existing keys | ||
// lazy val realized inside of insertF.flatMap | ||
lazy val updateF = existingKeys.isEmpty match { | ||
case true => Future.Unit | ||
case false => | ||
val existingKvs = existingKeys.map { k => k -> kvs.get(k).get } | ||
underlying.executeMultiUpdate(existingKvs.map { kv => | ||
val resV = semigroup.plus(result.get(kv._1).get.get, kv._2) | ||
kv._1 -> inj(resV) | ||
}.toMap) | ||
} | ||
|
||
// insert, update and commit or rollback accordingly | ||
insertF.flatMap { f => | ||
updateF.flatMap { f => | ||
underlying.commitTransaction.map { f => | ||
// return values before the merge | ||
result | ||
} | ||
} | ||
.onFailure { case e: Exception => | ||
underlying.rollbackTransaction.map { f => | ||
// map values to exception | ||
result.mapValues { v => e } | ||
} | ||
} | ||
} | ||
} | ||
} | ||
FutureOps.liftValues(kvs.keySet, mergeResult) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
storehaus-mysql/src/main/scala/com/twitter/storehaus/mysql/MySqlLongStore.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright 2013 Twitter Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. You may obtain | ||
* a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.twitter.storehaus.mysql | ||
|
||
import com.twitter.algebird.Semigroup | ||
import com.twitter.bijection.Injection | ||
import com.twitter.finagle.exp.mysql.Client | ||
import com.twitter.storehaus.ConvertedStore | ||
import com.twitter.storehaus.algebra.MergeableStore | ||
|
||
import org.jboss.netty.buffer.ChannelBuffer | ||
|
||
/** | ||
* @author Ruban Monu | ||
*/ | ||
|
||
/** Factory for [[com.twitter.storehaus.mysql.MySqlLongStore]] instances. */ | ||
object MySqlLongStore { | ||
|
||
def apply(underlying: MySqlStore) = | ||
new MySqlLongStore(underlying)(LongMySqlInjection) | ||
|
||
def apply(client: Client, table: String, kCol: String, vCol: String) = | ||
new MySqlLongStore(MySqlStore(client, table, kCol, vCol))(LongMySqlInjection) | ||
} | ||
import MySqlLongStore._ | ||
|
||
/** MySQL store for Long values */ | ||
class MySqlLongStore(underlying: MySqlStore)(inj: Injection[Long, MySqlValue]) | ||
extends MergeableMySqlStore[Long](underlying)(inj, implicitly[Semigroup[Long]]) { | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.