├── version.sbt ├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── aerospike-core └── src │ ├── main │ └── scala │ │ └── io │ │ └── github │ │ └── reugn │ │ └── aerospike │ │ └── scala │ │ ├── DataSource.scala │ │ ├── listener │ │ ├── ScalaAbortListener.scala │ │ ├── ScalaWriteListener.scala │ │ ├── ScalaDeleteListener.scala │ │ ├── ScalaExistsListener.scala │ │ ├── ScalaRecordListener.scala │ │ ├── ScalaCommitListener.scala │ │ ├── ScalaExistsArrayListener.scala │ │ ├── ScalaRecordArrayListener.scala │ │ ├── ScalaInfoListener.scala │ │ ├── ScalaBatchOperateListListener.scala │ │ ├── ScalaBatchRecordArrayListener.scala │ │ └── PromiseLike.scala │ │ ├── util │ │ └── OperatingSystem.scala │ │ ├── RecordScanCallback.scala │ │ ├── StreamHandler.scala │ │ ├── QueryRecordSequenceListener.scala │ │ ├── Policies.scala │ │ ├── KeyRecordSource.scala │ │ ├── EventLoopProvider.scala │ │ ├── model │ │ └── QueryStatement.scala │ │ ├── RecordSet.scala │ │ ├── AerospikeClientBuilder.scala │ │ ├── AsyncHandler.scala │ │ └── AerospikeHandler.scala │ └── test │ └── scala │ └── io │ └── github │ └── reugn │ └── aerospike │ └── scala │ ├── TestCommon.scala │ └── AerospikeHandlerTest.scala ├── .github └── workflows │ └── build.yml ├── README.md ├── aerospike-zio └── src │ ├── test │ └── scala │ │ └── io │ │ └── github │ │ └── reugn │ │ └── aerospike │ │ └── scala │ │ └── zioeffect │ │ └── ZioAerospikeHandlerTest.scala │ └── main │ └── scala │ └── io │ └── github │ └── reugn │ └── aerospike │ └── scala │ └── zioeffect │ └── ZioAerospikeHandler.scala ├── aerospike-monix └── src │ ├── test │ └── scala │ │ └── io │ │ └── github │ │ └── reugn │ │ └── aerospike │ │ └── scala │ │ └── monixeffect │ │ └── MonixAerospikeHandlerTest.scala │ └── main │ └── scala │ └── io │ └── github │ └── reugn │ └── aerospike │ └── scala │ └── monixeffect │ └── MonixAerospikeHandler.scala └── LICENSE /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.7.0" -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.10.6 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.12.2") 2 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | project/project 2 | project/target 3 | target 4 | *.class 5 | 6 | *.iml 7 | *.ipr 8 | *.iws 9 | .idea 10 | out 11 | 12 | tags 13 | .*.swp 14 | .*.swo 15 | 16 | build 17 | .classpath 18 | .project 19 | .settings 20 | .bsp 21 | 22 | logs -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/DataSource.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import akka.stream.SourceShape 4 | import akka.stream.stage.GraphStage 5 | 6 | abstract class DataSource[T] extends GraphStage[SourceShape[T]] -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaAbortListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.AbortStatus 4 | import com.aerospike.client.listener.AbortListener 5 | 6 | class ScalaAbortListener extends AbortListener with PromiseLike[AbortStatus] { 7 | 8 | override def onSuccess(status: AbortStatus): Unit = { 9 | success(status) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaWriteListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.WriteListener 4 | import com.aerospike.client.{AerospikeException, Key} 5 | 6 | class ScalaWriteListener extends WriteListener with PromiseLike[Key] { 7 | 8 | override def onSuccess(key: Key): Unit = { 9 | success(key) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaDeleteListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.DeleteListener 4 | import com.aerospike.client.{AerospikeException, Key} 5 | 6 | class ScalaDeleteListener extends DeleteListener with PromiseLike[Boolean] { 7 | 8 | override def onSuccess(key: Key, existed: Boolean): Unit = { 9 | success(existed) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaExistsListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.ExistsListener 4 | import com.aerospike.client.{AerospikeException, Key} 5 | 6 | class ScalaExistsListener extends ExistsListener with PromiseLike[Boolean] { 7 | 8 | override def onSuccess(key: Key, exists: Boolean): Unit = { 9 | success(exists) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaRecordListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.RecordListener 4 | import com.aerospike.client.{AerospikeException, Key, Record} 5 | 6 | class ScalaRecordListener extends RecordListener with PromiseLike[Record] { 7 | 8 | override def onSuccess(key: Key, record: Record): Unit = { 9 | success(record) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/util/OperatingSystem.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.util 2 | 3 | sealed trait OperatingSystem 4 | 5 | case object Linux extends OperatingSystem 6 | 7 | case object Mac extends OperatingSystem 8 | 9 | case object Other extends OperatingSystem 10 | 11 | object OperatingSystem { 12 | def apply(): OperatingSystem = { 13 | val OSName = System.getProperty("os.name").toLowerCase 14 | if (OSName.contains("nux")) Linux 15 | else if (OSName.contains("mac")) Mac 16 | else Other 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaCommitListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.CommitListener 4 | import com.aerospike.client.{AerospikeException, CommitStatus} 5 | 6 | class ScalaCommitListener extends CommitListener with PromiseLike[CommitStatus] { 7 | 8 | override def onSuccess(status: CommitStatus): Unit = { 9 | success(status) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException.Commit): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaExistsArrayListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.ExistsArrayListener 4 | import com.aerospike.client.{AerospikeException, Key} 5 | 6 | class ScalaExistsArrayListener extends ExistsArrayListener with PromiseLike[Seq[Boolean]] { 7 | 8 | override def onSuccess(keys: Array[Key], exists: Array[Boolean]): Unit = { 9 | success(exists.toIndexedSeq) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaRecordArrayListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.RecordArrayListener 4 | import com.aerospike.client.{AerospikeException, Key, Record} 5 | 6 | class ScalaRecordArrayListener extends RecordArrayListener with PromiseLike[Seq[Record]] { 7 | 8 | override def onSuccess(keys: Array[Key], records: Array[Record]): Unit = { 9 | success(records.toIndexedSeq) 10 | } 11 | 12 | override def onFailure(exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaInfoListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.AerospikeException 4 | import com.aerospike.client.listener.InfoListener 5 | 6 | import java.util 7 | import scala.collection.JavaConverters._ 8 | 9 | class ScalaInfoListener extends InfoListener with PromiseLike[Map[String, String]] { 10 | 11 | override def onSuccess(map: util.Map[String, String]): Unit = { 12 | success(map.asScala.toMap) 13 | } 14 | 15 | override def onFailure(ae: AerospikeException): Unit = { 16 | failure(ae) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaBatchOperateListListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.BatchOperateListListener 4 | import com.aerospike.client.{AerospikeException, BatchRecord} 5 | 6 | import java.util 7 | 8 | class ScalaBatchOperateListListener extends BatchOperateListListener with PromiseLike[Boolean] { 9 | 10 | override def onSuccess(records: util.List[BatchRecord], status: Boolean): Unit = { 11 | success(status) 12 | } 13 | 14 | override def onFailure(exception: AerospikeException): Unit = { 15 | failure(exception) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/ScalaBatchRecordArrayListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import com.aerospike.client.listener.BatchRecordArrayListener 4 | import com.aerospike.client.{AerospikeException, BatchRecord, BatchResults} 5 | 6 | class ScalaBatchRecordArrayListener extends BatchRecordArrayListener with PromiseLike[BatchResults] { 7 | 8 | override def onSuccess(records: Array[BatchRecord], status: Boolean): Unit = { 9 | success(new BatchResults(records, status)) 10 | } 11 | 12 | override def onFailure(records: Array[BatchRecord], exception: AerospikeException): Unit = { 13 | failure(exception) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/RecordScanCallback.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.query.KeyRecord 4 | import com.aerospike.client.{AerospikeException, Key, Record, ScanCallback} 5 | 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | class RecordScanCallback() extends ScanCallback { 9 | private val recordSet = ArrayBuffer[KeyRecord]() 10 | 11 | @throws[AerospikeException] 12 | override def scanCallback(key: Key, record: Record): Unit = { 13 | recordSet.append(new KeyRecord(key, record)) 14 | } 15 | 16 | def getRecordSet: List[KeyRecord] = recordSet.toList 17 | } 18 | 19 | object RecordScanCallback { 20 | 21 | def apply(): RecordScanCallback = new RecordScanCallback() 22 | } 23 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/listener/PromiseLike.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.listener 2 | 3 | import scala.concurrent.{Future, Promise} 4 | import scala.util.{Failure, Success} 5 | 6 | trait PromiseLike[T] { 7 | 8 | private val p: Promise[T] = Promise[T]() 9 | 10 | /** 11 | * Completes the underlying promise with a value. 12 | */ 13 | def success(value: T): this.type = { 14 | p.complete(Success(value)) 15 | this 16 | } 17 | 18 | /** 19 | * Completes the underlying promise with an exception. 20 | */ 21 | def failure(cause: Throwable): this.type = { 22 | p.complete(Failure(cause)) 23 | this 24 | } 25 | 26 | /** 27 | * Future containing the value of the underlying promise. 28 | */ 29 | def future: Future[T] = p.future 30 | } 31 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/StreamHandler.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.policy.QueryPolicy 4 | import io.github.reugn.aerospike.scala.model.QueryStatement 5 | 6 | import scala.language.higherKinds 7 | 8 | sealed trait StreamHandler 9 | 10 | trait StreamHandler1[S[_]] extends StreamHandler { 11 | 12 | def query(statement: QueryStatement) 13 | (implicit policy: QueryPolicy = null): S[_] 14 | } 15 | 16 | trait StreamHandler2[S[_, _]] extends StreamHandler { 17 | 18 | def query(statement: QueryStatement) 19 | (implicit policy: QueryPolicy = null): S[_, _] 20 | } 21 | 22 | trait StreamHandler3[S[_, _, _]] extends StreamHandler { 23 | 24 | def query(statement: QueryStatement) 25 | (implicit policy: QueryPolicy = null): S[_, _, _] 26 | } 27 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/QueryRecordSequenceListener.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.listener.RecordSequenceListener 4 | import com.aerospike.client.query.KeyRecord 5 | import com.aerospike.client.{AerospikeException, Key, Record} 6 | 7 | class QueryRecordSequenceListener extends RecordSequenceListener { 8 | 9 | private val recordSet = new RecordSet 10 | 11 | override def onRecord(key: Key, record: Record): Unit = { 12 | if (record != null) { 13 | recordSet.put(new KeyRecord(key, record)) 14 | } 15 | } 16 | 17 | override def onSuccess(): Unit = { 18 | recordSet.close() 19 | } 20 | 21 | override def onFailure(exception: AerospikeException): Unit = { 22 | recordSet.closeExceptionally(exception) 23 | } 24 | 25 | def getRecordSet: RecordSet = recordSet 26 | } 27 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/Policies.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.policy._ 4 | 5 | import scala.language.implicitConversions 6 | 7 | object Policies { 8 | 9 | implicit lazy val policy: Policy = new Policy 10 | implicit lazy val writePolicy: WritePolicy = new WritePolicy 11 | implicit lazy val queryPolicy: QueryPolicy = new QueryPolicy 12 | implicit lazy val infoPolicy: InfoPolicy = new InfoPolicy 13 | implicit lazy val batchPolicy: BatchPolicy = new BatchPolicy 14 | 15 | object ClientPolicyImplicits { 16 | 17 | implicit class Cpi(val clientPolicy: ClientPolicy) { 18 | 19 | implicit def withEventLoops(): ClientPolicy = { 20 | if (clientPolicy.eventLoops == null) 21 | clientPolicy.eventLoops = EventLoopProvider.eventLoops 22 | clientPolicy 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/KeyRecordSource.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import akka.stream.stage.{GraphStageLogic, OutHandler} 4 | import akka.stream.{Attributes, Outlet, SourceShape} 5 | import com.aerospike.client.query.KeyRecord 6 | 7 | class KeyRecordSource(iterator: Iterator[KeyRecord]) extends DataSource[KeyRecord] { 8 | 9 | private val out: Outlet[KeyRecord] = Outlet("KeyRecordSource") 10 | 11 | override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = { 12 | new GraphStageLogic(shape) { 13 | setHandler(out, new OutHandler { 14 | override def onPull(): Unit = { 15 | if (iterator.hasNext) 16 | push(out, iterator.next()) 17 | else 18 | complete(out) 19 | } 20 | }) 21 | } 22 | } 23 | 24 | override def shape: SourceShape[KeyRecord] = SourceShape(out) 25 | } 26 | -------------------------------------------------------------------------------- /aerospike-core/src/test/scala/io/github/reugn/aerospike/scala/TestCommon.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.{BatchResults, Bin, Key} 4 | 5 | import scala.language.higherKinds 6 | 7 | trait TestCommon { 8 | 9 | protected val hostname = "localhost" 10 | protected val port = 3000 11 | 12 | protected val namespace = "test" 13 | protected val set = "scalaClient" 14 | 15 | protected val numberOfKeys = 10 16 | protected val keys: Array[Key] = new Array[Key](numberOfKeys) 17 | 18 | protected def populateKeys[T[_]](handler: AsyncHandler[T]): Seq[T[Key]] = { 19 | (0 until numberOfKeys) map { 20 | i => { 21 | val key = new Key(namespace, set, "key_" + i) 22 | keys(i) = key 23 | handler.put(key, new Bin("intBin", i), new Bin("strBin", "str_" + i)) 24 | } 25 | } 26 | } 27 | 28 | protected def deleteKeys[T[_]](handler: AsyncHandler[T]): T[BatchResults] = { 29 | handler.deleteBatch(keys) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | branches: [ '**' ] 5 | push: 6 | branches: [ '**' ] 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ ubuntu-latest ] 14 | scala: [ 2.12, 2.13 ] 15 | java: 16 | - adopt@1.8 17 | platform: [ jvm ] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | 25 | - name: Setup Java and Scala 26 | uses: olafurpg/setup-scala@v14 27 | with: 28 | java-version: ${{ matrix.java }} 29 | 30 | - name: Set up Aerospike Database 31 | uses: reugn/github-action-aerospike@v1 32 | 33 | - name: Cache SBT 34 | uses: actions/cache@v4 35 | with: 36 | path: | 37 | ~/.ivy2/cache 38 | ~/.sbt 39 | key: ${{ runner.os }}-sbt-${{ hashFiles('**/build.sbt') }} 40 | 41 | - name: Build and test 42 | run: sbt ++${{ matrix.scala }} test -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/EventLoopProvider.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.async.{EventLoop, EventLoops, EventPolicy, NettyEventLoops} 4 | import io.github.reugn.aerospike.scala.util.{Linux, Mac, OperatingSystem} 5 | import io.netty.channel.epoll.{Epoll, EpollEventLoopGroup} 6 | import io.netty.channel.kqueue.{KQueue, KQueueEventLoopGroup} 7 | import io.netty.channel.nio.NioEventLoopGroup 8 | 9 | object EventLoopProvider { 10 | 11 | private lazy val nThreads: Int = Runtime.getRuntime.availableProcessors 12 | 13 | private[scala] lazy val eventLoops: EventLoops = { 14 | val eventLoopGroup = OperatingSystem() match { 15 | case Linux if Epoll.isAvailable => 16 | new EpollEventLoopGroup(nThreads) 17 | case Mac if KQueue.isAvailable => 18 | new KQueueEventLoopGroup(nThreads) 19 | case _ => 20 | new NioEventLoopGroup(nThreads) 21 | } 22 | new NettyEventLoops(new EventPolicy, eventLoopGroup) 23 | } 24 | 25 | private[scala] def eventLoop: EventLoop = eventLoops.next() 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aerospike-client-scala 2 | [![Build](https://github.com/reugn/aerospike-client-scala/actions/workflows/build.yml/badge.svg)](https://github.com/reugn/aerospike-client-scala/actions/workflows/build.yml) 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.reugn/aerospike-core_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.reugn/aerospike-core_2.12/) 4 | 5 | Idiomatic and reactive Scala client for [Aerospike](https://www.aerospike.com/) database. 6 | 7 | ## Modules 8 | * `aerospike-core` provides standard Scala Future and Akka Streams implementation. 9 | * `aerospike-monix` integrates with [Monix](https://monix.io/) to support Monix effects. 10 | * `aerospike-zio` integrates with [ZIO](https://zio.dev/) to support ZIO effects. 11 | 12 | ## Getting started 13 | Add the `aerospike-core` module as a dependency in your project: 14 | ```scala 15 | libraryDependencies += "io.github.reugn" %% "aerospike-core" % "" 16 | ``` 17 | * replace `aerospike-core` with `aerospike-monix` or `aerospike-zio` if required. 18 | 19 | ## Build from Source 20 | 1. Clone the repository 21 | 2. Run `sbt clean +package` 22 | 23 | This will create libraries for all modules for both Scala 2.12 and 2.13. 24 | 25 | ## License 26 | Licensed under the [Apache 2.0 License](./LICENSE). 27 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/model/QueryStatement.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.model 2 | 3 | import com.aerospike.client.Operation 4 | import com.aerospike.client.query.{Filter, PartitionFilter, Statement} 5 | 6 | case class QueryStatement( 7 | namespace: String, 8 | setName: Option[String] = None, 9 | binNames: Option[Seq[String]] = None, 10 | secondaryIndexFilter: Option[Filter] = None, 11 | partitionFilter: Option[PartitionFilter] = None, 12 | operations: Option[Seq[Operation]] = None, 13 | maxRecords: Option[Long] = None, 14 | recordsPerSecond: Option[Int] = None 15 | ) { 16 | lazy val statement: Statement = { 17 | val statement: Statement = new Statement 18 | statement.setNamespace(namespace) 19 | setName.foreach(statement.setSetName) 20 | binNames.foreach(bins => statement.setBinNames(bins: _*)) 21 | secondaryIndexFilter.foreach(statement.setFilter) 22 | operations.foreach(ops => statement.setOperations(ops.toArray)) 23 | maxRecords.foreach(statement.setMaxRecords) 24 | recordsPerSecond.foreach(statement.setRecordsPerSecond) 25 | statement 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/RecordSet.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.query.KeyRecord 4 | import com.aerospike.client.{Key, Record} 5 | import io.github.reugn.aerospike.scala.RecordSet.queueCapacity 6 | 7 | import java.io.Closeable 8 | import java.util.concurrent.{BlockingQueue, LinkedBlockingQueue} 9 | 10 | object RecordSet { 11 | private val END: KeyRecord = new KeyRecord(null, null) 12 | private val queueCapacity = 256 13 | 14 | private class RecordSetIterator private[RecordSet](val recordSet: RecordSet) 15 | extends Iterator[KeyRecord] with Closeable { 16 | 17 | private var more: Boolean = recordSet.next 18 | 19 | override def hasNext: Boolean = more 20 | 21 | override def next(): KeyRecord = { 22 | val keyRecord: KeyRecord = recordSet.record 23 | more = recordSet.next 24 | keyRecord 25 | } 26 | 27 | override def close(): Unit = { 28 | recordSet.close() 29 | } 30 | } 31 | } 32 | 33 | final class RecordSet extends Iterable[KeyRecord] with Closeable { 34 | private val queue: BlockingQueue[KeyRecord] = new LinkedBlockingQueue[KeyRecord](queueCapacity) 35 | private var record: KeyRecord = _ 36 | private var valid: Boolean = true 37 | @volatile private var exception: Exception = _ 38 | 39 | def next: Boolean = { 40 | if (valid) { 41 | try record = queue.take() 42 | catch { 43 | case _: InterruptedException => 44 | valid = false 45 | } 46 | if (record eq RecordSet.END) { 47 | if (exception != null) { 48 | throw exception 49 | } 50 | valid = false 51 | } 52 | } 53 | valid 54 | } 55 | 56 | def closeExceptionally(e: Exception): Unit = { 57 | exception = e 58 | close() 59 | } 60 | 61 | override def close(): Unit = { 62 | put(RecordSet.END) 63 | } 64 | 65 | override def iterator: Iterator[KeyRecord] = new RecordSet.RecordSetIterator(this) 66 | 67 | def getKey: Key = record.key 68 | 69 | def getRecord: Record = record.record 70 | 71 | def put(record: KeyRecord): Boolean = if (!valid) false 72 | else try { 73 | queue.put(record) 74 | true 75 | } catch { 76 | case _: InterruptedException => 77 | if (valid) abort() 78 | false 79 | } 80 | 81 | protected def abort(): Unit = { 82 | valid = false 83 | queue.clear() 84 | while (true) 85 | if (queue.offer(RecordSet.END) && queue.poll() == null) return 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/AerospikeClientBuilder.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client.metrics.MetricsPolicy 4 | import com.aerospike.client.policy.{AuthMode, ClientPolicy} 5 | import com.aerospike.client.{AerospikeClient, Host, IAerospikeClient} 6 | import com.typesafe.config.Config 7 | import io.github.reugn.aerospike.scala.AerospikeClientBuilder._ 8 | import io.github.reugn.aerospike.scala.Policies.ClientPolicyImplicits._ 9 | 10 | class AerospikeClientBuilder(config: Config, metricsPolicy: Option[MetricsPolicy]) { 11 | 12 | private def buildClientPolicy(): ClientPolicy = { 13 | val policy = new ClientPolicy(); 14 | Option(config.getString("aerospike.clientpolicy.user")).foreach(policy.user = _) 15 | Option(config.getString("aerospike.clientpolicy.password")).foreach(policy.password = _) 16 | Option(config.getString("aerospike.clientpolicy.clusterName")).foreach(policy.clusterName = _) 17 | Option(config.getString("aerospike.clientpolicy.authMode")) 18 | .foreach(mode => policy.authMode = AuthMode.valueOf(mode.toUpperCase)) 19 | Option(config.getInt("aerospike.clientpolicy.timeout")).foreach(policy.timeout = _) 20 | Option(config.getInt("aerospike.clientpolicy.loginTimeout")).foreach(policy.loginTimeout = _) 21 | Option(config.getInt("aerospike.clientpolicy.minConnsPerNode")).foreach(policy.minConnsPerNode = _) 22 | Option(config.getInt("aerospike.clientpolicy.maxConnsPerNode")).foreach(policy.maxConnsPerNode = _) 23 | Option(config.getInt("aerospike.clientpolicy.asyncMinConnsPerNode")).foreach(policy.asyncMinConnsPerNode = _) 24 | Option(config.getInt("aerospike.clientpolicy.asyncMaxConnsPerNode")).foreach(policy.asyncMaxConnsPerNode = _) 25 | Option(config.getInt("aerospike.clientpolicy.connPoolsPerNode")).foreach(policy.connPoolsPerNode = _) 26 | Option(config.getInt("aerospike.clientpolicy.maxSocketIdle")).foreach(policy.maxSocketIdle = _) 27 | Option(config.getInt("aerospike.clientpolicy.tendInterval")).foreach(policy.tendInterval = _) 28 | Option(config.getBoolean("aerospike.clientpolicy.failIfNotConnected")).foreach(policy.failIfNotConnected = _) 29 | Option(config.getBoolean("aerospike.clientpolicy.useServicesAlternate")).foreach(policy.useServicesAlternate = _) 30 | Option(config.getBoolean("aerospike.clientpolicy.forceSingleNode")).foreach(policy.forceSingleNode = _) 31 | Option(config.getBoolean("aerospike.clientpolicy.rackAware")).foreach(policy.rackAware = _) 32 | Option(config.getInt("aerospike.clientpolicy.rackId")).foreach(policy.rackId = _) 33 | policy.withEventLoops() 34 | } 35 | 36 | def build(): IAerospikeClient = { 37 | val client = Option(config.getString("aerospike.hostList")) map { 38 | hostList => 39 | new AerospikeClient(buildClientPolicy(), Host.parseHosts(hostList, defaultPort): _*) 40 | } getOrElse { 41 | val hostname = Option(config.getString("aerospike.hostname")).getOrElse(defaultHostName) 42 | val port = Option(config.getInt("aerospike.port")).getOrElse(defaultPort) 43 | new AerospikeClient(buildClientPolicy(), hostname, port) 44 | } 45 | metricsPolicy.foreach(policy => client.enableMetrics(policy)) 46 | client 47 | } 48 | } 49 | 50 | object AerospikeClientBuilder { 51 | private[aerospike] val defaultHostName = "localhost" 52 | private[aerospike] val defaultPort = 3000 53 | 54 | def apply(config: Config): AerospikeClientBuilder = new AerospikeClientBuilder(config, None) 55 | } 56 | -------------------------------------------------------------------------------- /aerospike-zio/src/test/scala/io/github/reugn/aerospike/scala/zioeffect/ZioAerospikeHandlerTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.zioeffect 2 | 3 | import com.aerospike.client._ 4 | import com.aerospike.client.exp.{Exp, ExpOperation, ExpReadFlags} 5 | import io.github.reugn.aerospike.scala.TestCommon 6 | import io.github.reugn.aerospike.scala.model.QueryStatement 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatest.matchers.should.Matchers 9 | import org.scalatest.{BeforeAndAfter, OptionValues} 10 | import zio.Runtime.{default => rt} 11 | import zio.{Unsafe, ZIO} 12 | 13 | class ZioAerospikeHandlerTest extends AnyFlatSpec 14 | with TestCommon with Matchers with BeforeAndAfter with OptionValues { 15 | 16 | private val client: ZioAerospikeHandler = ZioAerospikeHandler(hostname, port) 17 | override protected val set = "client_zio" 18 | 19 | behavior of "ZioAerospikeHandler" 20 | 21 | before { 22 | for (t <- populateKeys(client)) { 23 | unsafeRun(t) 24 | } 25 | } 26 | 27 | after { 28 | unsafeRun(deleteKeys(client)) 29 | } 30 | 31 | it should "get record properly" in { 32 | val t = client.get(keys(0)) 33 | val record = unsafeRun(t) 34 | record.getLong("intBin") shouldBe 0L 35 | } 36 | 37 | it should "get records properly" in { 38 | val t = client.getBatch(keys.toIndexedSeq) 39 | val records = unsafeRun(t) 40 | records.size shouldBe keys.length 41 | } 42 | 43 | it should "get records with read operations properly" in { 44 | val mulIntBin = "mulIntBin" 45 | val multiplier = 10L 46 | val mulExp = Exp.build(Exp.mul(Exp.intBin("intBin"), Exp.`val`(multiplier))) 47 | val t = client.getBatchOp(keys.toIndexedSeq, ExpOperation.read(mulIntBin, mulExp, ExpReadFlags.DEFAULT)) 48 | val records = unsafeRun(t) 49 | records.size shouldBe keys.length 50 | records.zipWithIndex.map { case (rec: Record, i: Int) => 51 | val expected = multiplier * i 52 | rec.getLong(mulIntBin) == expected 53 | } forall { 54 | _ == true 55 | } shouldBe true 56 | } 57 | 58 | it should "append bin properly" in { 59 | unsafeRun(client.append(keys(0), new Bin("strBin", "_"))) 60 | val record = unsafeRun(client.get(keys(0))) 61 | record.getString("strBin") shouldBe "str_0_" 62 | } 63 | 64 | it should "prepend bin properly" in { 65 | unsafeRun(client.prepend(keys(0), new Bin("strBin", "_"))) 66 | val record = unsafeRun(client.get(keys(0))) 67 | record.getString("strBin") shouldBe "_str_0" 68 | } 69 | 70 | it should "add bin properly" in { 71 | unsafeRun(client.add(keys(0), new Bin("intBin", 10))) 72 | val record = unsafeRun(client.get(keys(0))) 73 | record.getLong("intBin") shouldBe 10L 74 | } 75 | 76 | it should "delete record properly" in { 77 | val deleteResult = unsafeRun(client.delete(keys(0))) 78 | deleteResult shouldBe true 79 | val record = unsafeRun(client.get(keys(0))) 80 | record shouldBe null 81 | } 82 | 83 | it should "delete batch of records properly" in { 84 | val t = client.deleteBatch(keys.toSeq) 85 | val result = unsafeRun(t) 86 | result.status shouldBe true 87 | } 88 | 89 | it should "record to be exist" in { 90 | val result = unsafeRun(client.exists(keys(0))) 91 | result shouldBe true 92 | } 93 | 94 | it should "records to be exist" in { 95 | val result = unsafeRun(client.existsBatch(keys.toIndexedSeq)) 96 | result.forall(identity) shouldBe true 97 | } 98 | 99 | it should "operate bin properly" in { 100 | unsafeRun(client.operate(keys(0), Operation.put(new Bin("intBin", 100)))) 101 | val record = unsafeRun(client.get(keys(0))) 102 | record.getLong("intBin") shouldBe 100L 103 | } 104 | 105 | it should "operate batch of records properly" in { 106 | val t = client.operateBatch(keys.toSeq, 107 | Operation.put(new Bin("intBin", 100))) 108 | val result = unsafeRun(t) 109 | result.status shouldBe true 110 | } 111 | 112 | it should "operate list of BatchRecords properly" in { 113 | val records: Seq[BatchRecord] = 114 | List(new BatchWrite(keys(0), Array(Operation.put(new Bin("intBin", 100))))) ++ 115 | keys.slice(1, numberOfKeys).map(new BatchDelete(_)).toList 116 | val t = client.operateBatchRecord(records) 117 | val result = unsafeRun(t) 118 | result shouldBe true 119 | } 120 | 121 | it should "execute info command properly" in { 122 | val node = client.asJava.getCluster.getRandomNode 123 | val command = "namespaces" 124 | val t = client.info(node, command) 125 | val result = unsafeRun(t) 126 | result.get(command).value shouldBe namespace 127 | } 128 | 129 | it should "query all properly" in { 130 | val queryStatement = QueryStatement(namespace, setName = Some(set)) 131 | val t = client.query(queryStatement).runCollect 132 | unsafeRun(t).length shouldBe numberOfKeys 133 | } 134 | 135 | private def unsafeRun[E, A](task: ZIO[Any, E, A]): A = { 136 | Unsafe.unsafe { implicit unsafe => 137 | rt.unsafe.run(task).getOrThrowFiberFailure() 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/AsyncHandler.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import com.aerospike.client._ 4 | import com.aerospike.client.cluster.Node 5 | import com.aerospike.client.policy._ 6 | import com.aerospike.client.query.{KeyRecord, Statement} 7 | import com.aerospike.client.task.ExecuteTask 8 | 9 | import java.util.Calendar 10 | import scala.language.higherKinds 11 | 12 | trait AsyncHandler[F[_]] { 13 | 14 | protected def client: IAerospikeClient 15 | 16 | def asJava: IAerospikeClient = client 17 | 18 | //------------------------------------------------------- 19 | // Write Record Operations 20 | //------------------------------------------------------- 21 | 22 | def put(key: Key, bins: Bin*)(implicit policy: WritePolicy = null): F[Key] 23 | 24 | //------------------------------------------------------- 25 | // String Operations 26 | //------------------------------------------------------- 27 | 28 | def append(key: Key, bins: Bin*)(implicit policy: WritePolicy = null): F[Key] 29 | 30 | def prepend(key: Key, bins: Bin*)(implicit policy: WritePolicy = null): F[Key] 31 | 32 | //------------------------------------------------------- 33 | // Arithmetic Operations 34 | //------------------------------------------------------- 35 | 36 | def add(key: Key, bins: Bin*)(implicit policy: WritePolicy = null): F[Key] 37 | 38 | //------------------------------------------------------- 39 | // Delete Operations 40 | //------------------------------------------------------- 41 | 42 | def delete(key: Key)(implicit policy: WritePolicy = null): F[Boolean] 43 | 44 | def deleteBatch(keys: Seq[Key]) 45 | (implicit policy: BatchPolicy = null, batchDeletePolicy: BatchDeletePolicy = null): F[BatchResults] 46 | 47 | def truncate(ns: String, set: String, beforeLastUpdate: Option[Calendar] = None) 48 | (implicit policy: InfoPolicy = null): F[Unit] 49 | 50 | //------------------------------------------------------- 51 | // Touch Operations 52 | //------------------------------------------------------- 53 | 54 | def touch(key: Key)(implicit policy: WritePolicy = null): F[Key] 55 | 56 | //------------------------------------------------------- 57 | // Existence-Check Operations 58 | //------------------------------------------------------- 59 | 60 | def exists(key: Key)(implicit policy: Policy = null): F[Boolean] 61 | 62 | def existsBatch(keys: Seq[Key])(implicit policy: BatchPolicy = null): F[Seq[Boolean]] 63 | 64 | //------------------------------------------------------- 65 | // Read Record Operations 66 | //------------------------------------------------------- 67 | 68 | def get(key: Key, binNames: String*)(implicit policy: Policy = null): F[Record] 69 | 70 | def getBatch(keys: Seq[Key], binNames: String*)(implicit policy: BatchPolicy = null): F[Seq[Record]] 71 | 72 | def getBatchOp(keys: Seq[Key], operations: Operation*)(implicit policy: BatchPolicy = null): F[Seq[Record]] 73 | 74 | def getHeader(key: Key)(implicit policy: Policy = null): F[Record] 75 | 76 | def getHeaderBatch(keys: Seq[Key])(implicit policy: BatchPolicy = null): F[Seq[Record]] 77 | 78 | //------------------------------------------------------- 79 | // Generic Database Operations 80 | //------------------------------------------------------- 81 | 82 | def operate(key: Key, operations: Operation*)(implicit policy: WritePolicy = null): F[Record] 83 | 84 | def operateBatch(keys: Seq[Key], operations: Operation*) 85 | (implicit policy: BatchPolicy = null, batchWritePolicy: BatchWritePolicy = null): F[BatchResults] 86 | 87 | def operateBatchRecord(records: Seq[BatchRecord]) 88 | (implicit policy: BatchPolicy = null): F[Boolean] 89 | 90 | //------------------------------------------------------- 91 | // Multi-Record Transactions 92 | //------------------------------------------------------- 93 | 94 | def commit(txn: Txn): F[CommitStatus] 95 | 96 | def abort(txn: Txn): F[AbortStatus] 97 | 98 | //------------------------------------------------------- 99 | // Scan Operations 100 | //------------------------------------------------------- 101 | 102 | def scanNodeName(nodeName: String, ns: String, set: String, binNames: String*) 103 | (implicit policy: ScanPolicy = null): F[List[KeyRecord]] 104 | 105 | def scanNode(node: Node, ns: String, set: String, binNames: String*) 106 | (implicit policy: ScanPolicy = null): F[List[KeyRecord]] 107 | 108 | //---------------------------------------------------------- 109 | // Query/Execute 110 | //---------------------------------------------------------- 111 | 112 | def execute(statement: Statement, operations: Operation*) 113 | (implicit policy: WritePolicy = null): F[ExecuteTask] 114 | 115 | //-------------------------------------------------------- 116 | // Info 117 | //-------------------------------------------------------- 118 | 119 | def info(node: Node, commands: String*) 120 | (implicit policy: InfoPolicy = null): F[Map[String, String]] 121 | } 122 | -------------------------------------------------------------------------------- /aerospike-monix/src/test/scala/io/github/reugn/aerospike/scala/monixeffect/MonixAerospikeHandlerTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.monixeffect 2 | 3 | import com.aerospike.client._ 4 | import com.aerospike.client.exp.{Exp, ExpOperation, ExpReadFlags} 5 | import io.github.reugn.aerospike.scala.TestCommon 6 | import io.github.reugn.aerospike.scala.model.QueryStatement 7 | import monix.execution.Scheduler.Implicits.global 8 | import monix.reactive.Consumer 9 | import org.scalatest.flatspec.AsyncFlatSpec 10 | import org.scalatest.matchers.should.Matchers 11 | import org.scalatest.{BeforeAndAfter, OptionValues} 12 | 13 | import scala.concurrent.Await 14 | import scala.concurrent.duration.Duration 15 | 16 | class MonixAerospikeHandlerTest extends AsyncFlatSpec 17 | with TestCommon with Matchers with BeforeAndAfter with OptionValues { 18 | 19 | private val client: MonixAerospikeHandler = MonixAerospikeHandler(hostname, port) 20 | override protected val set = "client_monix" 21 | 22 | behavior of "MonixAerospikeHandler" 23 | 24 | before { 25 | for (t <- populateKeys(client)) { 26 | Await.result(t.runToFuture, Duration.Inf) 27 | } 28 | } 29 | 30 | after { 31 | Await.result(deleteKeys(client).runToFuture, Duration.Inf) 32 | } 33 | 34 | it should "get record properly" in { 35 | val t = client.get(keys(0)) 36 | val record = Await.result(t.runToFuture, Duration.Inf) 37 | record.getLong("intBin") shouldBe 0L 38 | } 39 | 40 | it should "get records properly" in { 41 | val t = client.getBatch(keys.toIndexedSeq) 42 | val records = Await.result(t.runToFuture, Duration.Inf) 43 | records.size shouldBe keys.length 44 | } 45 | 46 | it should "get records with read operations properly" in { 47 | val mulIntBin = "mulIntBin" 48 | val multiplier = 10L 49 | val mulExp = Exp.build(Exp.mul(Exp.intBin("intBin"), Exp.`val`(multiplier))) 50 | val t = client.getBatchOp(keys.toIndexedSeq, ExpOperation.read(mulIntBin, mulExp, ExpReadFlags.DEFAULT)) 51 | val records = Await.result(t.runToFuture, Duration.Inf) 52 | records.size shouldBe keys.length 53 | records.zipWithIndex.map { case (rec: Record, i: Int) => 54 | val expected = multiplier * i 55 | rec.getLong(mulIntBin) == expected 56 | } forall { 57 | _ == true 58 | } shouldBe true 59 | } 60 | 61 | it should "append bin properly" in { 62 | val t = client.append(keys(0), new Bin("strBin", "_")) 63 | Await.result(t.runToFuture, Duration.Inf) 64 | val record = Await.result(client.get(keys(0)).runToFuture, Duration.Inf) 65 | record.getString("strBin") shouldBe "str_0_" 66 | } 67 | 68 | it should "prepend bin properly" in { 69 | val t = client.prepend(keys(0), new Bin("strBin", "_")) 70 | Await.result(t.runToFuture, Duration.Inf) 71 | val record = Await.result(client.get(keys(0)).runToFuture, Duration.Inf) 72 | record.getString("strBin") shouldBe "_str_0" 73 | } 74 | 75 | it should "add bin properly" in { 76 | val t = client.add(keys(0), new Bin("intBin", 10)) 77 | Await.result(t.runToFuture, Duration.Inf) 78 | val record = Await.result(client.get(keys(0)).runToFuture, Duration.Inf) 79 | record.getLong("intBin") shouldBe 10L 80 | } 81 | 82 | it should "delete record properly" in { 83 | val deleteResult = Await.result(client.delete(keys(0)).runToFuture, Duration.Inf) 84 | deleteResult shouldBe true 85 | val record = Await.result(client.get(keys(0)).runToFuture, Duration.Inf) 86 | record shouldBe null 87 | } 88 | 89 | it should "delete batch of records properly" in { 90 | val t = client.deleteBatch(keys.toSeq) 91 | val result = Await.result(t.runToFuture, Duration.Inf) 92 | result.status shouldBe true 93 | } 94 | 95 | it should "record to be exist" in { 96 | val result = Await.result(client.exists(keys(0)).runToFuture, Duration.Inf) 97 | result shouldBe true 98 | } 99 | 100 | it should "records to be exist" in { 101 | val result = Await.result(client.existsBatch(keys.toIndexedSeq).runToFuture, Duration.Inf) 102 | result.forall(identity) shouldBe true 103 | } 104 | 105 | it should "operate bin properly" in { 106 | val t = client.operate(keys(0), Operation.put(new Bin("intBin", 100))) 107 | Await.result(t.runToFuture, Duration.Inf) 108 | val record = Await.result(client.get(keys(0)).runToFuture, Duration.Inf) 109 | record.getLong("intBin") shouldBe 100L 110 | } 111 | 112 | it should "operate batch of records properly" in { 113 | val t = client.operateBatch(keys.toSeq, 114 | Operation.put(new Bin("intBin", 100))) 115 | val result = Await.result(t.runToFuture, Duration.Inf) 116 | result.status shouldBe true 117 | } 118 | 119 | it should "execute info command properly" in { 120 | val node = client.asJava.getCluster.getRandomNode 121 | val command = "namespaces" 122 | val t = client.info(node, command) 123 | val result = Await.result(t.runToFuture, Duration.Inf) 124 | result.get(command).value shouldBe namespace 125 | } 126 | 127 | it should "operate list of BatchRecords properly" in { 128 | val records: Seq[BatchRecord] = 129 | List(new BatchWrite(keys(0), Array(Operation.put(new Bin("intBin", 100))))) ++ 130 | keys.slice(1, numberOfKeys).map(new BatchDelete(_)).toList 131 | val t = client.operateBatchRecord(records) 132 | val result = Await.result(t.runToFuture, Duration.Inf) 133 | result shouldBe true 134 | } 135 | 136 | it should "query all properly" in { 137 | val queryStatement = QueryStatement(namespace, setName = Some(set)) 138 | val observable = client.query(queryStatement) 139 | val t = observable.consumeWith(Consumer.toList.map(_.length)) 140 | Await.result(t.runToFuture, Duration.Inf) shouldBe numberOfKeys 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /aerospike-core/src/test/scala/io/github/reugn/aerospike/scala/AerospikeHandlerTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.Materializer 5 | import akka.stream.scaladsl.Sink 6 | import com.aerospike.client._ 7 | import com.aerospike.client.exp.{Exp, ExpOperation, ExpReadFlags} 8 | import com.aerospike.client.policy.BatchWritePolicy 9 | import com.aerospike.client.query.{Filter, KeyRecord} 10 | import io.github.reugn.aerospike.scala.model.QueryStatement 11 | import org.scalatest.flatspec.AsyncFlatSpec 12 | import org.scalatest.matchers.should.Matchers 13 | import org.scalatest.{BeforeAndAfter, FutureOutcome, OptionValues} 14 | 15 | import scala.concurrent.{ExecutionContext, Future} 16 | 17 | class AerospikeHandlerTest extends AsyncFlatSpec 18 | with TestCommon with Matchers with BeforeAndAfter with OptionValues { 19 | 20 | private implicit val actorSystem: ActorSystem = ActorSystem("test") 21 | private implicit val materializer: Materializer = Materializer(actorSystem) 22 | 23 | implicit override def executionContext: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global 24 | 25 | private val client: AerospikeHandler = AerospikeHandler(hostname, port) 26 | 27 | override def withFixture(test: NoArgAsyncTest) = new FutureOutcome(for { 28 | _ <- Future.sequence(populateKeys(client)) 29 | result <- super.withFixture(test).toFuture 30 | _ <- deleteKeys(client) 31 | } yield result) 32 | 33 | behavior of "AerospikeHandler" 34 | 35 | it should "get record properly" in { 36 | client.get(keys(0)) map { 37 | record => 38 | record.getLong("intBin") shouldBe 0L 39 | } 40 | } 41 | 42 | it should "get records properly" in { 43 | client.getBatch(keys.toIndexedSeq) map { 44 | _.size shouldBe keys.length 45 | } 46 | } 47 | 48 | it should "get records with read operations properly" in { 49 | val mulIntBin = "mulIntBin" 50 | val multiplier = 10L 51 | val mulExp = Exp.build(Exp.mul(Exp.intBin("intBin"), Exp.`val`(multiplier))) 52 | client.getBatchOp(keys, ExpOperation.read(mulIntBin, mulExp, ExpReadFlags.DEFAULT)) map { seq => 53 | seq.size shouldBe keys.length 54 | seq.zipWithIndex.map { case (rec: Record, i: Int) => 55 | val expected = multiplier * i 56 | rec.getLong(mulIntBin) == expected 57 | } 58 | } map { seq => 59 | seq.forall(_ == true) 60 | } map { 61 | _ shouldBe true 62 | } 63 | } 64 | 65 | it should "append bin properly" in { 66 | client.append(keys(0), new Bin("strBin", "_")) flatMap { 67 | _ => 68 | client.get(keys(0)) map { record => 69 | record.getString("strBin") shouldBe "str_0_" 70 | } 71 | } 72 | } 73 | 74 | it should "prepend bin properly" in { 75 | client.prepend(keys(0), new Bin("strBin", "_")) flatMap { 76 | _ => 77 | client.get(keys(0)) map { record => 78 | record.getString("strBin") shouldBe "_str_0" 79 | } 80 | } 81 | } 82 | 83 | it should "add bin properly" in { 84 | client.add(keys(0), new Bin("intBin", 10)) flatMap { 85 | _ => 86 | client.get(keys(0)) map { record => 87 | record.getLong("intBin") shouldBe 10L 88 | } 89 | } 90 | } 91 | 92 | it should "delete record properly" in { 93 | client.delete(keys(0)) flatMap { 94 | result => 95 | result shouldBe true 96 | client.get(keys(0)) map { record => 97 | record shouldBe null 98 | } 99 | } 100 | } 101 | 102 | it should "delete batch of records properly" in { 103 | client.deleteBatch(keys.toSeq) flatMap { 104 | result => 105 | result.status shouldBe true 106 | client.getBatch(keys.toSeq) map { seq => 107 | seq.filter(_ != null) 108 | } map { record => 109 | record shouldBe empty 110 | } 111 | } 112 | } 113 | 114 | it should "record to be exist" in { 115 | client.exists(keys(0)) map { 116 | result => 117 | result shouldBe true 118 | } 119 | } 120 | 121 | it should "records to be exist" in { 122 | client.existsBatch(keys.toIndexedSeq) map { 123 | result => 124 | result.forall(identity) shouldBe true 125 | } 126 | } 127 | 128 | it should "operate bin properly" in { 129 | client.operate(keys(0), Operation.put(new Bin("intBin", 100))) flatMap { 130 | _ => 131 | client.get(keys(0)) map { record => 132 | record.getLong("intBin") shouldBe 100L 133 | } 134 | } 135 | } 136 | 137 | it should "operate batch of records properly" in { 138 | implicit val bwp: BatchWritePolicy = new BatchWritePolicy 139 | bwp.expiration = -2 140 | client.operateBatch(keys.toSeq, 141 | Operation.put(new Bin("intBin", 100))) flatMap { 142 | batchResults => 143 | batchResults.status shouldBe true 144 | client.getBatch(keys) map { seq => 145 | seq.map { rec => 146 | rec.getLong("intBin") == 100L 147 | } 148 | } map { seq => 149 | seq.forall(_ == true) 150 | } map { 151 | _ shouldBe true 152 | } 153 | } 154 | } 155 | 156 | it should "operate list of BatchRecords properly" in { 157 | val records: Seq[BatchRecord] = 158 | List(new BatchWrite(keys(0), Array(Operation.put(new Bin("intBin", 100))))) ++ 159 | keys.slice(1, numberOfKeys).map(new BatchDelete(_)).toList 160 | client.operateBatchRecord(records) map { 161 | _ shouldBe true 162 | } 163 | Thread.sleep(100) 164 | client.getBatch(keys) map { res => 165 | res.filter(_ != null) 166 | } map { seq => 167 | seq.length shouldBe 1 168 | seq.head.getLong("intBin") 169 | } map { 170 | _ shouldBe 100L 171 | } 172 | } 173 | 174 | it should "execute info command properly" in { 175 | val node = client.asJava.getCluster.getRandomNode 176 | val command = "namespaces" 177 | client.info(node, command) map { result => 178 | result.get(command).value shouldBe namespace 179 | } 180 | } 181 | 182 | it should "scan nodes properly" in { 183 | Future.sequence(client.asJava.getCluster.validateNodes().toList map { node => 184 | client.scanNode(node, namespace, set) map { 185 | _.length 186 | } 187 | }).map(_.sum shouldBe numberOfKeys) 188 | } 189 | 190 | it should "scan nodes by name properly" in { 191 | Future.sequence(client.asJava.getCluster.validateNodes().toList map { node => 192 | client.scanNodeName(node.getName, namespace, set) map { 193 | _.length 194 | } 195 | }).map(_.sum shouldBe numberOfKeys) 196 | } 197 | 198 | it should "query all properly" in { 199 | val queryStatement = QueryStatement(namespace, setName = Some(set)) 200 | client.query(queryStatement).runWith(Sink.seq[KeyRecord]) map { 201 | _.length shouldBe numberOfKeys 202 | } 203 | } 204 | 205 | it should "fail on non-existent secondary index query" in { 206 | val queryStatement = QueryStatement( 207 | namespace, 208 | setName = Some(set), 209 | secondaryIndexFilter = Some(Filter.equal("bin1", 1)) 210 | ) 211 | assertThrows[AerospikeException] { 212 | client.query(queryStatement).runWith(Sink.seq[KeyRecord]) 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /aerospike-monix/src/main/scala/io/github/reugn/aerospike/scala/monixeffect/MonixAerospikeHandler.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.monixeffect 2 | 3 | import com.aerospike.client._ 4 | import com.aerospike.client.cluster.Node 5 | import com.aerospike.client.policy._ 6 | import com.aerospike.client.query.{KeyRecord, Statement} 7 | import com.aerospike.client.task.ExecuteTask 8 | import com.typesafe.config.Config 9 | import io.github.reugn.aerospike.scala._ 10 | import io.github.reugn.aerospike.scala.model.QueryStatement 11 | import monix.eval.Task 12 | import monix.reactive.Observable 13 | 14 | import java.util.Calendar 15 | import scala.collection.JavaConverters.seqAsJavaListConverter 16 | 17 | class MonixAerospikeHandler(protected val client: IAerospikeClient) 18 | extends AsyncHandler[Task] 19 | with StreamHandler1[Observable] { 20 | 21 | override def put(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 22 | Task(client.put(policy, key, bins: _*)).map(_ => key) 23 | } 24 | 25 | override def append(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 26 | Task(client.append(policy, key, bins: _*)).map(_ => key) 27 | } 28 | 29 | override def prepend(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 30 | Task(client.prepend(policy, key, bins: _*)).map(_ => key) 31 | } 32 | 33 | override def add(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 34 | Task(client.add(policy, key, bins: _*)).map(_ => key) 35 | } 36 | 37 | override def delete(key: Key)(implicit policy: WritePolicy): Task[Boolean] = { 38 | Task(client.delete(policy, key)) 39 | } 40 | 41 | override def deleteBatch(keys: Seq[Key]) 42 | (implicit policy: BatchPolicy, batchDeletePolicy: BatchDeletePolicy): Task[BatchResults] = { 43 | Task(client.delete(policy, batchDeletePolicy, keys.toArray)) 44 | } 45 | 46 | override def truncate(ns: String, set: String, beforeLastUpdate: Option[Calendar] = None) 47 | (implicit policy: InfoPolicy): Task[Unit] = { 48 | Task(client.truncate(policy, ns, set, beforeLastUpdate.orNull)) 49 | } 50 | 51 | override def touch(key: Key)(implicit policy: WritePolicy): Task[Key] = { 52 | Task(client.touch(policy, key)).map(_ => key) 53 | } 54 | 55 | override def exists(key: Key)(implicit policy: Policy): Task[Boolean] = { 56 | Task(client.exists(policy, key)) 57 | } 58 | 59 | override def existsBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Task[Seq[Boolean]] = { 60 | Task(client.exists(policy, keys.toArray)).map(_.toIndexedSeq) 61 | } 62 | 63 | override def get(key: Key, binNames: String*)(implicit policy: Policy): Task[Record] = { 64 | Task { 65 | if (binNames.toArray.length > 0) 66 | client.get(policy, key, binNames: _*) 67 | else 68 | client.get(policy, key) 69 | } 70 | } 71 | 72 | override def getBatch(keys: Seq[Key], binNames: String*)(implicit policy: BatchPolicy): Task[Seq[Record]] = { 73 | Task { 74 | if (binNames.toArray.length > 0) 75 | client.get(policy, keys.toArray, binNames: _*) 76 | else 77 | client.get(policy, keys.toArray) 78 | } map { 79 | _.toIndexedSeq 80 | } 81 | } 82 | 83 | override def getBatchOp(keys: Seq[Key], operations: Operation*)(implicit policy: BatchPolicy): Task[Seq[Record]] = { 84 | Task(client.get(policy, keys.toArray, operations: _*)) 85 | } 86 | 87 | override def getHeader(key: Key)(implicit policy: Policy): Task[Record] = { 88 | Task(client.getHeader(policy, key)) 89 | } 90 | 91 | override def getHeaderBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Task[Seq[Record]] = { 92 | Task(client.getHeader(policy, keys.toArray)).map(_.toIndexedSeq) 93 | } 94 | 95 | override def operate(key: Key, operations: Operation*)(implicit policy: WritePolicy): Task[Record] = { 96 | Task(client.operate(policy, key, operations: _*)) 97 | } 98 | 99 | override def operateBatch(keys: Seq[Key], operations: Operation*) 100 | (implicit policy: BatchPolicy, batchWritePolicy: BatchWritePolicy): Task[BatchResults] = { 101 | Task(client.operate(policy, batchWritePolicy, keys.toArray, operations: _*)) 102 | } 103 | 104 | override def operateBatchRecord(records: Seq[BatchRecord])(implicit policy: BatchPolicy): Task[Boolean] = { 105 | Task(client.operate(policy, records.asJava)) 106 | } 107 | 108 | override def commit(txn: Txn): Task[CommitStatus] = { 109 | Task(client.commit(txn)) 110 | } 111 | 112 | override def abort(txn: Txn): Task[AbortStatus] = { 113 | Task(client.abort(txn)) 114 | } 115 | 116 | override def scanNodeName(nodeName: String, ns: String, set: String, binNames: String*) 117 | (implicit policy: ScanPolicy): Task[List[KeyRecord]] = { 118 | Task { 119 | val callback = RecordScanCallback() 120 | client.scanNode(policy, nodeName, ns, set, callback, binNames: _*) 121 | callback.getRecordSet 122 | } 123 | } 124 | 125 | override def scanNode(node: Node, ns: String, set: String, binNames: String*) 126 | (implicit policy: ScanPolicy): Task[List[KeyRecord]] = { 127 | Task { 128 | val callback = RecordScanCallback() 129 | client.scanNode(policy, node, ns, set, callback, binNames: _*) 130 | callback.getRecordSet 131 | } 132 | } 133 | 134 | override def execute(statement: Statement, operations: Operation*) 135 | (implicit policy: WritePolicy): Task[ExecuteTask] = { 136 | Task(client.execute(policy, statement, operations: _*)) 137 | } 138 | 139 | override def info(node: Node, commands: String*)(implicit policy: InfoPolicy): Task[Map[String, String]] = { 140 | Task(Info.request(policy, node, commands: _*)) map { 141 | import scala.collection.JavaConverters._ 142 | _.asScala.toMap 143 | } 144 | } 145 | 146 | override def query(statement: QueryStatement) 147 | (implicit policy: QueryPolicy): Observable[KeyRecord] = { 148 | val listener = new QueryRecordSequenceListener 149 | statement.partitionFilter match { 150 | case Some(partitionFilter) => 151 | client.queryPartitions(null, listener, policy, statement.statement, partitionFilter) 152 | case None => 153 | client.query(null, listener, policy, statement.statement) 154 | } 155 | Observable.fromIterator(Task(listener.getRecordSet.iterator)) 156 | } 157 | } 158 | 159 | object MonixAerospikeHandler { 160 | 161 | import io.github.reugn.aerospike.scala.Policies.ClientPolicyImplicits._ 162 | 163 | def apply(client: IAerospikeClient): MonixAerospikeHandler = 164 | new MonixAerospikeHandler(client) 165 | 166 | def apply(config: Config): MonixAerospikeHandler = 167 | new MonixAerospikeHandler(AerospikeClientBuilder(config).build()) 168 | 169 | def apply(hostname: String, port: Int): MonixAerospikeHandler = 170 | apply(new ClientPolicy(), hostname, port) 171 | 172 | def apply(policy: ClientPolicy, hostname: String, port: Int): MonixAerospikeHandler = 173 | new MonixAerospikeHandler(new AerospikeClient(policy.withEventLoops(), hostname, port)) 174 | 175 | def apply(policy: ClientPolicy, hosts: Seq[Host]): MonixAerospikeHandler = 176 | new MonixAerospikeHandler(new AerospikeClient(policy.withEventLoops(), hosts: _*)) 177 | } 178 | -------------------------------------------------------------------------------- /aerospike-zio/src/main/scala/io/github/reugn/aerospike/scala/zioeffect/ZioAerospikeHandler.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala.zioeffect 2 | 3 | import com.aerospike.client._ 4 | import com.aerospike.client.cluster.Node 5 | import com.aerospike.client.policy._ 6 | import com.aerospike.client.query.{KeyRecord, Statement} 7 | import com.aerospike.client.task.ExecuteTask 8 | import com.typesafe.config.Config 9 | import io.github.reugn.aerospike.scala._ 10 | import io.github.reugn.aerospike.scala.model.QueryStatement 11 | import zio.stream.ZStream 12 | import zio.{Task, ZIO} 13 | 14 | import java.util.Calendar 15 | import scala.collection.JavaConverters.seqAsJavaListConverter 16 | 17 | class ZioAerospikeHandler(protected val client: IAerospikeClient) 18 | extends AsyncHandler[Task] 19 | with StreamHandler3[ZStream] { 20 | 21 | override def put(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 22 | ZIO.attemptBlocking(client.put(policy, key, bins: _*)).map(_ => key) 23 | } 24 | 25 | override def append(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 26 | ZIO.attemptBlocking(client.append(policy, key, bins: _*)).map(_ => key) 27 | } 28 | 29 | override def prepend(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 30 | ZIO.attemptBlocking(client.prepend(policy, key, bins: _*)).map(_ => key) 31 | } 32 | 33 | override def add(key: Key, bins: Bin*)(implicit policy: WritePolicy): Task[Key] = { 34 | ZIO.attemptBlocking(client.add(policy, key, bins: _*)).map(_ => key) 35 | } 36 | 37 | override def delete(key: Key)(implicit policy: WritePolicy): Task[Boolean] = { 38 | ZIO.attemptBlocking(client.delete(policy, key)) 39 | } 40 | 41 | override def deleteBatch(keys: Seq[Key]) 42 | (implicit policy: BatchPolicy, batchDeletePolicy: BatchDeletePolicy): Task[BatchResults] = { 43 | ZIO.attemptBlocking(client.delete(policy, batchDeletePolicy, keys.toArray)) 44 | } 45 | 46 | override def truncate(ns: String, set: String, beforeLastUpdate: Option[Calendar] = None) 47 | (implicit policy: InfoPolicy): Task[Unit] = { 48 | ZIO.attemptBlocking(client.truncate(policy, ns, set, beforeLastUpdate.orNull)) 49 | } 50 | 51 | override def touch(key: Key)(implicit policy: WritePolicy): Task[Key] = { 52 | ZIO.attemptBlocking(client.touch(policy, key)).map(_ => key) 53 | } 54 | 55 | override def exists(key: Key)(implicit policy: Policy): Task[Boolean] = { 56 | ZIO.attemptBlocking(client.exists(policy, key)) 57 | } 58 | 59 | override def existsBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Task[Seq[Boolean]] = { 60 | ZIO.attemptBlocking(client.exists(policy, keys.toArray)).map(_.toIndexedSeq) 61 | } 62 | 63 | override def get(key: Key, binNames: String*)(implicit policy: Policy): Task[Record] = { 64 | ZIO.attemptBlocking { 65 | if (binNames.toArray.length > 0) 66 | client.get(policy, key, binNames: _*) 67 | else 68 | client.get(policy, key) 69 | } 70 | } 71 | 72 | override def getBatch(keys: Seq[Key], binNames: String*)(implicit policy: BatchPolicy): Task[Seq[Record]] = { 73 | ZIO.attemptBlocking { 74 | if (binNames.toArray.length > 0) 75 | client.get(policy, keys.toArray, binNames: _*) 76 | else 77 | client.get(policy, keys.toArray) 78 | } map { 79 | _.toIndexedSeq 80 | } 81 | } 82 | 83 | override def getBatchOp(keys: Seq[Key], operations: Operation*)(implicit policy: BatchPolicy): Task[Seq[Record]] = { 84 | ZIO.attemptBlocking(client.get(policy, keys.toArray, operations: _*)) 85 | } 86 | 87 | override def getHeader(key: Key)(implicit policy: Policy): Task[Record] = { 88 | ZIO.attemptBlocking(client.getHeader(policy, key)) 89 | } 90 | 91 | override def getHeaderBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Task[Seq[Record]] = { 92 | ZIO.attemptBlocking(client.getHeader(policy, keys.toArray)).map(_.toIndexedSeq) 93 | } 94 | 95 | override def operate(key: Key, operations: Operation*)(implicit policy: WritePolicy): Task[Record] = { 96 | ZIO.attemptBlocking(client.operate(policy, key, operations: _*)) 97 | } 98 | 99 | override def operateBatch(keys: Seq[Key], operations: Operation*) 100 | (implicit policy: BatchPolicy, batchWritePolicy: BatchWritePolicy): Task[BatchResults] = { 101 | ZIO.attemptBlocking(client.operate(policy, batchWritePolicy, keys.toArray, operations: _*)) 102 | } 103 | 104 | override def operateBatchRecord(records: Seq[BatchRecord])(implicit policy: BatchPolicy): Task[Boolean] = { 105 | ZIO.attemptBlocking(client.operate(policy, records.asJava)) 106 | } 107 | 108 | override def commit(txn: Txn): Task[CommitStatus] = { 109 | ZIO.attemptBlocking(client.commit(txn)) 110 | } 111 | 112 | override def abort(txn: Txn): Task[AbortStatus] = { 113 | ZIO.attemptBlocking(client.abort(txn)) 114 | } 115 | 116 | override def scanNodeName(nodeName: String, ns: String, set: String, binNames: String*) 117 | (implicit policy: ScanPolicy): Task[List[KeyRecord]] = { 118 | ZIO.attemptBlocking { 119 | val callback = RecordScanCallback() 120 | client.scanNode(policy, nodeName, ns, set, callback, binNames: _*) 121 | callback.getRecordSet 122 | } 123 | } 124 | 125 | override def scanNode(node: Node, ns: String, set: String, binNames: String*) 126 | (implicit policy: ScanPolicy): Task[List[KeyRecord]] = { 127 | ZIO.attemptBlocking { 128 | val callback = RecordScanCallback() 129 | client.scanNode(policy, node, ns, set, callback, binNames: _*) 130 | callback.getRecordSet 131 | } 132 | } 133 | 134 | override def execute(statement: Statement, operations: Operation*) 135 | (implicit policy: WritePolicy): Task[ExecuteTask] = { 136 | ZIO.attemptBlocking(client.execute(policy, statement, operations: _*)) 137 | } 138 | 139 | override def info(node: Node, commands: String*)(implicit policy: InfoPolicy): Task[Map[String, String]] = { 140 | ZIO.attemptBlocking(Info.request(policy, node, commands: _*)) map { 141 | import scala.collection.JavaConverters._ 142 | _.asScala.toMap 143 | } 144 | } 145 | 146 | override def query(statement: QueryStatement) 147 | (implicit policy: QueryPolicy): ZStream[Any, Throwable, KeyRecord] = { 148 | val listener = new QueryRecordSequenceListener 149 | statement.partitionFilter match { 150 | case Some(partitionFilter) => 151 | client.queryPartitions(null, listener, policy, statement.statement, partitionFilter) 152 | case None => 153 | client.query(null, listener, policy, statement.statement) 154 | } 155 | ZStream.fromIterator(listener.getRecordSet.iterator) 156 | } 157 | } 158 | 159 | object ZioAerospikeHandler { 160 | 161 | import Policies.ClientPolicyImplicits._ 162 | 163 | def apply(client: IAerospikeClient): ZioAerospikeHandler = 164 | new ZioAerospikeHandler(client) 165 | 166 | def apply(config: Config): ZioAerospikeHandler = 167 | new ZioAerospikeHandler(AerospikeClientBuilder(config).build()) 168 | 169 | def apply(hostname: String, port: Int): ZioAerospikeHandler = 170 | apply(new ClientPolicy(), hostname, port) 171 | 172 | def apply(policy: ClientPolicy, hostname: String, port: Int): ZioAerospikeHandler = 173 | new ZioAerospikeHandler(new AerospikeClient(policy.withEventLoops(), hostname, port)) 174 | 175 | def apply(policy: ClientPolicy, hosts: Seq[Host]): ZioAerospikeHandler = 176 | new ZioAerospikeHandler(new AerospikeClient(policy.withEventLoops(), hosts: _*)) 177 | } 178 | -------------------------------------------------------------------------------- /aerospike-core/src/main/scala/io/github/reugn/aerospike/scala/AerospikeHandler.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.aerospike.scala 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Source 5 | import com.aerospike.client._ 6 | import com.aerospike.client.cluster.Node 7 | import com.aerospike.client.policy._ 8 | import com.aerospike.client.query.{KeyRecord, Statement} 9 | import com.aerospike.client.task.ExecuteTask 10 | import com.typesafe.config.Config 11 | import io.github.reugn.aerospike.scala.listener._ 12 | import io.github.reugn.aerospike.scala.model.QueryStatement 13 | 14 | import java.util.Calendar 15 | import scala.collection.JavaConverters.seqAsJavaListConverter 16 | import scala.concurrent.{ExecutionContext, Future} 17 | 18 | class AerospikeHandler(protected val client: IAerospikeClient)(implicit ec: ExecutionContext) 19 | extends AsyncHandler[Future] 20 | with StreamHandler2[Source] { 21 | 22 | override def put(key: Key, bins: Bin*)(implicit policy: WritePolicy): Future[Key] = { 23 | val listener = new ScalaWriteListener 24 | client.put(null, listener, policy, key, bins: _*) 25 | listener.future 26 | } 27 | 28 | override def append(key: Key, bins: Bin*)(implicit policy: WritePolicy): Future[Key] = { 29 | val listener = new ScalaWriteListener 30 | client.append(null, listener, policy, key, bins: _*) 31 | listener.future 32 | } 33 | 34 | override def prepend(key: Key, bins: Bin*)(implicit policy: WritePolicy): Future[Key] = { 35 | val listener = new ScalaWriteListener 36 | client.prepend(null, listener, policy, key, bins: _*) 37 | listener.future 38 | } 39 | 40 | override def add(key: Key, bins: Bin*)(implicit policy: WritePolicy): Future[Key] = { 41 | val listener = new ScalaWriteListener 42 | client.add(null, listener, policy, key, bins: _*) 43 | listener.future 44 | } 45 | 46 | override def delete(key: Key)(implicit policy: WritePolicy): Future[Boolean] = { 47 | val listener = new ScalaDeleteListener 48 | client.delete(null, listener, policy, key) 49 | listener.future 50 | } 51 | 52 | override def deleteBatch(keys: Seq[Key]) 53 | (implicit policy: BatchPolicy, batchDeletePolicy: BatchDeletePolicy): Future[BatchResults] = { 54 | val listener = new ScalaBatchRecordArrayListener 55 | client.delete(null, listener, policy, batchDeletePolicy, keys.toArray) 56 | listener.future 57 | } 58 | 59 | override def truncate(ns: String, set: String, beforeLastUpdate: Option[Calendar] = None) 60 | (implicit policy: InfoPolicy): Future[Unit] = { 61 | Future(client.truncate(policy, ns, set, beforeLastUpdate.orNull)) 62 | } 63 | 64 | override def touch(key: Key)(implicit policy: WritePolicy): Future[Key] = { 65 | val listener = new ScalaWriteListener 66 | client.touch(null, listener, policy, key) 67 | listener.future 68 | } 69 | 70 | override def exists(key: Key)(implicit policy: Policy): Future[Boolean] = { 71 | val listener = new ScalaExistsListener 72 | client.exists(null, listener, policy, key) 73 | listener.future 74 | } 75 | 76 | override def existsBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Future[Seq[Boolean]] = { 77 | val listener = new ScalaExistsArrayListener 78 | client.exists(null, listener, policy, keys.toArray) 79 | listener.future 80 | } 81 | 82 | override def get(key: Key, binNames: String*)(implicit policy: Policy): Future[Record] = { 83 | val listener = new ScalaRecordListener 84 | if (binNames.toArray.length > 0) 85 | client.get(null, listener, policy, key, binNames: _*) 86 | else 87 | client.get(null, listener, policy, key) 88 | listener.future 89 | } 90 | 91 | override def getBatch(keys: Seq[Key], binNames: String*)(implicit policy: BatchPolicy): Future[Seq[Record]] = { 92 | val listener = new ScalaRecordArrayListener 93 | if (binNames.toArray.length > 0) 94 | client.get(null, listener, policy, keys.toArray, binNames: _*) 95 | else 96 | client.get(null, listener, policy, keys.toArray) 97 | listener.future 98 | } 99 | 100 | override def getBatchOp(keys: Seq[Key], operations: Operation*)(implicit policy: BatchPolicy): Future[Seq[Record]] = { 101 | val listener = new ScalaRecordArrayListener 102 | client.get(null, listener, policy, keys.toArray, operations: _*) 103 | listener.future 104 | } 105 | 106 | override def getHeader(key: Key)(implicit policy: Policy): Future[Record] = { 107 | val listener = new ScalaRecordListener 108 | client.getHeader(null, listener, policy, key) 109 | listener.future 110 | } 111 | 112 | override def getHeaderBatch(keys: Seq[Key])(implicit policy: BatchPolicy): Future[Seq[Record]] = { 113 | val listener = new ScalaRecordArrayListener 114 | client.getHeader(null, listener, policy, keys.toArray) 115 | listener.future 116 | } 117 | 118 | override def operate(key: Key, operations: Operation*)(implicit policy: WritePolicy): Future[Record] = { 119 | val listener = new ScalaRecordListener 120 | client.operate(null, listener, policy, key, operations: _*) 121 | listener.future 122 | } 123 | 124 | override def operateBatch(keys: Seq[Key], operations: Operation*) 125 | (implicit policy: BatchPolicy, batchWritePolicy: BatchWritePolicy): Future[BatchResults] = { 126 | val listener = new ScalaBatchRecordArrayListener 127 | client.operate(null, listener, policy, batchWritePolicy, keys.toArray, operations: _*) 128 | listener.future 129 | } 130 | 131 | override def operateBatchRecord(records: Seq[BatchRecord])(implicit policy: BatchPolicy): Future[Boolean] = { 132 | val listener = new ScalaBatchOperateListListener 133 | client.operate(null, listener, policy, records.asJava) 134 | listener.future 135 | } 136 | 137 | override def commit(txn: Txn): Future[CommitStatus] = { 138 | val listener = new ScalaCommitListener 139 | client.commit(null, listener, txn) 140 | listener.future 141 | } 142 | 143 | override def abort(txn: Txn): Future[AbortStatus] = { 144 | val listener = new ScalaAbortListener 145 | client.abort(null, listener, txn) 146 | listener.future 147 | } 148 | 149 | override def execute(statement: Statement, operations: Operation*) 150 | (implicit policy: WritePolicy): Future[ExecuteTask] = { 151 | Future(client.execute(policy, statement, operations: _*)) 152 | } 153 | 154 | override def info(node: Node, commands: String*)(implicit policy: InfoPolicy): Future[Map[String, String]] = { 155 | val listener = new ScalaInfoListener 156 | client.info(null, listener, policy, node, commands: _*) 157 | listener.future 158 | } 159 | 160 | override def scanNodeName(nodeName: String, ns: String, set: String, binNames: String*) 161 | (implicit policy: ScanPolicy): Future[List[KeyRecord]] = { 162 | Future { 163 | val callback = RecordScanCallback() 164 | client.scanNode(policy, nodeName, ns, set, callback, binNames: _*) 165 | callback.getRecordSet 166 | } 167 | } 168 | 169 | override def scanNode(node: Node, ns: String, set: String, binNames: String*) 170 | (implicit policy: ScanPolicy): Future[List[KeyRecord]] = { 171 | Future { 172 | val callback = RecordScanCallback() 173 | client.scanNode(policy, node, ns, set, callback, binNames: _*) 174 | callback.getRecordSet 175 | } 176 | } 177 | 178 | override def query(statement: QueryStatement) 179 | (implicit policy: QueryPolicy): Source[KeyRecord, NotUsed] = { 180 | val listener = new QueryRecordSequenceListener 181 | statement.partitionFilter match { 182 | case Some(partitionFilter) => 183 | client.queryPartitions(null, listener, policy, statement.statement, partitionFilter) 184 | case None => 185 | client.query(null, listener, policy, statement.statement) 186 | } 187 | Source.fromGraph(new KeyRecordSource(listener.getRecordSet.iterator)) 188 | } 189 | } 190 | 191 | object AerospikeHandler { 192 | 193 | import Policies.ClientPolicyImplicits._ 194 | 195 | def apply(client: IAerospikeClient)(implicit ec: ExecutionContext): AerospikeHandler = 196 | new AerospikeHandler(client) 197 | 198 | def apply(config: Config)(implicit ec: ExecutionContext): AerospikeHandler = 199 | new AerospikeHandler(AerospikeClientBuilder(config).build()) 200 | 201 | def apply(hostname: String, port: Int)(implicit ec: ExecutionContext): AerospikeHandler = 202 | apply(new ClientPolicy(), hostname, port) 203 | 204 | def apply(policy: ClientPolicy, hostname: String, port: Int)(implicit ec: ExecutionContext): AerospikeHandler = 205 | new AerospikeHandler(new AerospikeClient(policy.withEventLoops(), hostname, port)) 206 | 207 | def apply(policy: ClientPolicy, hosts: Seq[Host])(implicit ec: ExecutionContext): AerospikeHandler = 208 | new AerospikeHandler(new AerospikeClient(policy.withEventLoops(), hosts: _*)) 209 | } 210 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------