Skip to content

v0.3.0-beta10

Latest
Compare
Choose a tag to compare
@takapi327 takapi327 released this 06 Jan 12:59
· 5 commits to master since this release

ldbc v0.3.0-beta10 is released.
This release includes new feature additions, enhancements to existing features, disruptive changes and much more.

Note

ldbc is pre-1.0 software and is still undergoing active development. New versions are not binary compatible with prior versions, although in most cases user code will be source compatible.
The major version will be the stable version.

What's Changed

Caution

This version is not compatible with the previous version, v0.3.0-beta9.

Adding Codec

If both Encoder and Decoder were needed, each had to be defined. With this modification, a new Codec has been added, allowing Encoder and Decoder to be defined together.

enum Status:
  case Active, InActive

-given Encoder[Status] = Encoder[Boolean].contramap {
-  case Status.Active   => true
-  case Status.InActive => false
-}

-given Decoder[Status] = Decoder[Boolean].map {
-  case true  => Status.Active
-  case false => Status.InActive
-}

+given Codec[Status] = Codec[Boolean].imap {
+  case true => Status.Active
+  case false => Status.InActive
+} {
+  case Status.Active   => true
+  case Status.InActive => false
+}

It can also be used in place of a Decoder or Encoder by building a Codec.

-given Decoder[City] = (Decoder[Int] *: Decoder[String] *: Decoder[Int]).to[City]
+given Codec[City] = (Codec[Int] *: Codec[String] *: Codec[Int]).to[City]

Modified to allow Either to be used during Decoder construction.

This allows us to construct a process for cases that do not match the following pattern

enum Status(val code: Int):
  case InActive extends Status(0)
  case Active extends Status(1)

given Decoder[Status] = Decoder[Int].emap {
  case 0 => Right(Status.InActive)
  case 1 => Right(Status.Active)
  case unknown => Left(s"$unknown is Unknown Status code")
}

This modification allows Codec to use Either for construction.

given Codec[Status] = Codec[Int].eimap {
  case 0 => Right(Status.InActive)
  case 1 => Right(Status.Active)
  case unknown => Left(s"$unknown is Unknown Status code")
}(_.code)

Additional function

Allow additional conditions of Where to be conditionally excluded

TableQuery[City]
  .select(_.name)
  .where(_.population > 1000000)
  .and(_.name == "Tokyo", false)
// SELECT name FROM city WHERE population > ?

Added a function to the Where conditional statement to determine whether to add a condition to the statement depending on the Option value.

val opt: Option[String] = ???

TableQuery[City]
  .select(_.name)
  .whereOpt(city => opt.map(value => city.name === value))

TableQuery[City]
  .select(_.name)
  .whereOpt(opt)((city, value) => city.name === value)

💣 Breaking Change

Modification of Encoder and Decoder into a composable form using twiddles.

The method of building custom-type Decoders has changed. With this modification, Decoder can be converted to any type using the map function.

- given Decoder.Elem[Continent] = Decoder.Elem.mapping[String, Continent](str => Continent.valueOf(str.replace(" ", "_")))
+ given Decoder[Continent] = Decoder[String].map(str => Continent.valueOf(str.replace(" ", "_")))

Decoder is still constructed implicitly.

case class City(id: Int, name: String, age: Int)


sql"SELECT id, name, age FROM city LIMIT 1"
  .query[City]
  .to[Option]
  .readOnly(conn)

However, implicit searches may fail if there are many properties in the model.

[error]    |Implicit search problem too large.
[error]    |an implicit search was terminated with failure after trying 100000 expressions.
[error]    |The root candidate for the search was:
[error]    |
[error]    |  given instance given_Decoder_P in object Decoder  for  ldbc.dsl.codec.Decoder[City]}

In such cases, raising the search limit in the compilation options may resolve the problem.

scalacOptions += "-Ximplicit-search-limit:100000"

However, it may lead to amplification of compilation time. In that case, it can also be resolved by manually building the Decoder as follows.

given Decoder[City] = (Decoder[Int] *: Decoder[String] *: Decoder[Int]).to[City]

This is true not only for Decoder but also for Encoder.

Rename Executor to DBIO

The type used to represent IO to the DB was Executor, but this was changed because the DBIO type is more intuitive for the user.

- trait Executor[F[_]: Temporal, T]:
+ trait DBIO[F[_]: Temporal, T]:

Migrate table name designation to derived

The method of specifying the table name using query builder has been changed from passing it as an argument of TableQuery to passing it as an argument of Table's derived.

Before

case class City(
  id: Int,
  name:             String,
  countryCode:      String,
  district:         String,
  population:       Int
) derives Table

val table = TableQuery[Test]("city")

After

case class City(
  id: Int,
  name:             String,
  countryCode:      String,
  district:         String,
  population:       Int
)

object City:
  given Table[City] = Table.derived[City]("city")

Renewal of Schema project

This modification changes the way Table types are constructed using the Schema project.
Below we will look at the construction of the Table type corresponding to the User model.

case class User(
  id: Long,
  name: String,
  age: Option[Int],
)

Before

Until now, we had to create instances of Table directly; the arguments of Table had to be passed the corresponding columns in the same order as the properties possessed by the User class, and the data type of the columns had to be set as well, which was mandatory.

The TableQuery using this table type was implemented using Dynamic, which allows type-safe access, but the development tools could not do the completion.

This method of construction was also a bit slower in compile time than class generation

val userTable = Table[User]("user")(
  column("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY),
  column("name", VARCHAR(255)),
  column("age", INT.UNSIGNED.DEFAULT(None)),
)    

After

In this modification, Table type generation has been changed to a method of creating a class by extending Table. In addition, the data type of a column is no longer required, but can be set arbitrarily by the implementer.

This change to a construction method similar to that of Slick has made it more familiar to implementers.

class UserTable extends Table[User]("user"):
  def id: Column[Long] = column[Long]("id")
  def name: Column[String] = column[String]("name")
  def age: Column[Option[Int]] = column[Option[Int]]("age")

  override def * : Column[User] = (id *: name *: age).to[User]

The data type of the columns can still be set. This setting is used, for example, when generating a schema using this table class.

class UserTable extends Table[User]("user"):
  def id: Column[Long] = column[Long]("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY)
  def name: Column[String] = column[String]("name", VARCHAR(255))
  def age: Column[Option[Int]] = column[Option[Int]]("age", INT.UNSIGNED.DEFAULT(None))

  override def * : Column[User] = (id *: name *: age).to[User]

🚀 Features

💪 Enhancement

  • Enhancement/2024 12 added where condition by @takapi327 in #338
  • Enhancement/2024 12 encoder extensions by @takapi327 in #347
  • Enhancement/2024 12 make encoder and decoder compatible with twiddles by @takapi327 in #348
  • Enhancement/2025 01 make decoder support either by @takapi327 in #350
  • Enhancement/2025 01 update where statement by @takapi327 in #353

🪲 Bug Fixes

🔧 Refactoring

⛓️ Dependency update

Full Changelog: v0.3.0-beta9...v0.3.0-beta10