All Downloads are FREE. Search and download functionalities are using the official Maven repository.

busymachines.pureharm.phdbslick.SlickQueryAlgebraDefinitions.scala Maven / Gradle / Ivy

/**
  * Copyright (c) 2019 BusyMachines
  *
  * See company homepage at: https://www.busymachines.com/
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
package busymachines.pureharm.phdbslick

import busymachines.pureharm.Identifiable

import busymachines.pureharm.effects._

import busymachines.pureharm.db._
import busymachines.pureharm.phdbslick.slickTypes._

/**
  *
  * Mix in into your global slick definition object.
  * You know which one, every application has one.
  *
  * Unfortunately, there is no way to make these definitions directly available
  * in your "api" object that you then import everywhere slick.
  *
  * Copy the definitions from [[definitions.SlickQueryAlgebraTypes]] to achieve
  * the one import experience.
  *
  * @author Lorand Szakacs, https://github.com/lorandszakacs
  * @since 04 Apr 2019
  *
  */
trait SlickQueryAlgebraDefinitions { self: slick.jdbc.JdbcProfile =>
  import api._
  import busymachines.pureharm.effects.implicits._
  import busymachines.pureharm.dbslick.implicits._

  /**
    * @tparam E
    *   The type of value to be inserted into the table
    * @tparam PK
    *   The type of the value by which to uniquely identify the
    *   element `E`, by default this is also the primary key, and
    *   also has the unique constraint.
    */
  abstract class TableWithPK[E, PK](tag: Tag, name: TableName)(
    implicit val identifiable:           Identifiable[E, PK],
    implicit val columnTypePK:           ColumnType[PK],
  ) extends Table[E](tag, name) {
    //override if you want different constraints, like autoincrement
    def id: Rep[PK] = column(identifiable.fieldName, O.PrimaryKey, O.Unique)
  }

  //===========================================================================
  //===========================================================================
  //===========================================================================

  /**
    *
    * @tparam E
    *   The type of value to be inserted into the table
    * @tparam PK
    *   The type of the value by which to uniquely identify the
    *   element `E`, by default this is also the primary key, and
    *   also has the unique constraint.
    * @tparam TA
    *   Slick Table definition
    */
  abstract class SlickDBQueryAlgebra[E, PK, TA <: TableWithPK[E, PK]](
    implicit val columnTypePK:   ColumnType[PK],
    implicit val identifiable:   Identifiable[E, PK],
    implicit val connectionIOEC: ConnectionIOEC,
  ) extends DAOAlgebra[ConnectionIO, E, PK] {

    /**
      * Because creating this object is done via a macro,
      * we have to actually override in each implementer and
      * call this with the explicit type for ``TA``
      * {{{
      *   class MyStringTable extends TableWithPK[String, String] {...}
      *   class MyQueries extends QueryAlgebra[String, String, MyStringTable]{
      *     override val dao = TableQuery[MyStringTable]
      *   }
      * }}}
      */
    def dao: TableQuery[TA]

    def find(pk: PK): ConnectionIO[Option[E]] = dao.filter(_.id === pk).result.headOption

    def retrieve(pk: PK): ConnectionIO[E] =
      (dao.filter(_.id === pk).result.head: ConnectionIO[E]).adaptError {
        case NonFatal(e) => new RuntimeException("TODO: replace with anomalies or something", e)
      }

    def insert(e: E): ConnectionIO[PK] = (dao.+=(e): ConnectionIO[Int]).map(_ => eid(e))

    def insertMany(es: Iterable[E]): ConnectionIO[Unit] = (dao.++=(es): ConnectionIO[Option[Int]]).void

    def update(e: E): ConnectionIO[E] = (dao.update(e): ConnectionIO[Int]).map(_ => e)

    def updateMany[M[_]: Traverse](es: M[E]): ConnectionIO[Unit] = es.traverse_((e: E) => update(e))

    def delete(pk: PK): ConnectionIO[Unit] = (dao.filter(_.id === pk).delete: ConnectionIO[Int]).void

    def deleteMany(pks: Iterable[PK]): ConnectionIO[Unit] =
      (dao.filter(_.id.inSet(pks)).delete: ConnectionIO[Int]).void

    def exists(pk: PK): ConnectionIO[Boolean] = dao.filter(_.id === pk).exists.result

    def existsAtLeastOne(pks: Iterable[PK]): ConnectionIO[Boolean] = dao.filter(_.id.inSet(pks)).exists.result

    def existAll(pks: Iterable[PK]): ConnectionIO[Boolean] =
      dao.filter(_.id.inSet(pks)).length.result.map(l => l == pks.size)

    private def eid(e: E): PK = identifiable.id(e)
  }

