Skip to content

Commit

Permalink
Surrender to the cake. It's the only way to avoid path-dependent type…
Browse files Browse the repository at this point in the history
… mismatches
  • Loading branch information
nafg committed Mar 12, 2020
1 parent 255ee22 commit a8a035a
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 136 deletions.
125 changes: 0 additions & 125 deletions src/main/scala/slick/additions/KeyedTableComponent.scala

This file was deleted.

113 changes: 113 additions & 0 deletions src/main/scala/slick/additions/KeyedTableProfile.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package slick
package additions

import scala.concurrent.ExecutionContext
import scala.language.{higherKinds, implicitConversions}

import slick.additions.entity._
import slick.ast._
import slick.jdbc.JdbcProfile
import slick.lifted.{MappedProjection, RepShape}


trait KeyedTableProfile { this: JdbcProfile =>
trait KeyedTableApi { this: API =>
implicit def lookupBaseColumnType[K: BaseColumnType, A]: BaseColumnType[Lookup[K, A]] =
MappedColumnType.base[Lookup[K, A], K](_.key, EntityKey(_))

type Ent[T <: EntityTableBase] = Entity[T#Key, T#Value]
type KEnt[T <: EntityTableBase] = KeyedEntity[T#Key, T#Value]
def Ent[T <: EntityTableBase](value: T#Value) = new KeylessEntity[T#Key, T#Value](value)

trait KeyedTableBase { keyedTable: Table[_] =>
type Key
def keyColumnName = "id"
def keyColumnOptions = List(O.PrimaryKey, O.AutoInc)
def key = column[Key](keyColumnName, keyColumnOptions: _*)
implicit def keyMapper: TypedType[Key]
}

abstract class KeyedTable[K, A](tag: Tag, tableName: String)(implicit val keyMapper: BaseColumnType[K])
extends Table[A](tag, tableName) with KeyedTableBase {
type Key = K
}

trait EntityTableBase extends KeyedTableBase { this: Table[_] =>
type Value
type Ent = Entity[Key, Value]
type KEnt = KeyedEntity[Key, Value]
}

abstract class EntityTable[K: BaseColumnType, V](tag: Tag, tableName: String)
extends KeyedTable[K, KeyedEntity[K, V]](tag, tableName) with EntityTableBase {
type Value = V
def Ent(v: Value) = new KeylessEntity[Key, Value](v)

def tableQuery: Query[EntityTable[K, V], KEnt, Seq]

def mapping: MappedProjection[V, _]

private def all: MappedProjection[KeyedEntity[K, V], (K, V)] =
(key, mapping) <> ((KeyedEntity.apply[K, V] _).tupled, KeyedEntity.unapply[K, V] _)

def * = all

def lookup = LookupRep[K, V](key <> (EntityKey.apply, lookup => Some(lookup.key)))
}

class KeyedTableQueryBase[K: BaseColumnType, A, T <: KeyedTable[K, A]](cons: Tag => (T with KeyedTable[K, A]))
extends TableQuery[T](cons) {
type Key = K
type Lookup
}

class KeyedTableQuery[K: BaseColumnType, A, T <: KeyedTable[K, A]](cons: Tag => (T with KeyedTable[K, A]))
extends KeyedTableQueryBase[K, A, T](cons) with Lookups[K, A, A, T] {

override def lookupQuery(lookup: Lookup) = this.filter(_.key === lookup.key)
override def lookupValue(a: A) = a
}

class EntTableQuery[K: BaseColumnType, V, T <: EntityTable[K, V]](cons: Tag => T with EntityTable[K, V])
extends KeyedTableQueryBase[K, KeyedEntity[K, V], T](cons) with Lookups[K, V, KeyedEntity[K, V], T] {

type Value = V
type Ent = Entity[Key, Value]
type KEnt = KeyedEntity[Key, Value]
def Ent(v: V): Ent = new KeylessEntity[Key, Value](v)

override def lookupQuery(lookup: Lookup) = this.filter(_.key === lookup.key)
override def lookupValue(a: KeyedEntity[K, V]) = a.value

implicit val mappingRepShape: Shape[FlatShapeLevel, MappedProjection[V, _], V, MappedProjection[V, _]] =
RepShape[FlatShapeLevel, MappedProjection[V, _], V]

def forInsertQuery[E, C[_]](q: Query[T, E, C]) = q.map(_.mapping)

def insert(v: V)(implicit ec: ExecutionContext): DBIO[SavedEntity[K, V]] = insert(Ent(v): Ent)

def insert(e: Ent)(implicit ec: ExecutionContext): DBIO[SavedEntity[K, V]] = {
// Insert it and get the new or old key
val action = e match {
case ke: this.KEnt =>
this returning this.map(_.key: Rep[Key]) forceInsert ke
case ent: this.Ent =>
forInsertQuery(this).returning(this.map(_.key)) += ent.value
}
action.map(SavedEntity(_, e.value))
}
def update(ke: KEnt)(implicit ec: ExecutionContext): DBIO[SavedEntity[K, V]] =
forInsertQuery(lookupQuery(ke)).update(ke.value)
.map(_ => SavedEntity(ke.key, ke.value))

def save(e: Entity[K, V])(implicit ec: ExecutionContext): DBIO[SavedEntity[K, V]] =
e match {
case ke: KEnt => update(ke)
case ke: Ent => insert(ke)
}

def delete(ke: KEnt)(implicit ec: ExecutionContext) =
lookupQuery(ke).delete
}
}
}
2 changes: 1 addition & 1 deletion src/test/scala/slick/additions/KeyedTableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package slick.additions
import scala.concurrent.ExecutionContext.Implicits.global

import slick.additions.test.TestsCommon
import slick.additions.test.driver.api._
import slick.additions.test.TestProfile.api._

import org.scalatest.concurrent.IntegrationPatience
import org.scalatest.funsuite.AnyFunSuite
Expand Down
12 changes: 12 additions & 0 deletions src/test/scala/slick/additions/test/TestProfile.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package slick.additions.test

import slick.additions.KeyedTableProfile
import slick.jdbc.H2Profile


trait TestProfile extends H2Profile with KeyedTableProfile {
object _api extends KeyedTableApi with API
override val api = _api
}

object TestProfile extends TestProfile
13 changes: 3 additions & 10 deletions src/test/scala/slick/additions/test/TestsCommon.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package slick.additions.test

import slick.additions.KeyedTableComponent
import slick.jdbc.H2Profile

import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, Suite}


object driver extends H2Profile with KeyedTableComponent


trait TestsCommon extends BeforeAndAfter with ScalaFutures {
this: Suite =>
trait TestsCommon extends BeforeAndAfter with ScalaFutures { this: Suite =>

import driver.api._
import TestProfile.api._


def schema: driver.DDL
def schema: TestProfile.DDL

val db = Database.forURL(s"jdbc:h2:mem:${getClass.getSimpleName};DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")

Expand Down

0 comments on commit a8a035a

Please sign in to comment.