Giter VIP home page Giter VIP logo

cassandra4io's Introduction

cassandra4io's People

Contributors

alexey-yuferov avatar alexuf avatar calvinlfer avatar danielleontiev avatar ilyshav avatar lmnet avatar narma avatar pashnik avatar scala-steward avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cassandra4io's Issues

Support default values for fields of case classes

Currently, if we perform read of row which contains null, while case class has default value, exception is raised.
I.e.

final case class Obj(userId: Int, userData: String = "data")
// we will receive record (123, null)
cqlt"select user_id, user_data from user_table".as[Obj]

But since cql does not support ifNull in quieries, having its emulation in scala code becomes quite handy and useful

CQL4s

Hello, first, great effort here. I did not realise this library existed, which is a shame cause I have been working on a similar project.
I think it'd be nice to maybe compare ideas and effort at some point in the future. Have a look if you like: cql4s

Support automatic derivation for User Defined Types

Hello again,

I have been working on a proof of concept to support the automatic derivation of User-Defined types (including nested user-defined types as well as collections containing both primitives and user-defined types within these user-defined types). I make use of two additional typeclasses as well as some conversions to the top-level Binder/Reads typeclasses:

trait UdtValueBinder[A] { self =>
  def bind(input: A, fieldName: String, constructor: UdtValue): UdtValue
  // ...
}

trait UdtValueReads[A] {
  def read(fieldName: String, in: UdtValue): A
 // ...
}

I was hoping you would consider a contribution if you think this feature is useful. I think it tremendously cuts down boilerplate for our users and takes out the tricky process of hand-writing instances and dealing with the low-level UDTValue API. The work is published here for your consideration: https://github.com/calvinlfer/cassandra4io-experiments/tree/master/src/main/scala/com/userdefinedtypessupport and includes different examples containing various types of nesting. The only case I could not manage to inductively handle is the one corresponding to nested Cassandra collections due to the API. Please let me know if this is of interest and we would gladly contribute this

Compilation time grows exponentially when inserting data into a large table (20+ fields)

Hey all,

We have a use case at work where we deal with tables that contain more than 20+ fields. The compilation time starts to grow very quickly for each field added and at a certain point - compilation time takes more than an hour which is extremely poor ergonomics for our users especially with multiple tables containing lots of fields.

I have narrowed the problem down to the way insertion is implemented via BindableBuilder which causes implicit re-resolution of each element that is part of the HList in the implicit chain each time a field is added causing this massive overhead. So for example, if you insert a String, you'll end up with String :: HNil and if you then insert an Int then you'll end up with
Int :: String :: HNil where the implicit resolution of (String :: HNil) is not cached and needs to be resolved once again resulting in a very slow process since now you need to resolve String :: HNil and then Int :: String :: HNil. If you had five fields, it would end up like this:

f1 :: f2 :: f3 :: f4 :: f5 :: HNil requires

  • f5 :: HNil 5 times
  • f4 :: f5 :: HNil 4 times
  • f3 :: f4 :: f5 :: HNil 3 times
  • f2 :: f3 :: f4 :: f5 :: HNil 2 times
  • f1 :: f2 :: f3 :: f4 :: f5 :: HNil 1 time

I have found that we don't even end up using the types that we are tracking for inserts and that it's needed purely for Shapeless' applyProduct. I have also looked into the way Doobie does this as I wanted to compare if Doobie has the same limitation but it appears that Doobie has a very different way of formulating their string interpolator and they don't have this limitation.

I have managed to work around this limitation by creating a simple builder for Insert, Updates (in-progress) and Selects (in-progress) which do not rely on applyProduct and by association BindableBuilder which brings compilation time back down to a matter of milliseconds and avoids rebuilding implicit re-resolution chains as much as possible. Here's a little snippet:

package com.ringcentral.cassandra4io.dsl

import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.datastax.oss.driver.api.core.cql.BoundStatement
import com.ringcentral.cassandra4io.CassandraSession
import com.ringcentral.cassandra4io.cql.Binder