  object SlickDBQueryAlgebra {

    def fromTableQuery[E, PK, TA <: TableWithPK[E, PK]](
      qt: TableQuery[TA],
    )(
      implicit
      columnTypePK:   ColumnType[PK],
      identifiable:   Identifiable[E, PK],
      connectionIOEC: ConnectionIOEC,
    ): SlickDBQueryAlgebra[E, PK, TA] = {
      new SlickDBQueryAlgebra[E, PK, TA]() {
        override val dao: TableQuery[TA] = qt
      }
    }
  }

  //===========================================================================
  //===========================================================================
  //===========================================================================

  abstract class SlickDBAlgebra[F[_], E, PK, TA <: TableWithPK[E, PK]](
    implicit val transactor:     Transactor[F],
    implicit val columnTypePK:   ColumnType[PK],
    implicit val identifiable:   Identifiable[E, PK],
    implicit val connectionIOEC: ConnectionIOEC,
  ) extends DAOAlgebra[F, E, PK] {

    protected def queries: SlickDBQueryAlgebra[E, PK, TA]

    override def find(pk: PK): F[Option[E]] = transactor.run(queries.find(pk))

    override def retrieve(pk: PK): F[E] = transactor.run(queries.retrieve(pk))

    override def insert(e: E): F[PK] = transactor.run(queries.insert(e))

    override def insertMany(es: Iterable[E]): F[Unit] = transactor.run(queries.insertMany(es))

    override def update(e: E): F[E] = transactor.run(queries.update(e))

    override def updateMany[M[_]: Traverse](es: M[E]): F[Unit] = transactor.run(queries.updateMany(es))

    override def delete(pk: PK): F[Unit] = transactor.run(queries.delete(pk))

    override def deleteMany(pks: Iterable[PK]): F[Unit] = transactor.run(queries.deleteMany(pks))

    override def exists(pk: PK): F[Boolean] = transactor.run(queries.exists(pk))

    override def existsAtLeastOne(pks: Iterable[PK]): F[Boolean] = transactor.run(queries.existsAtLeastOne(pks))

    override def existAll(pks: Iterable[PK]): F[Boolean] = transactor.run(queries.existAll(pks))
  }

  object SlickDBAlgebra {

    def fromQueryAlgebra[F[_], E, PK, TA <: TableWithPK[E, PK]](
      q: SlickDBQueryAlgebra[E, PK, TA],
    )(
      implicit tr: Transactor[F],
    ): SlickDBAlgebra[F, E, PK, TA] = {
      new SlickDBAlgebra[F, E, PK, TA]()(
        transactor     = tr,
        columnTypePK   = q.columnTypePK,
        identifiable   = q.identifiable,
        connectionIOEC = q.connectionIOEC,
      ) {
        override protected val queries: SlickDBQueryAlgebra[E, PK, TA] = q
      }
    }

    def fromTableQuery[F[_], E, PK, TA <: TableWithPK[E, PK]](
      qt: TableQuery[TA],
    )(
      implicit transactor: Transactor[F],
      columnTypePK:        ColumnType[PK],
      identifiable:        Identifiable[E, PK],
      connectionIOEC:      ConnectionIOEC,
    ): SlickDBAlgebra[F, E, PK, TA] = {
      fromQueryAlgebra(SlickDBQueryAlgebra.fromTableQuery(qt))
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy