#rzeszowjava

Introduction (101)

Introduction (101)

Let's talk about different approaches to API

Repository

Preso

Who am I?

twitter: @pdolega

github: github.com/pdolega​

Do stuff @ VirtusLab

 

What is Slick?

Not an ORM

ORM problems

N+1 problem (lazy / eager fetching)

Session context scope

Execution under the cover (cache)

O-R impedance mismatch

False promise

Leaky abstraction

FRM

 Embrace database model through a functional paradigm.

Code samples:

SmokeSpec

    




    "correctly open db" in {
      val db = Database.forConfig("memoryDb")
      val results = db.run(sql"SELECT 2 + 3".as[(Int)])
      // results: Future[Seq[Int]]
      

      // option 1 - bad
      blockingWait(results).head shouldBe 5

      // option 2 - better
      results.map { r =>
        r.head shouldBe (5)
      }.futureValue    // this is only for unit tests to work
    }


    memoryDb = {
      connectionPool = disabled
      url = "jdbc:h2:mem:slick-101"
      driver = "org.h2.Driver"
      user = ""
      password = ""
      keepAliveConnection = true
    }

Config

    




    "correctly open db" in {
      val db = Database.forConfig("memoryDb")
      val results = db.run(sql"SELECT 2 + 3".as[(Int)])
      // results: Future[Seq[Int]]
      

      // option 1 - bad
      blockingWait(results).head shouldBe 5

      // option 2 - better
      results.map { r =>
        r.head shouldBe (5)
      }.futureValue    // this is only for unit tests to work
    }


    
        db.run(
            sql"SELECT 2 + 3".as[(Int)]
        )
    
    
    
    
    
        db.run(
            SQLActionBuilder("SELECT 2 + 3", SetUnit).as[Int]
        )

Referential transparency

&

Local Reasoning



      val invitedStudent = profileDao
        .findByUserId(token.invitingUserId)
        .map(_.get)

      val token = inviteTokenDao
        .update(token.copy(
            invitedUser = Some(
                invitedStudent.userId), 
                isUsed = true
            )
        )

      friendsAndFollowersService
        .makeStudentsFriends(invitedStudent.id, invitingSt.id)

      val invitingStudent = studentProfileDao
        .findByUserId(token.invitingUserId).map(_.get)
      
      userDao.findFullUserById(invitedStudent.userId)
        .map(_.get.user)

Spring annotations









            @Transactional
            public List listStudentsBySchool(long id) {
                ...
                repository.findByClasses_School_Id(id);
                ...
            }

Back to reactive

Reactive (™?)

...wait a minute, JDBC is blocking anyway

Everything is async...

How many connections do you need?

db_pool_connections =

(core_count * 2) + effective_spindle_count

source: postgres docs

Imagine that you have a website that while maybe not Facebook-scale still often has 10,000 users making database requests simultaneously -- accounting for some 20,000 transactions per second.

 

How big should your connection pool be?

Server handling 10 000 client connections may need not more than 10 connection

The point being...

Use less threads / let CPU be busy

Threads are expensive

Yet traditionally they are wasted on IO

Don't block general threads / use designated thread pool

Type-Safe



CREATE TABLE student (
    id BIGINT GENERATED BY DEFAULT 
        AS IDENTITY PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    middle_name VARCHAR(100),
    surname VARCHAR(100) NOT NULL,
    nationality VARCHAR(100) NOT NULL
);





db.run(
    StudentTable
    .filter(_.name === "John"))
    .result              
)

OK!






db.run(
    StudentTable
    .filter(_.firstName === "John"))
    .result              
)

Wrong!






db.run(
    StudentTable
    .filter(_.name === 153))
    .result              
)

Wrong!

How?

Tables

Code samples:

TableSpec



CREATE TABLE university (
    id BIGINT GENERATED BY DEFAULT 
        AS IDENTITY PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    middle_name VARCHAR(100)
);

  // in-application respresentation of table's tuple
  case class University(name: String,
                        id: Long = -1L)

  // definition of table
  class UniversityTable(tag: Tag) extends Table[University](
    tag, "university") {
    
    def name = column[String]("name")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

    // default projection
    def * = (name, id) <> (University.tupled, 
                           University.unapply)
  }

  // 'table' object used for interacting with in app
  lazy val UniversityTable = TableQuery[UniversityTable]






  // creating schema
  override protected def beforeEach {
    blockingWait(db.run(UniversityTable.schema.create))
  }
    "be insertable and retrievable - poor version" in {
      // read everything from a table
      blockingWait(db.run(UniversityTable.result)) should 
                    have size 0

      // insert into table
      blockingWait(db.run(
        UniversityTable ++= Seq(
          University("Hogwart"),
          University("Scala University")
        )
      ))

      // read again
      val results = blockingWait(
            db.run(UniversityTable.result)
      )

      results.map(_.name) should contain theSameElementsAs
        Seq("Hogwart", "Scala University")
    }

Poor version

Queries

Code samples:

QueriesSpec

      // here we get Future[Seq[Student]]
      db.run(StudentTable.result) 
        // here I map over Future
        // within map we have results: Seq[Student]
        .map { results => 
            // we map it to (String, String)
            results.map(student => (student.name, 
                                    student.surname))    
      }
      // we end up with Future[Seq[(String, String)]]
select "NAME", "MIDDLE_NAME", "SURNAME", "NATIONALITY", "ID" 
from "STUDENT"

Actual query


      db.run(
        StudentTable.map { student =>
            // here I map over query!
            (student.name, student.surname)
        }
        .result
      )
      // we end up with Future[Seq[(String, String)]]
select "NAME" "SURNAME"
from "STUDENT"

Actual query


      db.run(
        StudentTable.map { student =>
            // here I map over query!
            (student.name, student.surname)
        }
        .result
      )
      // we end up with Future[Seq[(String, String)]]

Steps of execution


def execQuery[E, U, C[_]](q: Query[E, U, C]): Future[C[U]] = 
    db.run(    // convert into Future
        q.result    // convert into DBIO
    )

The best approach to write queries using Slick’s type-safe API is thinking in terms of Scala collections.

    select  "NAME", 
            "MIDDLE_NAME", 
            "SURNAME", 
            "NATIONALITY", 
            "ID" 
    
    from    "STUDENT"



    StudentTable

// or

    for {student <- StudentTable }
        yield student
    select  "NAME", 
            "MIDDLE_NAME", 
            "SURNAME", 
            "NATIONALITY", 
            "ID" 
    
    from    "STUDENT"



    for {student <- StudentTable }
        yield student

// or

    StudentTable.map { student =>
        student
    }



    for { 
        x <- XXX 
        y <- YYY
        z <- ZZZ
    } yield x * y * z

// or

    XXX.flatMap { x =>
        YYY.flatMap { y =>
            ZZZ.map { z =>
                x * y * z
            }
        }
    }




execQuery(
    StudentTable
)

def execQuery[E, U, C[_]](q: Query[E, U, C]): Future[C[U]] = 
    db.run(    // convert into Future
        q.result    // convert into DBIO
    )
      


    StudentTable
        .map(_.name)
    

    select "NAME" 
    
    from "STUDENT"



    StudentTable
        .map(s => 
            (s.name, s.middleName.ifNull("*no-middlename*"))
        )
        .sortBy(_.name)
    select  "NAME", 
            ifnull("MIDDLE_NAME",'*no-middlename*') 
    
    from "STUDENT" 
    order by "NAME"

Wrong!

The best approach to write queries using Slick’s type-safe API is thinking in terms of Scala collections.




        List<Integer> num = 
            Arrays.asList(
                new Student("John", "Doe"), 
                new Student("Rowan", "Atkinson")
            );

        num.stream()
            .map(s -> new Pair(s.getName(), s.getMiddleName()))
            .filter(s -> s.getName());

Wrong!




    StudentTable
        .map(s => 
            (s.name, s.middleName.ifNull("*no-middlename*"))
        )
        .sortBy(_._1)
    select  "NAME", 
            ifnull("MIDDLE_NAME",'*no-middlename*') 
    
    from "STUDENT" 
    order by "NAME"

