web-dev-qa-db-ja.com

構造化オブジェクトからのTypeorm動的クエリビルダー

Graphqlサーバーで使用するために、prismaの動作と非常によく似たいくつかのフィルター条件を指定できる構造化入力タイプを定義しました。

enter image description here

これにより、次のようなクエリで構造化フィルターを送信できます。

_{
  users(
    where: {
      OR: [{ email: { starts_with: "ja" } }, { email: { ends_with: ".com" } }],
      AND: [{ email: { starts_with: "ja" } }, { email: { ends_with: ".com" } }],
      email: {contains: "lowe"}
    }
  ) {
    id
    email
  }
}
_

リゾルバー内で、関数を介してargs.whereをフィードして構造を解析し、TypeOrmのクエリビルダーを使用して適切なSQLに変換します。関数の全体は次のとおりです。

_import { Brackets } from "typeorm";

export const filterQuery = (query: any, where: any) => {
  if (!where) {
    return query;
  }

  Object.keys(where).forEach(key => {
    if (key === "OR") {
      where[key].map((queryArray: any) => {
        query.orWhere(new Brackets(qb => filterQuery(qb, queryArray)));
      });
    } else if (key === "AND") {
      where[key].map((queryArray: any) => {
        query.andWhere(new Brackets(qb => filterQuery(qb, queryArray)));
      });
    } else {
      const whereArgs = Object.entries(where);

      whereArgs.map(whereArg => {
        const [fieldName, filters] = whereArg;
        const ops = Object.entries(filters);

        ops.map(parameters => {
          const [operation, value] = parameters;

          switch (operation) {
            case "is": {
              query.andWhere(`${fieldName} = :isvalue`, { isvalue: value });
              break;
            }
            case "not": {
              query.andWhere(`${fieldName} != :notvalue`, { notvalue: value });
              break;
            }
            case "in": {
              query.andWhere(`${fieldName} IN :invalue`, { invalue: value });
              break;
            }
            case "not_in": {
              query.andWhere(`${fieldName} NOT IN :notinvalue`, {
                notinvalue: value
              });
              break;
            }
            case "lt": {
              query.andWhere(`${fieldName} < :ltvalue`, { ltvalue: value });
              break;
            }
            case "lte": {
              query.andWhere(`${fieldName} <= :ltevalue`, { ltevalue: value });
              break;
            }
            case "gt": {
              query.andWhere(`${fieldName} > :gtvalue`, { gtvalue: value });
              break;
            }
            case "gte": {
              query.andWhere(`${fieldName} >= :gtevalue`, { gtevalue: value });
              break;
            }
            case "contains": {
              query.andWhere(`${fieldName} ILIKE :convalue`, {
                convalue: `%${value}%`
              });
              break;
            }
            case "not_contains": {
              query.andWhere(`${fieldName} NOT ILIKE :notconvalue`, {
                notconvalue: `%${value}%`
              });
              break;
            }
            case "starts_with": {
              query
                .andWhere(`${fieldName} ILIKE :swvalue`)
                .setParameter("swvalue", `${value}%`);
              break;
            }
            case "not_starts_with": {
              query
                .andWhere(`${fieldName} NOT ILIKE :nswvalue`)
                .setParameter("nswvalue", `${value}%`);
              break;
            }
            case "ends_with": {
              query.andWhere(`${fieldName} ILIKE :ewvalue`, {
                ewvalue: `%${value}`
              });
              break;
            }
            case "not_ends_with": {
              query.andWhere(`${fieldName} ILIKE :newvalue`, {
                newvalue: `%${value}`
              });
              break;
            }
            default: {
              break;
            }
          }
        });
      });
    }
  });

  return query;
};
_

これは(ちょっと)機能しますが、私が期待するようにAND/ORクエリをネストしません(以前はKNEXで機能していました)。上記の関数はSQLを生成します。

SELECT "user"."id" AS "user_id", "user"."name" AS "user_name", "user"."email" AS "user_email", "user"."loginToken" AS "user_loginToken", "user"."loginTokenExpiry" AS "user_loginTokenExpiry", "user"."active" AS "user_active", "user"."visible" AS "user_visible", "user"."isStaff" AS "user_isStaff", "user"."isBilling" AS "user_isBilling", "user"."createdAt" AS "user_createdAt", "user"."updatedAt" AS "user_updatedAt", "user"."version" AS "user_version" FROM "user" "user" WHERE (email ILIKE $1) AND (email ILIKE $2) OR (email ILIKE $3) OR (email ILIKE $4) AND email ILIKE $5 -- PARAMETERS: ["ja%","%.com","ja%","%.com","%lowe%"]

しかし、私はもっと次のようなものを見ることを期待しています:

_..... 
WHERE email ILIKE '%low%' 
AND (
    email ILIKE 'ja%' AND email ILIKE '%.com'
) AND (
    email ILIKE 'ja%' OR email ILIKE '%.com'
)
_

ナンセンスで反復的なクエリを許してください。予想されるNESTEDステートメントを説明しようとしています。

クエリビルダー関数のAND/ORブランチを強制的に期待どおりに適切にネストするにはどうすればよいですか?

**誰かがここで実際のTypeScriptタイピングを理解するのを手伝ってくれるならボーナスポイント**

5
Jake Lowen

もっと複雑な検索をしなければなりません。これもこのユースケースをカバーしていますか?

{
  "AND":[
    {"name":{"contains":"Peter"}},
    {"OR": [
      {"phone_001":{"contains":"123455621"}},
      {"phone_002":{"contains":"123455621"}}
    ]}
  ]
}
0
StS