private[dsl] class Insert[State <: InsertState](
  private val keyspace: Option[String],
  private val table: String,
  private val columnNames: Vector[String],
  private val columnValueSetter: (BoundStatement, Insert2.Index) => (BoundStatement, Insert2.Index)
) { self =>

  def column[A](columnName: String, columnValue: A)(implicit ev: Binder[A]): Insert2[InsertState.NonEmpty] = {
    val updatedSetter: (BoundStatement, Insert2.Index) => (BoundStatement, Insert2.Index) =
      (boundStatement, index) => {
        val (bsUpdated, indexUpdated) = columnValueSetter(boundStatement, index)
        val (bsResult, indexResult)   = ev.bind(bsUpdated, indexUpdated, columnValue)
        (bsResult, indexResult)
      }

    new Insert[InsertState.NonEmpty](
      keyspace = self.keyspace,
      table = self.table,
      columnNames = self.columnNames :+ columnName,
      columnValueSetter = updatedSetter
    )
  }

  def render(implicit ev: State =:= InsertState.NonEmpty): String = {
    val fullyQualifiedTableName =
      keyspace
        .map(k => s"$k.$table")
        .getOrElse(table)

    val columns = columnNames.mkString(start = "(", sep = ", ", end = ")")
    val values  = columnNames.indices.map(_ => "?").mkString(start = "(", sep = ", ", end = ")")

    s"INSERT INTO $fullyQualifiedTableName $columns VALUES $values"
  }

  def execute[F[+_]: Monad](session: CassandraSession[F])(implicit ev: State =:= InsertState.NonEmpty): F[Boolean] = {
    val query = render
    session.prepare(query).flatMap { preparedStatement =>
      val initial     = preparedStatement.bind()
      val (result, _) = columnValueSetter(initial, 0)
      session
        .execute(result)
        .map(_.wasApplied)
    }
  }
}
object Insert {
  type Index = Int

  def apply(table: String, keyspace: Option[String] = None): Insert2[InsertState.Empty] =
    new Insert2[InsertState.Empty](keyspace, table, Vector.empty, (bs, i) => (bs, i))
}

/** Phantom type used to prevent an insert statement from being built without data */
sealed trait InsertState
object InsertState {
  sealed trait Empty    extends InsertState
  sealed trait NonEmpty extends InsertState
}

Usage:

val insert =
  Insert("test_data_multiple_keys")
    .column("id1", 1L)
    .column("id2", 1)
    .column("data", "lol")
    .execute(session)

This is just as type-safe as the cql string interpolator (and not much better in terms of type safety) but it provides our users with a much nicer alternative to slow compilation times. I would love to improve the string interpolated version but that would require a lot of time. Would you guys consider taking in MRs for these builders in the mean time?

Reading a datatype that is set to null returns a default value instead of giving you back an error

Hey there,

I have found that reading values that could potentially be null but reading them as a non-optional datatype will return default values instead of providing an error. I believe this is the default behavior of the underlying Datastax Java client.

Here's an example:

CREATE TABLE example_table(
    a int,
    b ascii,
    c double,
    PRIMARY KEY(a)
);

INSERT INTO example_table(a) VALUES (1);

Reading this using the following:

final case class TableRow(a: Int, b: String, c: Double)

class TableRepo(session: CassandraSession[IO]) {
    def get(a: Int): Stream[IO, TableRow] =
      cql"SELECT a, b, c FROM example_table WHERE a = $a"
        .as[TableRow]
        .select(session)
  }

Doing that will give you back a TableRow(a = 1, b = null, c = 0.0)
I was wondering if we could impose an additional constraint about fields not being null and if they were null then throwing an error (rather than returning default values that could be confusing - for example here double returns 0.0 and boolean returns false when null) when you tried to read these fields from Cassandra. I have managed to do this by doing the following:

package com.experiments.calvin
import com.datastax.oss.driver.api.core.cql.{BoundStatement, Row}
import com.ringcentral.cassandra4io.cql.{Binder, Reads}
import shapeless.<:!<

final case class NonNullableConstraintViolated(message: String) extends Exception

final case class NonNullable[A](underlying: A)
object NonNullable {
  implicit def nonNullableCqlReads[A](implicit
    underlyingReads: Reads[A],
    evidenceNotNested: A <:!< NonNullable[_]
  ): Reads[NonNullable[A]] = { (row: Row, index: Int) =>
    if (row.isNull(index))
      throw NonNullableConstraintViolated(
        s"Expected valid data at index $index at row ${row.getFormattedContents} but got NULL instead"
      )
    else {
      val (underlying, nextIndex) = underlyingReads.read(row, index)
      (NonNullable(underlying), nextIndex)
    }
  }

  implicit def nonNullableCqlBinder[A](implicit
    underlyingBinder: Binder[A],
    evidenceNotOption: A <:!< Option[_],
    evidenceNotNested: A <:!< NonNullable[_]
  ): Binder[NonNullable[A]] =
    (statement: BoundStatement, index: Int, value: NonNullable[A]) =>
      underlyingBinder.bind(statement, index, value.underlying)
}

There are some extra implicit requirements to prevent doing thing like NonNullable[Option[A]] or NonNullable[NonNullable[A]]. I was wondering if you consider adding a mechanism/constraint like this to the library that would now throw errors if you read data that you did not expect to be null or would you happen to know if you can tweak this behavior within the core Datastax Java client itself?

Thank you for reading
Cal

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.