OK!




    StudentTable
        .sortBy(_.name)
        .map(s => 
            (s.name, s.middleName.ifNull("*no-middlename*"))
        )
    select  "NAME", 
            ifnull("MIDDLE_NAME",'*no-middlename*') 
    
    from "STUDENT" 
    order by "NAME"

      StudentTable
        .map(_.nationality ++ "  ")
        .map(_.toUpperCase)
        .map(_.trim)
        .map((_, currentTime, pi))
        .map(row => 
            row._1 ++ " " ++ 
            row._2.asColumnOf[String] ++ " " ++ 
            row._3
        )
    select  (((ltrim(rtrim(ucase("NATIONALITY"||'  ')))||' ') 
            || cast(curtime() as VARCHAR))||' ') 
            || cast(pi() as VARCHAR) 
    from "STUDENT"

    StudentTable
        .map { nat =>
          (nat.nationality ++ "  ").toUpperCase.trim ++ 
          " " ++
          currentTime.asColumnOf[String] ++ 
          " " ++
          pi
        }

      StudentTable
        .map(_.nationality ++ "  ")
        .map(_.toUpperCase)
        .map(_.trim)
        .map((_, currentTime, pi))
        .map(row => 
            row._1 ++ " " ++ 
            row._2.asColumnOf[String] ++ " " ++ 
            row._3
        )


    StudentTable
        .filter(_.name === "Tom")

    // or

    for {
        student <- StudentTable if student.name === "Tom"
    } yield student

    select  "NAME", "MIDDLE_NAME", "SURNAME", ... 

    from    "STUDENT" 

    where   "NAME" = 'Tom'
    StudentTable
        .filterNot(student => 
            student.name === "Tom" && 
            student.surname.startsWith("Smi")
    )
    // or
    for {
        student <- StudentTable if !(
                student.name === "Tom" && 
                student.surname.startsWith("Smi"))
    } yield student

    select "NAME", 
           "MIDDLE_NAME", 
           "SURNAME", 
           "NATIONALITY", 
           "ID" 

    from "STUDENT" 
    
    where not (("NAME" = 'Tom') and 
               ("SURNAME" like 'Smi%' escape '^'))


    StudentTable
        .filter(student => student.middleName.nonEmpty)
        .sortBy(s => (s.name.desc, s.middleName.asc))
        .distinct

    select distinct "NAME", ... 

    from "STUDENT" 

    where "MIDDLE_NAME" is not null 

    order by "NAME" desc, "MIDDLE_NAME"


      StudentTable
        .map(s => (s.name, s.surname))
        .drop(2)
        .take(3)

    select ... 

    from "STUDENT" 

    limit 3 offset 2


      StudentTable
        .map(s => (s.name, s.surname))
        .take(3)
        .drop(2)

    select ... 

    from "STUDENT" 

    limit 1 offset 2

The best approach to write queries using Slick’s type-safe API is thinking in terms of Scala collections.


    StudentTable
        .filter(_.surname =!= "Test")
        .groupBy(_.surname)
        .map { case (surname, group) =>
          (surname, group.map(_.name).countDistinct)
        }
        .filter(row => row._2 > 5)

    select "SURNAME", count(distinct "NAME") 

    from "STUDENT" 

    where not ("SURNAME" = 'Test') 

    group by "SURNAME" 

    having count(distinct "NAME") > 5

*Click*

Actions

    "be insertable and retrievable - poor version" in {
      // read everything from a table
      blockingWait(db.run(UniversityTable.result)) should 
                    have size 0

      // insert into table
      blockingWait(db.run(
        UniversityTable ++= Seq(
          University("Hogwart"),
          University("Scala University")
        )
      ))

      // read again
      val results = blockingWait(
            db.run(UniversityTable.result)
      )

      results.map(_.name) should contain theSameElementsAs
        Seq("Hogwart", "Scala University")
    }

Poor version






memDb.run(
    UniversityTable                            
    .filter(_.name === "Hogwarth") // Query
    .result               // DBIOAction (DBIO)
)    // Future

Monadic Trio

class Maybe<T> {
    private T arg;

    private static Maybe<?> None = new Maybe<>();

    private Maybe() {
    }

    public Maybe(T arg) {
        this.arg = arg;
    }

    public static <T> Maybe<T> unit(T arg) {
        if(arg == null) {
            return (Maybe<T>)None;
        } else {
            return new Maybe(arg);
        }
    }

    public <R> Maybe<R> flatMap(Function<T, Maybe<R>> func) {
        if(isEmpty()) {
            return (Maybe<R>)None;
        } else {
            return func.apply(arg);
        }
    }

    public <R> Maybe<R> map(Function<T, R> func) {
        return flatMap(x -> Maybe.unit(func.apply(arg)));
    }

    public T getOrElse(T alternative) {
        if(isEmpty()) {
            return alternative;
        } else {
            return arg;
        }
    }

    private boolean isEmpty() {
        return arg == null;
    }
}






class Maybe<T> {
    ...

    public static <T> Maybe<T> unit(T arg) {
        if(arg == null) {
            return (Maybe<T>)None;
        } else {
            return new Maybe(arg);
        }
    }

    ...
}




class Maybe<T> {
    ...

    public <R> Maybe<R> flatMap(Function<T, Maybe<R>> func) {
        if(isEmpty()) {
            return (Maybe<R>)None;
        } else {
            return func.apply(arg);
        }
    }


    private boolean isEmpty() {
        return arg == null;
    }

    ...
}




class Maybe<T> {

    ...

    public <R> Maybe<R> map(Function<T, R> func) {
        return flatMap(x -> Maybe.unit(func.apply(arg)));
    }

    ...
}




class Maybe<T> {

    ...

    public T getOrElse(T alternative) {
        if(isEmpty()) {
            return alternative;
        } else {
            return arg;
        }
    }

    ...
}
      


         public Mayber<Person> getParent(...)




          Maybe.unit(getUserId())
                .flatMap(id -> getParent(id))
                .flatMap(id -> getParent(id))
                .map(grandParent -> grandParent.getAge())
                .getOrElse(99)








        public static <T> Maybe<T> unit(Supplier<T> func)

        public <R> Maybe<R> flatMap(Function<T, Maybe<R>> func) 

        public <R> Maybe<R> map(Function<T, R> func)
      


         public Mayber<Person> getParent(...)




          Maybe.unit(getUserId())
                .flatMap(id -> getParent(id))
                .flatMap(id -> getParent(id))
                .map(grandParent -> grandParent.getAge())
                .getOrElse(99)

execution

    "be insertable and retrievable - poor version" in {
      // read everything from a table
      blockingWait(db.run(UniversityTable.result)) should 
                    have size 0

      // insert into table
      blockingWait(db.run(
        UniversityTable ++= Seq(
          University("Hogwart"),
          University("Scala University")
        )
      ))

      // read again
      val results = blockingWait(
            db.run(UniversityTable.result)
      )

      results.map(_.name) should contain theSameElementsAs
        Seq("Hogwart", "Scala University")
    }

Poor version

Description

vs

execution







db.run(                      // execution
    (StudentCourseSegmentTable              // description
          join CourseTable on (_.courseId === _.id)
          join StudentTable on (_._1.studentId === _.id))
    .result
)
      db.run(    // first all within this block is constructed
                 // and the resulting DBIO is passed to db.run
                 // to be executed
          for {
            emptyResults <- UniversityTable.result
            _ <- insertUnis
            nonEmptyResults <- UniversityTable.result
          } yield {
            emptyResults should have size 0
            nonEmptyResults.map(_.name) should 
                    contain theSameElementsAs
              Seq("Hogwart", "Scala University")
          }
      )
...
      def insertUnis = UniversityTable ++= Seq(
          University("Hogwart"),
          University("Scala University")
      ) 

Better version

transactions.stream()
    .filter(t -> t.getType() == Transaction.GROCERY)
    .sorted(comparing(Transaction::getValue).reversed())
    .map(Transaction::getId) // up to this line - description
    .collect(toList()) // execution

Java 8 streams

db.run(                      // execution
    (StudentCourseSegmentTable              // description
          join CourseTable on (_.courseId === _.id)
          join StudentTable on (_._1.studentId === _.id)).result
)

Scala / Slick

DBIOAction / DBIO

DBIOAction[R, S, E]

R - type of result
S - streaming / not streaming
E - what kind of effect

DBIO[R] =

DBIOAction[R, NoStream, Effect.All]

R - type of result

  def executeReadOnly[R, S <: dbio.NoStream](
    readOnlyOper: DBIOAction[R, S, Effect.Read] // param decl
  ): Future[Unit] = { // result type
    db.run(readOnlyOper).map { results =>
      log.info(s"Results are: $results")
    }
  }
