{"id":15056897,"url":"https://github.com/ringcentral/cassandra4io","last_synced_at":"2025-04-09T13:11:23.254Z","repository":{"id":43036509,"uuid":"317964816","full_name":"ringcentral/cassandra4io","owner":"ringcentral","description":"Asynchronous lightweight fs2 and cats.effect.IO wrapper under datastax cassandra 4.x driver with doobie-like syntax","archived":false,"fork":false,"pushed_at":"2024-12-05T12:13:47.000Z","size":193,"stargazers_count":54,"open_issues_count":12,"forks_count":14,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-01T20:43:37.607Z","etag":null,"topics":["cassandra","fs2","non-blocking"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ringcentral.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-02T19:09:14.000Z","updated_at":"2024-12-05T12:08:27.000Z","dependencies_parsed_at":"2023-10-16T19:18:54.329Z","dependency_job_id":"262b94c7-a1ca-4571-ad88-8c589fe39230","html_url":"https://github.com/ringcentral/cassandra4io","commit_stats":{"total_commits":122,"total_committers":14,"mean_commits":8.714285714285714,"dds":0.7540983606557377,"last_synced_commit":"48a0c5746253ff387d61a19cfd9a25558f0c1a33"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ringcentral%2Fcassandra4io","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ringcentral%2Fcassandra4io/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ringcentral%2Fcassandra4io/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ringcentral%2Fcassandra4io/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ringcentral","download_url":"https://codeload.github.com/ringcentral/cassandra4io/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248045266,"owners_count":21038555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cassandra","fs2","non-blocking"],"created_at":"2024-09-24T21:57:56.013Z","updated_at":"2025-04-09T13:11:23.226Z","avatar_url":"https://github.com/ringcentral.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cassandra 4 io\n\n![CI](https://github.com/ringcentral/cassandra4io/workflows/CI/badge.svg?branch=main)\n![Maven Central](https://img.shields.io/maven-central/v/com.ringcentral/cassandra4io_2.13)\n\nThis is lightweight cats-effect and fs2 IO wrapper for latest datastax 4.x driver.\n\nWhy 4.x?\n \n4.x was re-written in immutable first design, within async first API, \noptimizations, fewer allocations, metrics improvements, and fully compatible with cassandra 3.x\n\n## Goals\n- Be safe, type-safe.\n- Be fast \n    - use minimal allocations\n    - minimize resources and abstractions overhead over original datastax driver, which is good\n\n\n## How to use\nCassandra4io is currently available for Scala 2.13 and 2.12.\n\n### Add a dependency to your project\n```scala\nlibraryDependencies += (\"com.ringcentral\" %% \"cassandra4io\" % \"0.1.14\")\n```\n\n### Create a connection to Cassandra\n```scala\nimport com.ringcentral.cassandra4io.CassandraSession\n\nimport com.datastax.oss.driver.api.core.CqlSession\nimport cats.effect._\n\nimport java.net.InetSocketAddress\n\nval builder = CqlSession\n      .builder()\n      .addContactPoint(InetSocketAddress.createUnresolved(\"localhost\", 9042))\n      .withLocalDatacenter(\"datacenter1\")\n      .withKeyspace(\"awesome\") \n\ndef makeSession[F[_]: Async]: Resource[F, CassandraSession[F]] =\n  CassandraSession.connect(builder)\n```\n\n### Write some requests\n\npackage `com.ringcentral.cassandra4io.cql` introduces typed way to deal with cql queries\n\n### Simple syntax\n\n```scala\nimport cats.effect.Sync\nimport cats.syntax.all._\nimport com.datastax.oss.driver.api.core.ConsistencyLevel\nimport com.ringcentral.cassandra4io.CassandraSession\nimport com.ringcentral.cassandra4io.cql._\n\ncase class Model(id: Int, data: String)\n\ntrait Dao[F[_]] {\n  def put(value: Model): F[Unit]\n  def get(id: Int): F[Option[Model]]\n}\n\nclass DaoImpl[F[_]: Async](session: CassandraSession[F]) extends Dao[F] {\n\n  private def insertQuery(value: Model) =\n    cql\"insert into table (id, data) values (${value.id}, ${value.data})\"\n      .config(_.setConsistencyLevel(ConsistencyLevel.ALL))\n\n  private def selectQuery(id: Int) =\n    cql\"select id, data from table where id = $id\".as[Model]\n  \n  override def put(value: Model) = insertQuery(value).execute(session).void\n  override def get(id: Int) = selectQuery(id).select(session).head.compile.last\n}\n```\n\nthis syntax reuse implicit driver prepared statements cache\n  \n### Templated syntax\n\n```scala\nimport cats.effect._\nimport scala.concurrent.duration._\nimport cats.syntax.all._\nimport scala.jdk.DurationConverters._\nimport com.datastax.oss.driver.api.core.ConsistencyLevel\nimport com.ringcentral.cassandra4io.CassandraSession\nimport com.ringcentral.cassandra4io.cql._\n\ncase class Model(pk: Long, ck: String, data: String, metaData: String)\ncase class Key(pk: Long, ck: String)\ncase class Data(data: String, metaData: String)\n\ntrait Dao[F[_]] {\n  def put(value: Model): F[Unit]\n  def update(key: Key, data: Data): F[Unit]\n  def get(key: Key): F[Option[Model]]\n}\n\nobject Dao {\n\n  private val tableName = \"table\"\n  private val insertQuery =\n    cqlt\"insert into ${Const(tableName)} (pk, ck, data, meta_data) values (${Put[Long]}, ${Put[String]}, ${Put[String]}, ${Put[String]})\"\n      .config(_.setTimeout(1.second.toJava))\n  private val insertQueryAlternative =\n    cqlt\"insert into ${Const(tableName)} (${Columns[Model]}) values (${Values[Model]})\"\n  private val updateQuery = cqlt\"update ${Const(tableName)} set ${Assignment[Data]} where ${EqualsTo[Key]}\"\n  private val selectQuery = cqlt\"select ${Columns[Model]} from ${Const(tableName)} where ${EqualsTo[Key]}\".as[Model]\n\n  def apply[F[_] : Async](session: CassandraSession[F]) = for {\n    insert \u003c- insertQuery.prepare(session)\n    update \u003c- updateQuery.prepare(session)\n    insertAlternative \u003c- insertQueryAlternative.prepare(session)\n    select \u003c- selectQuery.prepare(session)\n  } yield new Dao[F] {\n    override def put(value: Model) = insert(\n      value.pk,\n      value.ck,\n      value.data,\n      value.metaData\n    ).execute.void // insertAlternative(value).execute.void\n    override def update(key: Key, data: Data): F[Unit] = updateQuery(data, key).execute.void\n    override def get(key: Key) = select(key).config(_.setExecutionProfileName(\"default\")).select.head.compile.last\n  }\n}\n```\nAs you can see `${Columns[Model]}` expands to `pk, ck, data, meta_data`, `${Values[Model]}` to `?, ?, ?, ?`, `${Assignment[Data]}` to `data = ?, meta_data = ?` and `${EqualsTo[Key]}` expands to `pk = ? and ck = ?`.\nLatter three types adjust query type as well for being able to bind corresponding values \n\n### Handling optional fields (`null`)\n\nBy default, cassandra4io encodes `Option` as a `null` value. Which is ok for most cases. But in Cassandra, there is a difference between a `null` value and an empty value. In java driver this difference is represented by `BoundStatement#setToNull` (default behavior) and `BoundStatement#unset` (setting an empty field). The main advantage of using `unset` instead of `setToNull` is that tombstone will not be created for an empty field.\n\nTo use the `unset` instead of the `setToNull` for your optional value in a `cql` interpolators you could add `.usingUnset` to your optional value. Like in the following example:\n```scala\nimport com.ringcentral.cassandra4io.cql._\n\ncql\"insert into entities(foo, bar, baz) values (${e.foo}, ${e.bar}, ${e.baz.usingUnset}\"\n```\n\n## User Defined Type (UDT) support\n\nCassandra4IO provides support for Cassandra's User Defined Type (UDT) values. \nFor example, given the following Cassandra schema:\n\n```cql\ncreate type basic_info(\n    weight double,\n    height text,\n    datapoints frozen\u003cset\u003cint\u003e\u003e\n);\n\ncreate table person_attributes(\n    person_id int,\n    info frozen\u003cbasic_info\u003e,\n    PRIMARY KEY (person_id)\n);\n```\n\n**Note:** `frozen` means immutable\n\nHere is how to insert and select data from the `person_attributes` table:\n\n```scala\nfinal case class BasicInfo(weight: Double, height: String, datapoints: Set[Int])\nobject BasicInfo {\n  implicit val cqlReads: Reads[BasicInfo]   = FromUdtValue.deriveReads[BasicInfo]\n  implicit val cqlBinder: Binder[BasicInfo] = ToUdtValue.deriveBinder[BasicInfo]\n}\n\nfinal case class PersonAttribute(personId: Int, info: BasicInfo)\n```\n\nWe provide a set of typeclasses (`FromUdtValue` and `ToUDtValue`) under the hood that automatically convert your Scala \ntypes into types that Cassandra can understand without having to manually convert your data-types into Datastax Java \ndriver's `UdtValue`s. \n\n```scala\nclass UDTUsageExample[F[_]: Async](session: CassandraSession[F]) {\n  val data = PersonAttribute(1, BasicInfo(180.0, \"tall\", Set(1, 2, 3, 4, 5)))\n  val insert: F[Boolean] =\n    cql\"INSERT INTO cassandra4io.person_attributes (person_id, info) VALUES (${data.personId}, ${data.info})\"\n            .execute(session)\n\n  val retrieve: fs2.Stream[F, PersonAttribute] = \n    cql\"SELECT person_id, info FROM cassandra4io.person_attributes WHERE person_id = ${data.personId}\"\n            .as[PersonAttribute]\n            .select(session)\n}\n```\n\n### More control over the transformation process of `UdtValue`s\n\nIf you wanted to have additional control into how you map data-types to and from Cassandra rather than using `FromUdtValue`\n\u0026 `ToUdtValue`, we expose the Datastax Java driver API to you for full control. Here is an example using `BasicInfo`:\n\n```scala\nobject BasicInfo {\n  implicit val cqlReads: Reads[BasicInfo] = Reads[UdtValue].map { udtValue =\u003e\n    BasicInfo(\n      weight = udtValue.getDouble(\"weight\"),\n      height = udtValue.getString(\"height\"),\n      datapoints = udtValue\n        .getSet[java.lang.Integer](\"datapoints\", classOf[java.lang.Integer])\n        .asScala\n        .toSet\n        .map { int: java.lang.Integer =\u003e Int.unbox(int) }\n    )\n  }\n\n  implicit val cqlBinder: Binder[BasicInfo] = Binder[UdtValue].contramapUDT { (info, constructor) =\u003e\n    constructor\n      .newValue()\n      .setDouble(\"weight\", info.weight)\n      .setString(\"height\", info.height)\n      .setSet(\"datapoints\", info.datapoints.map(Int.box).asJava, classOf[java.lang.Integer])\n  }\n}\n```\n\nPlease note that we recommend using `FromUdtValue` and `ToUdtValue` to automatically derive this hand-written (and error-prone) \ncode. \n\n## Interpolating on CQL parameters\n\nCassandra4IO Allows you to interpolate (i.e. using string interpolation) on values that are not valid CQL parameters using \n`++` or `concat` to build out your CQL query. For example, you can interpolate on the keyspace and table name using \nthe `cqlConst` interpolator like so:\n\n```scala\nval session: CassandraSession[IO] = ???\nval keyspaceName = \"cassandra4io\"\nval tableName    = \"person_attributes\"\nval keyspace     = cqlConst\"$keyspaceName.\"\nval table        = cqlConst\"$tableName\"\n\ndef insert(data: PersonAttribute) = \n  (cql\"INSERT INTO \" ++ keyspace ++ table ++ cql\" (person_id, info) VALUES (${data.personId}, ${data.info})\")\n    .execute(session)\n```\n\nThis allows you (the author of the application) to feed in parameters like the table name and keyspace through \nconfiguration. Please be aware that you should not be taking your user's input and feeding this into `cqlConst` as \nthis will pose an injection risk.\n\n## References\n- [Datastax Java driver](https://docs.datastax.com/en/developer/java-driver/4.9)\n\n## License\nCassandra4io is released under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fringcentral%2Fcassandra4io","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fringcentral%2Fcassandra4io","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fringcentral%2Fcassandra4io/lists"}