mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-05-10 22:31:15 +00:00
Support filters for array and derived type fields in plural GQL queries (#448)
* Handle nested filters for array type fields * Transform Decimal param values to strings for db queries * Handle nested filters for derived fields * Handle filters for array type fields
This commit is contained in:
parent
92f3fb8252
commit
7f37dac888
@ -22,6 +22,7 @@ import {
|
|||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
|
import Decimal from 'decimal.js';
|
||||||
|
|
||||||
import { BlockProgressInterface, ContractInterface, EventInterface, StateInterface, StateSyncStatusInterface, StateKind, SyncStatusInterface } from './types';
|
import { BlockProgressInterface, ContractInterface, EventInterface, StateInterface, StateSyncStatusInterface, StateKind, SyncStatusInterface } from './types';
|
||||||
import { MAX_REORG_DEPTH, UNKNOWN_EVENT_NAME } from './constants';
|
import { MAX_REORG_DEPTH, UNKNOWN_EVENT_NAME } from './constants';
|
||||||
@ -839,21 +840,32 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.entries(where).forEach(([field, filters]) => {
|
Object.entries(where).forEach(([field, filters]) => {
|
||||||
// TODO: Handle nested filters on derived and array fields
|
|
||||||
const columnMetadata = repo.metadata.findColumnWithPropertyName(field);
|
const columnMetadata = repo.metadata.findColumnWithPropertyName(field);
|
||||||
assert(columnMetadata);
|
|
||||||
|
|
||||||
filters.forEach((filter, index) => {
|
filters.forEach((filter, index) => {
|
||||||
let { not, operator, value } = filter;
|
let { not, operator, value } = filter;
|
||||||
|
|
||||||
// Handle nested relation filter
|
|
||||||
const relation = relations[field];
|
const relation = relations[field];
|
||||||
|
|
||||||
|
// Handle nested relation filter (only one level deep supported)
|
||||||
if (operator === 'nested' && relation) {
|
if (operator === 'nested' && relation) {
|
||||||
const relationRepo = this.conn.getRepository<any>(relation.entity);
|
const relationRepo = this.conn.getRepository<any>(relation.entity);
|
||||||
const relationTableName = relationRepo.metadata.tableName;
|
const relationTableName = relationRepo.metadata.tableName;
|
||||||
let relationSubQuery: SelectQueryBuilder<any> = relationRepo.createQueryBuilder(relationTableName, repo.queryRunner)
|
let relationSubQuery: SelectQueryBuilder<any> = relationRepo.createQueryBuilder(relationTableName, repo.queryRunner)
|
||||||
.select('1')
|
.select('1');
|
||||||
.where(`${relationTableName}.id = "${alias}"."${columnMetadata.databaseName}"`);
|
|
||||||
|
if (relation.isDerived) {
|
||||||
|
const derivationField = relation.field;
|
||||||
|
relationSubQuery = relationSubQuery.where(`${relationTableName}.${derivationField} = ${alias}.id`);
|
||||||
|
} else {
|
||||||
|
// Column has to exist for non-derived fields
|
||||||
|
assert(columnMetadata);
|
||||||
|
|
||||||
|
if (relation.isArray) {
|
||||||
|
relationSubQuery = relationSubQuery.where(`${relationTableName}.id = ANY(${alias}.${columnMetadata.databaseName})`);
|
||||||
|
} else {
|
||||||
|
relationSubQuery = relationSubQuery.where(`${relationTableName}.id = ${alias}.${columnMetadata.databaseName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// canonicalBlockHashes take precedence over block number if provided
|
// canonicalBlockHashes take precedence over block number if provided
|
||||||
if (block.canonicalBlockHashes) {
|
if (block.canonicalBlockHashes) {
|
||||||
@ -874,15 +886,27 @@ export class Database {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form the where clause.
|
// Column has to exist if it's not a nested filter
|
||||||
let whereClause = `"${alias}"."${columnMetadata.databaseName}" `;
|
assert(columnMetadata);
|
||||||
|
const columnIsArray = columnMetadata.isArray;
|
||||||
|
|
||||||
if (columnMetadata.relationMetadata) {
|
// Form the where clause.
|
||||||
// For relation fields, use the id column.
|
assert(operator);
|
||||||
const idColumn = columnMetadata.relationMetadata.joinColumns.find(column => column.referencedColumn?.propertyName === 'id');
|
let whereClause = '';
|
||||||
assert(idColumn);
|
|
||||||
whereClause = `"${alias}"."${idColumn.databaseName}" `;
|
// In case of array field having contains, NOT comes before the field name
|
||||||
|
// Disregards nocase
|
||||||
|
if (columnIsArray && operator.includes('contains')) {
|
||||||
|
if (not) {
|
||||||
|
whereClause += 'NOT ';
|
||||||
|
whereClause += `"${alias}"."${columnMetadata.databaseName}" `;
|
||||||
|
whereClause += '&& ';
|
||||||
|
} else {
|
||||||
|
whereClause += `"${alias}"."${columnMetadata.databaseName}" `;
|
||||||
|
whereClause += '@> ';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
whereClause += `"${alias}"."${columnMetadata.databaseName}" `;
|
||||||
|
|
||||||
if (not) {
|
if (not) {
|
||||||
if (operator === 'equals') {
|
if (operator === 'equals') {
|
||||||
@ -892,10 +916,10 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(operator);
|
|
||||||
whereClause += `${OPERATOR_MAP[operator]} `;
|
whereClause += `${OPERATOR_MAP[operator]} `;
|
||||||
|
}
|
||||||
|
|
||||||
value = this._transformBigIntValues(value);
|
value = this._transformBigValues(value);
|
||||||
if (operator === 'in') {
|
if (operator === 'in') {
|
||||||
whereClause += '(:...';
|
whereClause += '(:...';
|
||||||
} else {
|
} else {
|
||||||
@ -913,6 +937,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!columnIsArray) {
|
||||||
if (['contains', 'contains_nocase', 'ends', 'ends_nocase'].some(el => el === operator)) {
|
if (['contains', 'contains_nocase', 'ends', 'ends_nocase'].some(el => el === operator)) {
|
||||||
value = `%${value}`;
|
value = `%${value}`;
|
||||||
}
|
}
|
||||||
@ -920,6 +945,7 @@ export class Database {
|
|||||||
if (['contains', 'contains_nocase', 'starts', 'starts_nocase'].some(el => el === operator)) {
|
if (['contains', 'contains_nocase', 'starts', 'starts_nocase'].some(el => el === operator)) {
|
||||||
value += '%';
|
value += '%';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectQueryBuilder = selectQueryBuilder.andWhere(whereClause, { [variableName]: value });
|
selectQueryBuilder = selectQueryBuilder.andWhere(whereClause, { [variableName]: value });
|
||||||
});
|
});
|
||||||
@ -966,10 +992,10 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Transform in the GQL type BigInt parsing itself
|
// TODO: Transform in the GQL type BigInt parsing itself
|
||||||
_transformBigIntValues (value: any): any {
|
_transformBigValues (value: any): any {
|
||||||
// Handle array of bigints
|
// Handle array of bigints
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (value.length > 0 && typeof value[0] === 'bigint') {
|
if (value.length > 0 && (typeof value[0] === 'bigint' || Decimal.isDecimal(value[0]))) {
|
||||||
return value.map(val => {
|
return value.map(val => {
|
||||||
return val.toString();
|
return val.toString();
|
||||||
});
|
});
|
||||||
@ -977,7 +1003,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle bigint
|
// Handle bigint
|
||||||
if (typeof value === 'bigint') {
|
if (typeof value === 'bigint' || Decimal.isDecimal(value)) {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user