🤔

あのprisma previewFeaturesは今?

に公開

はじめに

はじめまして、株式会社トリドリでバックエンドを担当しているyrmzです。
私が所属するチームではNestJSをメインに開発業務を行っています。

Prismaが発行するクエリ

ORMはprismaを使っているのですが、複数テーブルを取得しようとした際に複数クエリが発行されてしまうという問題があります。
例えば次のコードを実行したとしましょう。

await this.prisma.company.findUnique({
  where: { id },
  include: {
    shop: true,
  },
});

これに対して大抵の人は以下のようなクエリが発行されると期待するでしょう。

SELECT *
FROM company c
JOIN shop s ON c.id = s.company_id
WHERE c.id = @id;

しかし、残念ながら発行されるのは次のようなクエリです。

SELECT
    company.id,
    company.name,
    company.created_at,
    company.updated_at
FROM company
WHERE
    (
      // @idには実際の数字が入る
        company.id = @id
        AND 1 = 1
    )
LIMIT 1
OFFSET 0;

SELECT
    shop.id,
    shop.name,
    shop.created_at,
    shop.updated_at
FROM shop
// @campaign_idには実際の数字が入る
WHERE shop.company_id IN (@company_id);

というクエリが発行されてしまいます。

このようなクエリでも目的は達成できますが、JOINを使うよりも取得効率が悪くなってしまいます。

担当プロジェクトではGraphQLを使ってフロントエンドと通信しています。REST APIと比べると複数テーブルをまとめて取得するケースは少ないものの、サービスが今後も成長を続けていくことも考えると無視できる問題ではありません。

生クエリを書くことで回避できますが、previewFeaturesとしてrelationJoinsが2024.01.10から提供が開始されました。

2024年も年末ということで、この機能は今どんな状況なのか調べてみました。

relationJoinsとは?

relationJoinsは発行するクエリをjoinするかどうかを選択できる機能です。

この機能は以下のバージョンから提供を開始しました。

  • PostgreSQL:v5.8.0(2024.01.10〜)
  • MySQL:v.5.10.0(2024.02.21〜)

relationJoinsを使ってみる

prisma.schemaでrelationJoins を有効にし、npx prisma generate を実行することで機能が使えるようになります。

generator client {
  provider        = "prisma-client-js"
  engineType      = "binary"
  previewFeatures = ["relationJoins"]
}

有効にするとrelationLoadStrategyが設定できるようになり、join でのクエリ発行ができます。

    await this.prismaService.company.findUnique({
      relationLoadStrategy: "join",
      where: { databaseId },
      include: {
        shop: true,
      },
    });

上記のクエリの発行結果は

SELECT `t1`.`id`,`t1`.`name`,`t1`.`created_at` AS `createdAt`,`t1`.`updated_at` AS `updatedAt`,
    (
        SELECT COALESCE(JSON_ARRAYAGG(`__prisma_data__`),CONVERT('[]', JSON)) AS `__prisma_data__`
        FROM (
            SELECT `t4`.`__prisma_data__`
            FROM (
                SELECT JSON_OBJECT('id',`t3`.`id`,'name',`t3`.`name`,'createdAt',`t3`.`created_at`,'updatedAt',`t3`.`updated_at`) AS `__prisma_data__`
                FROM(
                    SELECT `t2`.*
                    FROM `shop` AS `t2`
                    WHERE `t1`.`id` = `t2`.`company_id`
                    /* root select */
                ) AS `t3`
            /* inner select */
            ) AS `t4`
        /* middle select */
        ) AS `t5`
    /* outer select */
    ) AS `shops`
FROM `company` AS `t1`
WHERE (`t1`.`id` = @id AND 1 = 1)
LIMIT 1;

期待していたよりも複雑なクエリが発行されてしまいました。

このクエリをよく見てみるとSELECT句のサブクエリとしてoffersテーブルを取得しています。

JOINは実行されておらず期待していたクエリとは異なることがわかります。

サブクエリで取得したカラムはJSON_ARRAYAGGでjsonとして取得しアプリケーション側でマージしていると考えられます。

リリースはいつ?

JOINしていないクエリが発行されているなどリリースに向けた対応はまだ必要そうです。

Discussionによると、2024/11/14時点では直近でのリリースは計画されていないようですが、優先度は高く対応していくようです。

GitHubで編集を提案
toridori tech blog

Discussion