Scala slick метод, который я пока не понимаю

Я пытаюсь понять некоторые работы Slick и то, что он требует.

Вот пример:

package models

case class Bar(id: Option[Int] = None, name: String)

object Bars extends Table[Bar]("bar") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  // This is the primary key column
  def name = column[String]("name")

  // Every table needs a * projection with the same type as the table type parameter
  def * = id.? ~ name <>(Bar, Bar.unapply _)
}

Может ли кто-нибудь объяснить мне, что цель метода * здесь, что такое <>, почему unapply? и что такое Projection-method ~ 'возвращает экземпляр Projection2?

Ответ 1

[ОБНОВЛЕНИЕ] - добавлено (еще одно) объяснение в for понятиях

  • Метод *:

    Это возвращает проекцию по умолчанию - вот как вы описываете:

    'все столбцы (или вычисленные значения) меня обычно интересуют.

    В вашей таблице может быть несколько полей; вам нужно только подмножество для ваша проекция по умолчанию. Проекция по умолчанию должна соответствовать типу параметры таблицы.

    Пусть возьмем его по одному. Без содержимого <> просто *:

    // First take: Only the Table Defintion, no case class:
    
    object Bars extends Table[(Int, String)]("bar") {
      def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
      def name = column[String]("name")
    
      def * = id ~ name // Note: Just a simple projection, not using .? etc
    }
    
    // Note that the case class 'Bar' is not to be found. This is 
    // an example without it (with only the table definition)
    

    Просто определение таблицы, подобное этому, позволит вам делать такие запросы, как:

    implicit val session: Session = // ... a db session obtained from somewhere
    
    // A simple select-all:
    val result = Query(Bars).list   // result is a List[(Int, String)]
    

    проекция по умолчанию (Int, String) приводит к List[(Int, String)] для простых запросов, таких как эти.

    // SELECT b.name, 1 FROM bars b WHERE b.id = 42;
    val q = 
       for (b <- Bars if b.id === 42) 
         yield (b.name ~ 1)
         // yield (b.name, 1) // this is also allowed: 
                              // tuples are lifted to the equivalent projection.
    

    Какой тип q? Это Query с проекцией (String, Int).  При вызове он возвращает List корней (String, Int) в соответствии с проекцией.

     val result: List[(String, Int)] = q.list
    

    В этом случае вы определили нужный прогноз в предложении yield  понимания for.

  • Теперь о <> и Bar.unapply.

    Это обеспечивает так называемые Mapped Projections.

    До сих пор мы видели, как slick позволяет вам выражать запросы в Scala которые возвращают проекцию столбцов (или вычисленных значений); Поэтому при выполнении эти запросы вы должны думать о строке результата запроса как Scala кортеж. Тип кортежа будет соответствовать прогнозу, который определен (вашим for, как в предыдущем примере, по умолчанию *). Вот почему field1 ~ field2 возвращает проекцию Projection2[A, B] где A - тип field1, а B - тип field2.

    q.list.map {
      case (name, n) =>  // do something with name:String and n:Int
    }
    
    Queury(Bars).list.map {
      case (id, name) =>  // do something with id:Int and name:String 
    }
    

    Мы имеем дело с кортежами, которые могут быть громоздкими, если у нас слишком много колонны. Мы хотели бы думать о результатах не как TupleN, а о некоторых объект с именованными полями.

    (id ~ name)  // A projection
    
    // Assuming you have a Bar case class:
    case class Bar(id: Int, name: String) // For now, using a plain Int instead
                                          // of Option[Int] - for simplicity
    
    (id ~ name <> (Bar, Bar.unapply _)) // A MAPPED projection
    
    // Which lets you do:
    Query(Bars).list.map ( b.name ) 
    // instead of
    // Query(Bars).list.map { case (_, name) => name }
    
    // Note that I use list.map instead of mapResult just for explanation sake.
    

    Как это работает? <> принимает проекцию Projection2[Int, String] и возвращает отображаемую проекцию типа Bar. Два аргумента Bar, Bar.unapply _ скажите, насколько эта проекция (Int, String) должна быть сопоставлена ​​с классом case.

    Это двухстороннее отображение; Bar - это конструктор класса case, так что информацию, необходимую для перехода от (id: Int, name: String) к Bar. И unapply если вы догадались, это для обратного.

    Откуда unapply? Это стандартный метод Scala, доступный для любой обычный класс case - только определение Bar дает вам Bar.unapply, который это экстрактор, который можно использовать для возврата id и name, который Bar был создан с помощью

    val bar1 = Bar(1, "one")
    // later
    val Bar(id, name) = bar1  // id will be an Int bound to 1,
                              // name a String bound to "one"
    // Or in pattern matching
    val bars: List[Bar] = // gotten from somewhere
    val barNames = bars.map {
      case Bar(_, name) => name
    }
    
    val x = Bar.unapply(bar1)  // x is an Option[(String, Int)]
    

    Итак, ваша проекция по умолчанию может быть сопоставлена ​​с классом case, который вы больше всего ожидаете использовать:

    object Bars extends Table[Bar]("bar") {
      def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
      def name = column[String]("name")
      def * = id ~ name <>(Bar, Bar.unapply _)
    }
    

    Или вы можете даже получить его для запроса:

    case class Baz(name: String, num: Int)
    
    // SELECT b.name, 1 FROM bars b WHERE b.id = 42;
    val q1 = 
       for (b <- Bars if b.id === 42) 
         yield (b.name ~ 1 <> (Baz, Baz.unapply _))
    

    Здесь тип q1 - это Query с отображенной проекцией на Baz.  При вызове он возвращает List объектов Baz:

     val result: List[Baz] = q1.list
    
  • Наконец, в стороне .? предлагает Option Lifting - Scala способ которые не могут быть.

     (id ~ name)   // Projection2[Int, String] // this is just for illustration
     (id.? ~ name) // Projection2[Option[Int], String]
    

    Что, обертывание, будет хорошо работать с вашим исходным определением Bar:

    case class Bar(id: Option[Int] = None, name: String)
    
    // SELECT b.id, b.name FROM bars b WHERE b.id = 42;
    val q0 = 
       for (b <- Bars if b.id === 42) 
         yield (b.id.? ~ b.name <> (Bar, Bar.unapply _))
    
    
    q0.list // returns a List[Bar]
    
  • В ответ на комментарий о том, как Slick использует for -понимание:

    Каким-то образом монады всегда могут появиться и потребовать быть частью объяснения...

    Понимание не относится только к коллекциям. Они могут использоваться в любом виде Монады, а коллекции - только один из многих видов монадов, доступных в Scala.

    Но, поскольку коллекции знакомы, они делают хороший старт укажите для объяснения:

    val ns = 1 to 100 toList; // Lists for familiarity
    val result = 
      for { i <- ns if i*i % 2 == 0 } 
        yield (i*i)
    // result is a List[Int], List(4, 16, 36, ...)
    

    В Scala для понимания используется синтаксический сахар для метод (возможно, вложенный) метод: приведенный выше код (более или менее) эквивалентно:

    ns.filter(i => i*i % 2 == 0).map(i => i*i)
    

    В принципе, все с filter, map, flatMap методы (другими словами, Монада) могут быть использованы в for понимание вместо ns. Хороший пример это Option monad. Здесь предыдущий пример где тот же оператор for работает как на List, а также Option monads:

    // (1)
    val result = 
      for { 
        i <- ns          // ns is a List monad
        i2 <- Some(i*i)  // Some(i*i) is Option
          if i2 % 2 == 0 // filter
      } yield i2
    
    // Slightly more contrived example:
    def evenSqr(n: Int) = { // return the square of a number 
      val sqr = n*n         // only when the square is even
      if (sqr % 2 == 0) Some (sqr)
      else None
    }
    
    // (2)
    result = 
      for { 
        i <- ns  
        i2 <- evenSqr(i) // i2 may/maynot be defined for i!
      } yield i2
    

    В последнем примере преобразование, возможно, будет выглядеть например:

    // 1st example
    val result = 
      ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0)
    
    // Or for the 2nd example
    result = 
      ns.flatMap(i => evenSqr(i)) 
    

    В Slick запросы являются монадическими - это просто объекты с методы map, flatMap и filter. Итак, понимание for (показано в объяснении метода *), просто переводится на:

    val q = 
      Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1)
    // Type of q is Query[(String, Int)]
    
    val r: List[(String, Int)] = q.list // Actually run the query
    

    Как вы можете видеть, flatMap, map и filter используются для сгенерируйте a Query повторным преобразованием Query(Bars) с каждым вызовом filter и map. В случае коллекции эти методы на самом деле итерации и фильтрации коллекции но в Slick они используются для генерации SQL. Подробнее здесь: Как Scala Slick переводит код Scala в JDBC?

Ответ 2

Поскольку никто другой не ответил, это может помочь вам начать работу. Я не очень хорошо знаю Slick.

В Документация Slick:

Поднятое вложение:

Для каждой таблицы требуется * метод, связанный с проекцией по умолчанию. Эта описывает, что вы возвращаете, когда возвращаете строки (в форме объект таблицы) из запроса. Проекция Slicks * не должна соответствовать таковой в базе данных. Вы можете добавить новые столбцы (например, с помощью вычисленные значения) или опустить некоторые столбцы по своему усмотрению. Тип без поднятия соответствующий проекции *, задается как параметр типа для Таблица. Для простых, не отображаемых таблиц это будет один столбец тип или кортеж типов столбцов.

Другими словами, slick должен знать, как обращаться со строкой, возвращаемой из базы данных. Определенный вами метод использует функции комбинаторных парсеров для объединения определений столбцов во что-то, что можно использовать в строке.