// this works
executeReadOnly(UniversityTable.result)
// this won't even compile
executeReadOnly(UniversityTable += University("Nice try!"))
// DBIOAction[Seq[University], Stream, Effect.Read]
db.run(
    UniversityTable.result
)
...

// DBIOAction[Option[Int], NoStream, Effect.Write]
db.run(
    UniversityTable += University("Nice try!")
)
...

// DBIOAction[Option[Int], NoStream, 
// Effect.Write with Effect.Transactional]
db.run(
    (UniversityTable += University("Nice try!"))
    .transactionally
)
...
// DBIOAction[Unit, NoStream, Effect.Schema]
db.run(
    UniversityTable.schema.create
)

Query

Query[M, U, C]

M - mixed type

U - unpacked type

C - collection type


UniversityTable                            
    .filter(_.name === "Hogwarth")

What are unpacked (U), mixed (M) and collection (C) types?


UniversityTable                            
    .filter(_.name === "Hogwarth")    
    // _ above is UniverityTable (M)

  // unpacked type
  case class University(name: String,
                        id: Long = -1L)

  // mixed type
  class UniversityTable(tag: Tag) extends Table[University](
    tag, "university") {
    
    def name = column[String]("name")
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

    // default projection
    def * = (name, id) <> (University.tupled, 
                           University.unapply)
  }







db.run(    // Future[Seq[University]]

   UniversityTable.filter(uni => // Query[UniversityTable,
                                 // University, Seq]
       uni.name === "Hogwarth"
   )
   .result    // DBIO[Seq[University]]

)

If you were to remember only one thing

Monadic Trio

DBIO

Query

description of a DB query

description of 1...N DB operations

Future

gimme a break...

The other thing worth to remember

It's (mostly) all about DBIO composition

Composition / Transactions

DBIO.transactionally

*Click*

db.run(
    (UniversityTable ++= Seq(
      University("Hogwart"),
      University("Scala University")
    )).transactionally
)

Transactions - example

OK, but how do we combine DB operations?

DBIO is a monad

db.run(
    (for {
        dbio1 <- insertUnis
        dbio2 <- ...
        dbio3 <- ...
        ...
        dbioN <- ...
    } yield {
        ...
    }).transactionally
)
def insertUnis: DBIO[Option[Int]] =
    UniversityTable ++= Seq(
      University("Hogwart"),
      University("Scala University")
    )

map / flatMap

seq

    val combined: DBIO[Unit] = DBIO.seq(
        produceDbOper1,    // DBIO[T]
        produceDbOper2,    // DBIO[R]
        produceDbOper3     // DBIO[M]
    )

sequence

    val combined: DBIO[Seq[T]] = DBIO.sequence(
        Seq(
            produceDbOper1, // DBIO[T]
            produceDbOper2, // DBIO[T]
            produceDbOper3  // DBIO[T]
        )
    )
  db.run( // Future[Seq[Seq[Course]]]
    for {
      students <- StudentTable.result
      courses <- DBIO.sequence(students.map(fetchMoreData))
    } yield {
      courses
    }
  )


...


  private def fetchMoreData(student: Student): 
    DBIO[Seq[Course]] = {
    (for {
      segment <- StudentCourseSegmentTable 
                if segment.studentId === student.id
      course <- segment.course
    } yield {
      course
    }).result
  }

unit

db.run(
    (for {
        student <- StudentTable
                    .filter(_.name === "Tim").result // DBIO
        smthNotDb <- calculateSomethingNotDb(student) // duh?!
        _ <- updateBasedOnCalculation(smthNotDb) // DBIO
    } yield (...)).transcationally
)

Wrong!

DBIO.successful

db.run(
    (for {
        student <- StudentTable
                    .filter(_.name === "Tim").result // DBIO
        smthNotDb <- DBIO.successful(
            calculateSomething(student)
        ) // Yeah!
        _ <- updateBasedOnCalculation(smthNotDb) // DBIO
    } yield (...)).transcationally
)




    def execTransact[T](dbio: DBIO[T]): Future[T] = 
        db.run(dbio.transactionally)

Combine operations & fire within transtion

How does FP deal with effects?

They cheat

If you cannot solve the problem, change it into problem you can solve

Build description (referenetially transparent)

+

combine

+

db.run(...)

Errors?

Exceptions



    // File Name : ExcepTest.java
    import java.io.*;
    
    public class ExcepTest {
    
       public static void main(String args[]) {
          try {
             int a[] = new int[2];
             System.out.println("Access element three :" + a[3]);
          } catch (ArrayIndexOutOfBoundsException e) {
             System.out.println("Exception thrown  :" + e);
          }
          System.out.println("Out of the block");
       }
    }

A better alternative is to have your functions return error values when things go wrong, and to deal with these explicitly, no matter how verbose it might be.

OCTOBER 13, 2003









            int fputc( int c, FILE *fp );

Not new concept - new incarnation Go lang


    input, err := files.ReadInput(inputPath)
    if err != nil {
	logrus.Debugf("Can't open the template: %v", err)
	return err
    }
    

    result, err := r.Render(templateName, string(input))
    if err != nil {
	return err
    }    
    
    err = files.WriteOutput(outputPath, []byte(result), 0644)
    if err != nil {
	logrus.Debugf("Can't save the rendered: %v", err)
	return err
    }

Try

  
  object Test extends App {
    
        def readTextFile(filename: String): Try[List[String]] = {
            Try(Source.fromFile(filename).getLines.toList)
        }
    
        val filename = "/etc/passwd"
        readTextFile(filename) match {
            case Success(lines) => lines.foreach(println)
            case Failure(f) => println(f)
        }
    
    }




db.run(
    (UniversityTable ++= Seq(
      University("Hogwart"),
      University("Scala University")
    )).transactionally.asTry
)







db.run(    // Future[Seq[University]]

   UniversityTable.filter(uni => // Query[UniversityTable,
                                 // University, Seq]
       uni.name === "Hogwarth"
   )
   .result.asTry    // DBIO[Try[Seq[University]]]

)
       

        tryDbioResult match {    // DBIO[Try[Result]]

            case Success(myResult) => ....
            case Failure(exception) => ...

        }
 

Joins

Monadic

vs

applicative


  // student course segment
  case class StudentCourseSegment(studentId: Id[Student],
                                  courseId: Id[Course],
                                  semesterId: Id[Semester],
                                  id: Id[StudentCourseSegment] = Id.none)

  class StudentCourseSegmentTable(tag: Tag) extends 
    Table[StudentCourseSegment](tag, "STUDENT_COURSE_SEGMENT") {
    def studentId = column[Id[Student]]("STUDENT_ID")
    def courseId = column[Id[Course]]("COURSE_ID")
    def semesterId = column[Id[Semester]]("SEMESTER_ID")
    def id = column[Id[StudentCourseSegment]]("ID", O.PrimaryKey, O.AutoInc)

    def * = (studentId, courseId, semesterId, id) <> (StudentCourseSegment.tupled, 
                                                      StudentCourseSegment.unapply)

    // foreign keys
    def student = foreignKey("fk_segment_student", studentId, StudentTable)(_.id)
    def course = foreignKey("fk_segment_course", courseId, CourseTable)(_.id)
    def semester = foreignKey("fk_segment_semester", semesterId, SemesterTable)(_.id)
  }

  lazy val StudentCourseSegmentTable = TableQuery[StudentCourseSegmentTable]



def student = 
    foreignKey("fk_segment_student", 
        studentId, StudentTable)(_.id)
def course = 
    foreignKey("fk_segment_course", 
        courseId, CourseTable)(_.id)
def semester = 
    foreignKey("fk_segment_semester", 
        semesterId, SemesterTable)(_.id)
db.run((
    for {
      segment <- StudentCourseSegmentTable
      course <- segment.course // foreign key
      student <- segment.student // foreign key
    } yield (course, student)
)
db.run(
        StudentCourseSegmentTable
          join CourseTable on (_.courseId === _.id)
          join StudentTable on (_._1.studentId === _.id)
        ).map {
            case ((segment, course), student) => 
                (course, student)
        }
)

Monadic

Applicative





... 
    // in my table mapping
    def id = column[Id[StudentCourseSegment]]("ID", 
        O.PrimaryKey, O.AutoInc)
...
    // in my case class
    case class StudentCourseSegment(studentId: Id[Student],
                        courseId: Id[Course],
                        semesterId: Id[Semester],
                        id: Id[StudentCourseSegment] = Id.none)
