タイプスクリプトとタイプを使用したGraphSQLクエリのフィルタリング
この記事では、GraphSQL APIに加えることのできるフィルタリング機能について説明します.場合によっては、1つまたは複数のマッチングルールまたは様々なルールの構成を適用することによってデータをフィルタリングする必要があります.
このアイデアは、我々のGraphSQL APIを通してORMのような問い合わせ能力を公開することです.そこでは、クライアント側で柔軟性を持ち、複雑なデータを求めることができます.
リレーショナルデータベースのテーブルの例を次に示します
ID
名称
シティ
事後コード
11
ハリーアッシュワース
ロンドン
EC 2 5 NT
12
パトリシシ・シンプソン
ブエノスアイレス
51010
13
ビクトリアチャン
ロンドン
N 6 4 AL
ID
カスタマーキューID
オルデナリ
量
製品
10289
11
2016 - 08 - 26
30
おもちゃ
10290
11
2016 - 08 - 27
25
プログラミング書籍
10410年
12
2017 - 01 - 10
49
プログラミング書籍
10411
12
2017 - 03 - 15
34
フィクション
10259
13
2016 - 07 - 18
10
おもちゃ
畝
TypeORM は様々なJavaScriptプラットフォーム(例えばnode . js)で実行できるORMです.これらは、上記で紹介されているテーブルにマッチするタイプのエンティティクラスです.
畝
さて、クライアント側が上記のデータについて複雑な質問をする必要があるとしましょう.例えば以下のようになります.
見るhere 式ツリーの詳細については
畝
では、このようなクエリを実行できるようにするためのGraphSQLスキーマを作成しましょう.
葉節は
フィルタ式 *指定した論理式を構築するとき
フィルタ
原子フィルター用のGraphSQL型(式ツリーの葉ノード).例えば、 それでは、サーバー側のgraphqlスキーマの残りを見てみましょう.はい、
クライアント側では、私たちは
さて、このフィルタリングされたクエリを実装する準備が整いました.してください.
OrderResolver.TS
FilterQueryBuilder.TS
ジョイントビルダー.TS
WhereBuilderTS
最後に、これは
このようなGraphSQL APIは、クライアントアプリケーションのための大きな柔軟性と制御を提供します.これらはすべて、安全性と実行時の検証を維持している間、GraphSQLからボックスから取得します.
あなたのデータをフィルタリングするルールの異なる組み合わせでは、正確にあなたが興味を持っているデータを表現することができますし、あなたのためにそれを取得するバックエンドを聞かせてください.
同様に、我々はさらにそれを強化する私たちのクエリにソートとページ付けを追加することもできます.
畝
このアイデアは、我々のGraphSQL APIを通してORMのような問い合わせ能力を公開することです.そこでは、クライアント側で柔軟性を持ち、複雑なデータを求めることができます.
リレーショナルデータベースのテーブルの例を次に示します
顧客
ID
名称
シティ
事後コード
11
ハリーアッシュワース
ロンドン
EC 2 5 NT
12
パトリシシ・シンプソン
ブエノスアイレス
51010
13
ビクトリアチャン
ロンドン
N 6 4 AL
注文
ID
カスタマーキューID
オルデナリ
量
製品
10289
11
2016 - 08 - 26
30
おもちゃ
10290
11
2016 - 08 - 27
25
プログラミング書籍
10410年
12
2017 - 01 - 10
49
プログラミング書籍
10411
12
2017 - 03 - 15
34
フィクション
10259
13
2016 - 07 - 18
10
おもちゃ
畝
エンティティクラス
TypeORM は様々なJavaScriptプラットフォーム(例えばnode . js)で実行できるORMです.これらは、上記で紹介されているテーブルにマッチするタイプのエンティティクラスです.
@Entity("Customers")
export class CustomerEntity {
@PrimaryGeneratedColumn({ type: "int", name: "id" })
id: number;
@Column("varchar", { name: "name", length: 255 })
name: string;
@Column("varchar", { name: "city", length: 255 })
city: string;
@Column("varchar", { name: "postal_code", length: 255 })
postalCode: string;
@OneToMany(() => OrderEntity, order => order.customer)
orders: OrderEntity[];
}
@Entity("Orders")
export class OrderEntity {
@PrimaryGeneratedColumn({ type: "int", name: "id" })
id: number;
@Column("datetime", { name: "order_date" })
orderDate: Date;
@Column("int", { name: "quantity" })
quantity: number;
@Column("varchar", { name: "product", length: 255 })
product: string;
@ManyToOne(() => CustomerEntity, customer => customer.orders)
@JoinColumn({name: 'customer_id', referencedColumnName: 'id'})
customer: CustomerEntity;
}
各クラスの末尾の追加リレーションフィールドに注意してください.これら二つのテーブルの関係を説明します.私たちのケースでは、顧客と注文の間の多くの関係に1つです.だから顧客は多くの注文を持つことができます.畝
クエリフィルタ
さて、クライアント側が上記のデータについて複雑な質問をする必要があるとしましょう.例えば以下のようになります.
Get all orders that:
(have quantity >= 20) AND (ordered books) AND [
(order date >= '2016-08-27') OR (customers with ids 11, 12) OR
(customers whose postal code contains '5NT')
]
この複雑なクエリをSQL形式でアトミックフィルタに分割できます.a = (OrderEntity.quantity >= 20)
b = (OrderEntity.product LIKE '%books%')
c = (OrderEntity.orderDate >= '2016-08-27')
d = (CustomerEntity.id IN (11, 12))
e = (CustomerEntity.postalCode LIKE '%5NT%')
論理式は次のようになります.a AND b AND (c OR d OR e)
この論理式は式ツリーとして表現できます:見るhere 式ツリーの詳細については
畝
グラフィカルなスキーマ
では、このようなクエリを実行できるようにするためのGraphSQLスキーマを作成しましょう.
enum Operator {
AND
OR
}
enum Operation {
EQ
IN
LIKE
GE
}
input Filter {
op: Operation!
values: [String!]!
field: String!
relationField: String
}
input FiltersExpression {
operator: Operator!
filters: [Filter!]
childExpressions: [FiltersExpression!]
}
メインタイプはこちらFiltersExpression
式ツリーの非葉節に対応します.これらは' AND 'または' OR 'のような論理演算ノードです.葉節は
Filter
種類これらは、私たちが小文字(A、B、C、D、E)と名付けた原子フィルターです.フィルタ式
operator
- 論理演算子('、' OR ')filters
- このノードの子孫原子フィルター(葉節).childExpressions
- 子孫のサブ式.FiltersExpression
ノードoperator
を返します.フィルタ
原子フィルター用のGraphSQL型(式ツリーの葉ノード).例えば、
CustomerEntity.id IN (11, 12)
.op
- 原子フィルターの条件付き操作op: IN
). values
- フィルタの値values: 11, 12
). field
- テーブルのフィールド名field: CustomerEntity.id
). relationField
- このオプションのパラメータは、メインテーブルの外部キーを表し、field
. typeORMでは、このクラスをエンティティークラスに追加して、テーブル間の関係を示します.relationField
SQLクエリでこれらのテーブルに参加するために使用されます.(この例ではrelationField: OrderEntity.customer
). Customer
and Order
typem実体に従った型、およびgetOrders
フィルタ式を受け取ったクエリを返します. type Customer {
id: Int!
name: String!
city: String
postalCode: String!
}
type Order {
id: Int!
customer: Customer!
orderDate: String!
quantity: Int!
product: String!
}
extend type Query {
getOrders(filters: FiltersExpression): [Order!]!
}
畝クライアント側
クライアント側では、私たちは
getOrders
我々の主な例からその複雑なフィルタ式を使用している質問.クライアントが送信したGraphSQLクエリの本文です.query getOrders {
getOrders(filters: {
operator: AND
filters: [
{
field: "OrderEntity.quantity"
op: GE
values: ["20"]
},
{
field: "OrderEntity.product"
op: LIKE
values: ["books"]
}
]
childExpressions: [
{
operator: OR
filters: [
{
field: "OrderEntity.orderDate"
op: GE
values: ["2016-08-27"]
},
{
field: "CustomerEntity.id"
relationField: "OrderEntity.customer"
op: IN
values: ["11", "12"]
},
{
field: "CustomerEntity.postalCode"
relationField: "OrderEntity.customer"
op: LIKE
values: ["5NT"]
}
]
}
]
}) {
id
orderDate
quantity
product
customer {
name
city
}
}
}
畝サーバ側
さて、このフィルタリングされたクエリを実装する準備が整いました.してください.
getOrders
:OrderResolver.TS
import {getRepository} from 'typeorm';
import {SelectQueryBuilder} from 'typeorm/query-builder/SelectQueryBuilder';
import {OrderEntity} from './OrderEntity';
export const resolvers = {
Query: {
getOrders: (parent, {filters}): Promise<OrderEntity[]> => {
const ordersRepo = getRepository(OrderEntity);
const fqb = new FilterQueryBuilder<OrderEntity>(ordersRepo, filters);
const qb: SelectQueryBuilder = fqb.build();
return qb.getMany();
}
}
リゾルバはFilterQueryBuilder
typeRMクエリを構築するにはそれをつくりましょう.FilterQueryBuilder.TS
import {Repository} from 'typeorm';
import {SelectQueryBuilder} from 'typeorm/query-builder/SelectQueryBuilder';
export default class FilterQueryBuilder<Entity> {
private readonly qb: SelectQueryBuilder<Entity>;
constructor(entityRepository: Repository<Entity>,
private filtersExpression?: FiltersExpression) {
this.qb = entityRepository.createQueryBuilder();
}
build() {
const jb = new JoinBuilder<Entity>(this.qb, this.filtersExpression);
jb.build();
const wb = new WhereBuilder<Entity>(this.qb, this.filtersExpression);
wb.build();
return this.qb;
}
}
JoinBuilder
FiltersExpressionを再帰的に横切って、それぞれのために左の結合を加えますrelationField
.ジョイントビルダー.TS
import {forEach} from 'lodash';
import {SelectQueryBuilder} from 'typeorm/query-builder/SelectQueryBuilder';
class JoinBuilder<Entity> {
private joinedEntities = new Set<string>();
constructor(private readonly qb: SelectQueryBuilder<Entity>,
private filtersExpression?: FiltersExpression) {
};
build() {
if (this.filtersExpression)
this.buildJoinEntitiesRec(this.filtersExpression);
}
private buildJoinEntitiesRec(fe: FiltersExpression) {
forEach(fe.filters, f => this.addJoinEntity(f.field, f.relationField));
forEach(fe.childExpressions, child => this.buildJoinEntitiesRec(child));
}
private addJoinEntity(field: string, relationField?: string) {
const entityName = field.split('.')[0];
if (relationField && !this.joinedEntities.has(entityName)) {
this.qb.leftJoinAndSelect(relationField, entityName);
this.joinedEntities.add(entityName);
}
}
}
WhereBuilder
再帰的にフィルタ式ツリーを越え、SQLクエリのWHERE句を構築します.WhereBuilderTS
import { isEmpty, map } from 'lodash';
import {SelectQueryBuilder} from 'typeorm/query-builder/SelectQueryBuilder';
type ParamValue = string | number | Array<string|number>;
export default class WhereBuilder<Entity> {
private params: Record<string, ParamValue> = {};
private paramsCount = 0;
constructor(private readonly qb: SelectQueryBuilder<Entity>,
private filtersExpression?: FiltersExpression) {
};
build() {
if (!this.filtersExpression)
return;
const whereSql = this.buildExpressionRec(this.filtersExpression);
this.qb.where(whereSql, this.params);
}
private buildExpressionRec(fe: FiltersExpression): string {
const filters = map(fe.filters, f => this.buildFilter(f));
const children = map(fe.childExpressions, child => this.buildExpressionRec(child));
const allSqlBlocks = [...filters, ...children];
const sqLExpr = allSqlBlocks.join(` ${fe.operator} `);
return isEmpty(sqLExpr) ? '' : `(${sqLExpr})`;
}
private buildFilter(filter: Filter): string {
const paramName = `${filter.field}_${++this.paramsCount}`;
switch (filter.op) {
case 'EQ':
this.params[paramName] = filter.values[0];
return `${filter.field} = :${paramName}`;
case 'IN':
this.params[paramName] = filter.values;
return `${filter.field} IN (:${paramName})`;
case 'LIKE':
this.params[paramName] = `%${filter.values[0]}%`;
return `${filter.field} LIKE :${paramName}`;
case 'GE':
this.params[paramName] = filter.values[0];
return `${filter.field} >= :${paramName}`;
default:
throw new Error(`Unknown filter operation: ${filter.op}`);
}
}
}
畝最後に、これは
FilterQueryBuilder
以下に例を示します:SELECT *
FROM Orders o,
LEFT JOIN Customers c ON o.customer_id = c.id
WHERE (o.quantity >= 20) AND (o.product LIKE '%books%')
AND
(
(o.order_date >= '2016-08-27') OR
(c.id IN (11, 12)) OR
(c.postal_code LIKE '%5NT%')
)
畝結論
このようなGraphSQL APIは、クライアントアプリケーションのための大きな柔軟性と制御を提供します.これらはすべて、安全性と実行時の検証を維持している間、GraphSQLからボックスから取得します.
あなたのデータをフィルタリングするルールの異なる組み合わせでは、正確にあなたが興味を持っているデータを表現することができますし、あなたのためにそれを取得するバックエンドを聞かせてください.
同様に、我々はさらにそれを強化する私たちのクエリにソートとページ付けを追加することもできます.
畝
Reference
この問題について(タイプスクリプトとタイプを使用したGraphSQLクエリのフィルタリング), 我々は、より多くの情報をここで見つけました https://dev.to/mgustus/filtering-graphql-query-using-typescript-and-typeorm-2l49テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol