Skip to content

Commit

Permalink
Fork OrderByAggregatesPlugin and modify to be compatible with histori…
Browse files Browse the repository at this point in the history
…cal feature (#1242)
  • Loading branch information
stwiname authored Aug 10, 2022
1 parent 600048a commit 6c3c928
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 3 deletions.
7 changes: 6 additions & 1 deletion packages/query/src/graphql/graphql.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import {makePluginHook} from 'postgraphile';
import {getPostGraphileBuilder, PostGraphileCoreOptions} from 'postgraphile-core';
import {SubscriptionServer} from 'subscriptions-transport-ws';
import {Config} from '../configure';
import {PinoConfig} from '../utils/logger';
import {getLogger, PinoConfig} from '../utils/logger';
import {getYargsOption} from '../yargs';
import {plugins} from './plugins';
import {PgSubscriptionPlugin} from './plugins/PgSubscriptionPlugin';
import {queryComplexityPlugin} from './plugins/QueryComplexityPlugin';
import {ProjectService} from './project.service';

const {argv} = getYargsOption();
const logger = getLogger('graphql-module');

const SCHEMA_RETRY_INTERVAL = 10; //seconds
const SCHEMA_RETRY_NUMBER = 5;
@Module({
Expand Down Expand Up @@ -68,6 +70,9 @@ export class GraphqlModule implements OnModuleInit, OnModuleDestroy {
return graphqlSchema;
} catch (e) {
await delay(SCHEMA_RETRY_INTERVAL);
if (retries === 1) {
logger.error(e);
}
return this.buildSchema(dbSchema, options, --retries);
}
} else {
Expand Down
3 changes: 1 addition & 2 deletions packages/query/src/graphql/plugins/PgAggregationPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import AggregateSpecsPlugin from '@graphile/pg-aggregates/dist/AggregateSpecsPlu
import FilterRelationalAggregatesPlugin from '@graphile/pg-aggregates/dist/FilterRelationalAggregatesPlugin';
import InflectionPlugin from '@graphile/pg-aggregates/dist/InflectionPlugin';
import {AggregateSpec, AggregateGroupBySpec} from '@graphile/pg-aggregates/dist/interfaces';
import OrderByAggregatesPlugin from '@graphile/pg-aggregates/dist/OrderByAggregatesPlugin';

import type {Plugin} from 'graphile-build';
import {makePluginByCombiningPlugins} from 'graphile-utils';

import {argv} from '../../yargs';
import OrderByAggregatesPlugin from './PgOrderByAggregatesPlugin';

const aggregate = argv('aggregate') as boolean;

Expand Down
167 changes: 167 additions & 0 deletions packages/query/src/graphql/plugins/PgOrderByAggregatesPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2020-2022 OnFinality Limited authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* WARNING
* This is a fork of https://github.com/graphile/pg-aggregates/blob/c8dd0f951663d5dacde21da26f3b94b62dc296c5/src/OrderByAggregatesPlugin.ts
* The only modification is to filter out `_id` and `_block_height` attributes to fix a naming conflict
*/

import {AggregateSpec} from '@graphile/pg-aggregates/dist/interfaces';
import type {Plugin} from 'graphile-build';
import type {SQL, QueryBuilder, PgClass, PgEntity} from 'graphile-build-pg';

type OrderBySpecIdentity = string | SQL | ((options: {queryBuilder: QueryBuilder}) => SQL);

type OrderSpec = [OrderBySpecIdentity, boolean] | [OrderBySpecIdentity, boolean, boolean];
export interface OrderSpecs {
[orderByEnumValue: string]: {
value: {
alias?: string;
specs: Array<OrderSpec>;
unique: boolean;
};
};
}

const OrderByAggregatesPlugin: Plugin = (builder) => {
builder.hook('GraphQLEnumType:values', (values, build, context) => {
const {
extend,
inflection,
pgIntrospectionResultsByKind: introspectionResultsByKind,
pgOmit: omit,
pgSql: sql,
} = build;
const pgAggregateSpecs: AggregateSpec[] = build.pgAggregateSpecs;
const {
scope: {isPgRowSortEnum},
} = context;

const pgIntrospection: PgEntity | undefined = context.scope.pgIntrospection;

if (!isPgRowSortEnum || !pgIntrospection || pgIntrospection.kind !== 'class') {
return values;
}

const foreignTable: PgClass = pgIntrospection;

const foreignKeyConstraints = foreignTable.foreignConstraints.filter((con) => con.type === 'f');

const newValues = foreignKeyConstraints.reduce((memo, constraint) => {
if (omit(constraint, 'read')) {
return memo;
}
const table: PgClass | undefined = introspectionResultsByKind.classById[constraint.classId];
if (!table) {
throw new Error(`Could not find the table that referenced us (constraint: ${constraint.name})`);
}
const keys = constraint.keyAttributes;
const foreignKeys = constraint.foreignKeyAttributes;
if (!keys.every((_) => _) || !foreignKeys.every((_) => _)) {
throw new Error('Could not find key columns!');
}
if (keys.some((key) => omit(key, 'read'))) {
return memo;
}
if (foreignKeys.some((key) => omit(key, 'read'))) {
return memo;
}
const isUnique = !!table.constraints.find(
(c) =>
(c.type === 'p' || c.type === 'u') &&
c.keyAttributeNums.length === keys.length &&
c.keyAttributeNums.every((n, i) => keys[i].num === n)
);
if (isUnique) {
// No point aggregating over a relation that's unique
return memo;
}

const tableAlias = sql.identifier(Symbol(`${foreignTable.namespaceName}.${foreignTable.name}`));

// Add count
memo = build.extend(
memo,
orderByAscDesc(
inflection.orderByCountOfManyRelationByKeys(keys, table, foreignTable, constraint),
({queryBuilder}) => {
const foreignTableAlias = queryBuilder.getTableAlias();
const conditions: SQL[] = [];
keys.forEach((key, i) => {
conditions.push(
sql.fragment`${tableAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(
foreignKeys[i].name
)}`
);
});
return sql.fragment`(select count(*) from ${sql.identifier(
table.namespaceName,
table.name
)} ${tableAlias} where (${sql.join(conditions, ' AND ')}))`;
},
false
),
`Adding orderBy count to '${foreignTable.namespaceName}.${foreignTable.name}' using constraint '${constraint.name}'`
);

// Filter out attributes relating to historical. This was causing conflicts with `id` and `_id`
const attributes = table.attributes.filter((attr) => attr.name !== '_id' && attr.name !== '_block_height');

// Add other aggregates
pgAggregateSpecs.forEach((spec) => {
attributes.forEach((attr) => {
memo = build.extend(
memo,
orderByAscDesc(
inflection.orderByColumnAggregateOfManyRelationByKeys(keys, table, foreignTable, constraint, spec, attr),
({queryBuilder}) => {
const foreignTableAlias = queryBuilder.getTableAlias();
const conditions: SQL[] = [];
keys.forEach((key, i) => {
conditions.push(
sql.fragment`${tableAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(
foreignKeys[i].name
)}`
);
});
return sql.fragment`(select ${spec.sqlAggregateWrap(
sql.fragment`${tableAlias}.${sql.identifier(attr.name)}`
)} from ${sql.identifier(table.namespaceName, table.name)} ${tableAlias} where (${sql.join(
conditions,
' AND '
)}))`;
},
false
),
`Adding orderBy ${spec.id} of '${attr.name}' to '${foreignTable.namespaceName}.${foreignTable.name}' using constraint '${constraint.name}'`
);
});
});

return memo;
}, {} as OrderSpecs);

return extend(values, newValues, `Adding aggregate orders to '${foreignTable.namespaceName}.${foreignTable.name}'`);
});
};

export function orderByAscDesc(baseName: string, columnOrSqlFragment: OrderBySpecIdentity, unique = false): OrderSpecs {
return {
[`${baseName}_ASC`]: {
value: {
alias: `${baseName}_ASC`,
specs: [[columnOrSqlFragment, true]],
unique,
},
},
[`${baseName}_DESC`]: {
value: {
alias: `${baseName}_DESC`,
specs: [[columnOrSqlFragment, false]],
unique,
},
},
};
}

export default OrderByAggregatesPlugin;

0 comments on commit 6c3c928

Please sign in to comment.