...



case class Id[T](value: Long)


object TypesafeId {
  implicit def columnType[T]: BaseColumnType[Id[T]] =
    MappedColumnType.base[Id[T], Long](toLong, fromLong)

  private def fromLong[T](dbId: Long): Id[T] = Id(dbId)

  private def toLong[T](id: Id[T]): Long = id.value
}




    StudentCourseSegmentTable
          join CourseTable on (_.studentId === _.id)    // WTF
          join StudentTable on (_._1.courseId === _.id)

Type-safety again

Wrong!

More complicated join

Applicative vs monadic

StudentCourseSegmentTable
    .join(StudentTable)
        .on { case (segment, student) => 
            student.id === segment.studentId }
    .join(CourseTable)
        .on { case ((segment, _), course) => 
            course.id === segment.courseId }
    .join(SemesterTable)
        .on { case (((segment, _), _), semester) => 
            semester.id === segment.semesterId }
    .filter { case (((_, student), _), _) =>
        student.name === "Tim"
    }
    select x2."NAME",
           ...
    from    "STUDENT_COURSE_SEGMENT" x2, 
            "STUDENT" x3, 
            "COURSE" x4, 
            "SEMESTER" x5 

    where   (x3."NAME" = 'Tim') and 
            (((x3."ID" = x2."STUDENT_ID") and 
            (x4."ID" = x2."COURSE_ID")) and 
            (x5."ID" = x2."SEMESTER_ID"))
    for {
      segment <- StudentCourseSegmentTable
      student <- segment.student if student.name === "Tim"
      course <- segment.course
      semester <- segment.semester
    } yield (segment, student, course, semester)
    select x2."NAME",
           ...
    from    "STUDENT_COURSE_SEGMENT" x2, 
            "STUDENT" x3, 
            "COURSE" x4, 
            "SEMESTER" x5 

    where   (x3."NAME" = 'Tim') and 
            (((x3."ID" = x2."STUDENT_ID") and 
            (x4."ID" = x2."COURSE_ID")) and 
            (x5."ID" = x2."SEMESTER_ID"))

Sometimes one form is more elegant than the other

Outer joins

// document
case class Document(studentId: Option[Id[Student]],
                    name: String,
                    uuid: String,
                    id: Id[Document] = Id.none)

class DocumentTable(tag: Tag) extends Table[Document]
    (tag, "DOCUMENT") {
    def studentId = column[Option[Id[Student]]]("STUDENT_ID")
    def name = column[String]("NAME")
    def uuid = column[String]("UUID")
    def id = column[Id[Document]]("ID", 
                        O.PrimaryKey, O.AutoInc)
    
    def * = (studentId, name, uuid, id) <> 
            (Document.tupled, Document.unapply)

    def student = foreignKey("fk_document_student", 
        studentId, StudentTable)(_.id.?)
}

lazy val DocumentTable = TableQuery[DocumentTable]



case class Document(studentId: Option[Id[Student]],
                    name: String,
                    uuid: String,
                    id: Id[Document] = Id.none)
//...
//...

class DocumentTable(tag: Tag) extends Table[Document]
    (tag, "DOCUMENT") {
    def studentId = column[Option[Id[Student]]]("STUDENT_ID")
//...
//...
    def student = foreignKey("fk_document_student", 
        studentId, StudentTable)(_.id.?)
}



    DocumentTable
          .joinLeft(StudentTable).on(_.studentId === _.id)
    select x2."STUDENT_ID", ..., x3."ID", ... 

    from "DOCUMENT" x2 

    left outer join "STUDENT" x3 
        on x2."STUDENT_ID" = x3."ID"


        DocumentTable
          .joinLeft(StudentTable).on(_.studentId === _.id)
          .filter { case(doc, student) => 
                student.map(_.name) === "Tom" 
        }
    select x2."STUDENT_ID", ..., x3."ID", ... 

    from "DOCUMENT" x2 

    left outer join "STUDENT" x3 
        on x2."STUDENT_ID" = x3."ID"

     where x3."NAME" = 'Tom'

Slick 3.2 ?

No nice monadic form for outer joins

Summary

Future / DBIO / Query

Slick is not ORM

DBIO composition

Think in terms of  Collection API

Questions?

