Статьи

SQL-запрос DSL для Scala от ScalikeJDBC

Существует огромное количество SQL API, изначально написанных на Scala. Мануэль Бернхардт подытожил прекрасную коллекцию в своем посте . В этом вопросе о переполнении стека можно увидеть другую коллекцию API-интерфейсов Scala SQL .

В частности, одним из API, на который мы хотим обратить внимание, является ScalikeJDBC ( лицензированный ASL 2.0 ), который недавно опубликовал API-интерфейс DSL для SQL-запросов, аналогичный jOOQ . Смотрите полную документацию здесь:

http://scalikejdbc.org/documentation/query-dsl.html

Пара примеров:

01
02
03
04
05
06
07
08
09
10
val orders: List[Order] = withSQL {
  select
    .from(Order as o)
    .innerJoin(Product as p).on(o.productId, p.id)
    .leftJoin(Account as a).on(o.accountId, a.id)
    .where.eq(o.productId, 123)
    .orderBy(o.id).desc
    .limit(4)
    .offset(0)
  }.map(Order(o, p, a)).list.apply()

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
def findOrder(id: Long, accountRequired: Boolean) =
withSQL {
  select
    .from[Order](Order as o)
    .innerJoin(Product as p).on(o.productId, p.id)
    .map { sql =>
      if (accountRequired)
        sql.leftJoin(Account as a)
           .on(o.accountId, a.id)
      else
        sql
    }.where.eq(o.id, 13)
  }.map { rs =>
    if (accountRequired)
      Order(o, p, a)(rs)
    else
      Order(o, p)(rs)
  }.single.apply()

Из того, как мы понимаем вещи, метод map который вызывается в середине оператора SQL (между innerJoin и where ), может преобразовать промежуточное состояние DSL с помощью лямбда-выражения, которое позволяет leftJoin если это необходимо. Очевидно, что это можно сделать и более процедурным образом, назначив это промежуточное состояние DSL локальной переменной.

Необходимость DSL-запросов SQL

В прошлом мы писали о многих подобных DSL-запросах SQL. Тот факт, что они постоянно появляются в различных API, не случайно. SQL является очень безопасным и пригодным для компоновки языком, который трудно использовать динамически через строковые API, такие как JDBC, ODBC и т. Д.

Наличие безопасной для типов внутренней доменной языковой модели SQL на языке хоста, таком как Java или Scala, дает большие преимущества. Но недостатки могут быстро проявиться, если DSL не будет тщательно продуман до предела. Взять, к примеру, следующий пример ScalikeJDBC QueryDSL:

1
2
3
4
5
6
7
8
val ids = withSQL {
  select(o.result.id).from(Order as o)
    .where(sqls.toAndConditionOpt(
      productId.map(id => sqls.eq(o.productId, id)),
      accountId.map(id => sqls.eq(o.accountId, id))
    ))
    .orderBy(o.id)
}.map(_.int(1)).list.apply()

Этот метод toAndConditionOpt действительно неожиданный и не следует принципу наименьшего удивления .

Вот почему дизайн API jOOQ основан на формальной BNF, которая очень похожа на сам SQL. Подробнее об этом читайте здесь .

Ссылка: SQL-запрос DSL для Scala от ScalikeJDBC от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ .