Поддержка SQL в стиле коллекции Scala, как в LINQ

Насколько я понимаю, единственное, что поддерживает LINQ, чего в настоящее время нет в Scala с библиотекой коллекций, - это интеграция с базой данных SQL.

Насколько я понимаю, LINQ может "накапливать" различные операции и может выдавать "полный" оператор в базу данных при запросе для его обработки там, предотвращая это простое SELECT сначала копирует всю таблицу в структуры данных виртуальной машины.

Если я ошибаюсь, я был бы счастлив, чтобы меня поправили.

Если нет, что необходимо для поддержки того же в Scala?

Разве не возможно написать библиотеку, которая реализует интерфейс коллекции, но не имеет никаких структур данных, поддерживающих ее, кроме строки, которая собирается с последующей коллекцией в требуемый оператор базы данных?

Или я совершенно не прав с моими наблюдениями?

5 ответов

Решение

Как автор ScalaQuery, мне нечего добавить к объяснению Стилгара. Часть LINQ, которая отсутствует в Scala, действительно является деревьями выражений. По этой причине ScalaQuery выполняет все свои вычисления для типов Column и Table вместо базовых типов этих объектов.

Вы объявляете таблицу как объект Table с проекцией (кортежем) ее столбцов, например:

class User extends Table[(Int, String)] {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def * = id ~ name
}

User.id и User.name теперь имеют тип Column[Int] и Column[String] соответственно. Все вычисления выполняются в монаде Query (которая является более естественным представлением запросов к базе данных, чем операторы SQL, которые должны быть созданы из нее). Возьмите следующий запрос:

val q = for(u <- User if u.id < 5) yield u.name

После некоторых неявных преобразований и отладки это приводит к:

val q:Query[String] =
  Query[User.type](User).filter(u => u.id < ConstColumn[Int](5)).map(u => u.name)

Методы filter и map не должны проверять свои аргументы как деревья выражений для построения запроса, они просто запускают их. Как вы можете видеть из типов, то, что внешне выглядит как "u.id:Int < 5:Int", на самом деле является "u.id:Column[Int]

Затем QueryBuilder использует этот AST для создания фактического оператора SQL для базы данных (с синтаксисом, специфичным для СУБД).

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

Я должен отметить, что в Scala есть экспериментальная поддержка деревьев выражений. Если вы передаете анонимную функцию в качестве аргумента методу, ожидающему параметр типа scala.reflect.Code[A]Вы получаете АСТ.

scala> import scala.reflect.Code      
import scala.reflect.Code 
scala> def codeOf[A](code: Code[A]) = code
codeOf: [A](code:scala.reflect.Code[A])scala.reflect.Code[A]
scala> codeOf((x: Int) => x * x).tree 
res8: scala.reflect.Tree=Function(List(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Apply(Select(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Method(scala.Int.$times,MethodType(List(LocalValue(NoSymbol,x$1,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),PrefixedType(ThisType(Class(scala)),Class(scala.Int))))),List(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))))))

Это было использовано в библиотеке генерации байт-кода 'Mnemonics', которая была представлена ее автором Йоханнесом Рудольфом на Scala Days 2010.

С LINQ компилятор проверяет, скомпилировано ли лямбда-выражение в IEnumerable или в IQueryable. Первые работы, как коллекции Scala. Второй компилирует выражение в дерево выражений (то есть структуру данных). Сила LINQ заключается в том, что сам компилятор может переводить лямбда-выражения в деревья выражений. Вы можете написать библиотеку, которая создает деревья выражений с интерфейсом, похожим на тот, который у вас есть для коллекции, но как вы собираетесь заставить компилятор создавать структуры данных (вместо кода JVM) из лямбд?

При этом я не уверен, что Scala предоставляет в этом отношении. Возможно, в Scala можно построить структуры данных из лямбд, но в любом случае я считаю, что вам нужна аналогичная функция в компиляторе для обеспечения поддержки баз данных. Помните, что базы данных - не единственный источник данных, для которого вы можете создавать провайдеров. Существует множество поставщиков LINQ, например Active Directory или Ebay API.

Изменить: Почему не может быть только API?

Для выполнения запросов вы не только используете методы API (фильтр, где и т. Д.), Но также используете лямбда-выражения в качестве аргументов этих методов.Where(x => x > 3) (C# LINQ). Компиляторы переводят лямбда-выражения в байт-код. API должен создавать структуры данных (деревья выражений), чтобы вы могли преобразовать структуру данных в базовый источник данных. В основном вам нужен компилятор, чтобы сделать это за вас.

Отказ от ответственности 1: Возможно (просто возможно) есть какой-то способ создания прокси-объектов, которые выполняют лямбда-выражения, но перегружают операторы для создания структур данных. Это приведет к несколько худшей производительности, чем фактическое LINQ (время выполнения или время компиляции). Я не уверен, возможна ли такая библиотека. Возможно, библиотека ScalaQuery использует аналогичный подход.

Отказ от ответственности 2: Возможно, язык Scala на самом деле может предоставлять лямбды в качестве проверяемых объектов, чтобы вы могли получить дерево выражений. Это сделало бы лямбда-функцию в Scala эквивалентной функции в C#. Возможно, библиотека ScalaQuery использует эту гипотетическую функцию.

Редактировать 2: я немного копал. Похоже, что ScalaQuery использует библиотечный подход и перегружает группу операторов для создания деревьев во время выполнения. Я не совсем уверен в деталях, потому что я не знаком с терминологией Scala и мне сложно читать сложный код Scala в статье: http://szeiger.de/blog/2008/12/21/a-type-safe-database-query-dsl-for-scala/

Как и для любого объекта, который может использоваться в запросе или возвращаться из него, таблица параметризируется в соответствии с типом значений, которые она представляет. Это всегда кортеж отдельных типов столбцов, в нашем случае - Integer и Strings (обратите внимание на использование java.lang.Integer вместо Int; подробнее об этом позже). В этом отношении SQuery (как я его сейчас назвал) ближе к HaskellDB, чем к LINQ, потому что Scala (как и большинство языков) не дает вам доступа к AST выражения во время выполнения. В LINQ вы можете писать запросы, используя реальные типы значений и столбцов в вашей базе данных, и преобразовывать AST выражения запроса в SQL во время выполнения. Без этой опции мы должны использовать мета-объекты, такие как Table и Column, чтобы построить из них наш собственный AST.

Очень классная библиотека на самом деле. Я надеюсь, что в будущем он получит любовь, которую заслуживает, и станет инструментом, готовым к производству.

Вы, вероятно, хотите что-то вроде http://scalaquery.org/. Он делает именно то, что предлагает ответ @Stilgar, за исключением того, что это только SQL.

Проверить http://squeryl.org/

Другие вопросы по тегам