Resources

Online workshop by Dave Gurnell: https://vimeo.com/148074461

Bonus

    





    for {
          segment <- StudentCourseSegmentTable
          student <- segment.student if student.name === "Tim"
          course <- segment.course
          semester <- segment.semester
    } yield (segment, student, course, semester)

DEBUG s.c.QueryCompilerBenchmark - ------------------- Phase: Time ---------
DEBUG s.c.QueryCompilerBenchmark -       assignUniqueSymbols:    0.446286 ms
DEBUG s.c.QueryCompilerBenchmark -                inferTypes:    0.239283 ms
DEBUG s.c.QueryCompilerBenchmark -              expandTables:    0.984031 ms
DEBUG s.c.QueryCompilerBenchmark -           forceOuterBinds:    1.048910 ms
DEBUG s.c.QueryCompilerBenchmark -         removeMappedTypes:    0.536035 ms
DEBUG s.c.QueryCompilerBenchmark -                expandSums:    0.016068 ms
DEBUG s.c.QueryCompilerBenchmark -         emulateOuterJoins:    0.108066 ms
DEBUG s.c.QueryCompilerBenchmark -             expandRecords:    0.421471 ms
DEBUG s.c.QueryCompilerBenchmark -        flattenProjections:    2.212847 ms
DEBUG s.c.QueryCompilerBenchmark -              rewriteJoins:    2.219579 ms
DEBUG s.c.QueryCompilerBenchmark -             verifySymbols:    0.204175 ms
DEBUG s.c.QueryCompilerBenchmark -             relabelUnions:    0.086386 ms
DEBUG s.c.QueryCompilerBenchmark -          createAggregates:    0.013349 ms
DEBUG s.c.QueryCompilerBenchmark -           resolveZipJoins:    0.126179 ms
DEBUG s.c.QueryCompilerBenchmark -          pruneProjections:    0.567347 ms
DEBUG s.c.QueryCompilerBenchmark -           rewriteDistinct:    0.018858 ms
DEBUG s.c.QueryCompilerBenchmark -    createResultSetMapping:    0.274411 ms
DEBUG s.c.QueryCompilerBenchmark -            hoistClientOps:    0.649790 ms
DEBUG s.c.QueryCompilerBenchmark -         reorderOperations:    0.643005 ms
DEBUG s.c.QueryCompilerBenchmark -     mergeToComprehensions:    4.482695 ms
DEBUG s.c.QueryCompilerBenchmark -            optimizeScalar:    0.179255 ms
DEBUG s.c.QueryCompilerBenchmark -          removeFieldNames:    1.145481 ms
DEBUG s.c.QueryCompilerBenchmark -                   codeGen:    3.783455 ms
DEBUG s.c.QueryCompilerBenchmark -                     TOTAL:   20.406962 ms
      
    val query = Compiled { name: Rep[String] =>
        for {
          segment <- StudentCourseSegmentTable
          student <- segment.student if student.name === name
          course <- segment.course
          semester <- segment.semester
        } yield (segment, student, course, semester)
    }
...
...
    db.run(query("Tim").result)

Precompilation

    Property age = Property.forName("age");
    List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.disjunction()
        .add( age.isNull() )
        .add( age.eq( new Integer(0) ) )
        .add( age.eq( new Integer(1) ) )
        .add( age.eq( new Integer(2) ) )
    ) )
    .add( Property.forName("name").in( 
        new String[] { "Fritz", "Izi", "Pk" } ) 
    )
    .list();

Criteria API?

    StudentTable.filter(row: StudentTable => ...)

Criteria API?

  
  StudentTable.filter(myExtractedFilter)


  def myExtractedFilter(row: StudentTable): Rep[Boolean] = {
    row.name === "Tom" && row.nationality === "American"
  }



    StudentTable
        .map(s => (s.name, s.surname))
        .distinctOn(_._1)
    
    select "NAME", min("SURNAME") 

    from "STUDENT" 

    group by "NAME"

Kotlin

Ratpack

Hadoop

Akka

Kafka

Kubernetes

AWS

Engineering spirit

Quality orientation

Experienced people to learn from 

Pair programming

Community

Autonomy (technical / processes)

Slick 101 - 2018

By Pawel Dolega

Slick 101 - 2018

  • 1,808