Browse Source

Fix too many bind Parameters

pull/5027/head
Dan 2 years ago
parent
commit
21955f3c09
  1. 5
      apps/api/src/app/order/order.service.ts
  2. 14
      apps/api/src/app/portfolio/current-rate.service.ts
  3. 2
      apps/api/src/app/portfolio/portfolio-calculator.ts
  4. 23
      apps/api/src/helper/dateQueryHelper.ts
  5. 33
      apps/api/src/services/data-provider/manual/manual.service.ts
  6. 62
      apps/api/src/services/market-data/market-data.service.ts
  7. 2
      apps/api/src/services/symbol-profile/symbol-profile.service.ts
  8. 46
      libs/common/src/lib/chunkhelper.ts

5
apps/api/src/app/order/order.service.ts

@ -274,7 +274,6 @@ export class OrderService {
{ {
AND: [ AND: [
{ {
// TODO Chunk?
OR: filtersByAssetClass.map(({ id }) => { OR: filtersByAssetClass.map(({ id }) => {
return { assetClass: AssetClass[id] }; return { assetClass: AssetClass[id] };
}) })
@ -289,7 +288,6 @@ export class OrderService {
}, },
{ {
SymbolProfileOverrides: { SymbolProfileOverrides: {
// TODO Chunk?
OR: filtersByAssetClass.map(({ id }) => { OR: filtersByAssetClass.map(({ id }) => {
return { assetClass: AssetClass[id] }; return { assetClass: AssetClass[id] };
}) })
@ -306,7 +304,6 @@ export class OrderService {
{ {
tags: { tags: {
some: { some: {
// TODO Chunk?
OR: filtersByTag.map(({ id }) => { OR: filtersByTag.map(({ id }) => {
return { return {
id: id id: id
@ -319,7 +316,6 @@ export class OrderService {
SymbolProfile: { SymbolProfile: {
tags: { tags: {
some: { some: {
// TODO Chunk?
OR: filtersByTag.map(({ id }) => { OR: filtersByTag.map(({ id }) => {
return { id }; return { id };
}) })
@ -333,7 +329,6 @@ export class OrderService {
} }
if (types) { if (types) {
// TODO Chunk?
where.OR = types.map((type) => { where.OR = types.map((type) => {
return { return {
type: { type: {

14
apps/api/src/app/portfolio/current-rate.service.ts

@ -14,9 +14,12 @@ import { flatten, isEmpty, uniqBy } from 'lodash';
import { GetValueObject } from './interfaces/get-value-object.interface'; import { GetValueObject } from './interfaces/get-value-object.interface';
import { GetValuesObject } from './interfaces/get-values-object.interface'; import { GetValuesObject } from './interfaces/get-values-object.interface';
import { GetValuesParams } from './interfaces/get-values-params.interface'; import { GetValuesParams } from './interfaces/get-values-params.interface';
import { DateQueryHelper } from '@ghostfolio/api/helper/dateQueryHelper';
@Injectable() @Injectable()
export class CurrentRateService { export class CurrentRateService {
private dateQueryHelper = new DateQueryHelper();
public constructor( public constructor(
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
@ -34,7 +37,7 @@ export class CurrentRateService {
(!dateQuery.lt || isBefore(new Date(), dateQuery.lt)) && (!dateQuery.lt || isBefore(new Date(), dateQuery.lt)) &&
(!dateQuery.gte || isBefore(dateQuery.gte, new Date())) && (!dateQuery.gte || isBefore(dateQuery.gte, new Date())) &&
(!dateQuery.in || this.containsToday(dateQuery.in)); (!dateQuery.in || this.containsToday(dateQuery.in));
let { query, dates } = this.dateQueryHelper.handleDateQueryIn(dateQuery);
const promises: Promise<GetValueObject[]>[] = []; const promises: Promise<GetValueObject[]>[] = [];
const quoteErrors: ResponseError['errors'] = []; const quoteErrors: ResponseError['errors'] = [];
const today = resetHours(new Date()); const today = resetHours(new Date());
@ -89,7 +92,7 @@ export class CurrentRateService {
promises.push( promises.push(
this.marketDataService this.marketDataService
.getRange({ .getRange({
dateQuery, dateQuery: query,
uniqueAssets uniqueAssets
}) })
.then((data) => { .then((data) => {
@ -116,9 +119,12 @@ export class CurrentRateService {
errors: quoteErrors.map(({ dataSource, symbol }) => { errors: quoteErrors.map(({ dataSource, symbol }) => {
return { dataSource, symbol }; return { dataSource, symbol };
}), }),
values: uniqBy(values, ({ date, symbol }) => `${date}-${symbol}`) values: uniqBy(values, ({ date, symbol }) => `${date}-${symbol}`).filter(
(v) =>
dates?.length === 0 ||
dates.some((d: Date) => d.getTime() === v.date.getTime())
)
}; };
if (!isEmpty(quoteErrors)) { if (!isEmpty(quoteErrors)) {
for (const { dataSource, symbol } of quoteErrors) { for (const { dataSource, symbol } of quoteErrors) {
try { try {

2
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -215,7 +215,6 @@ export class PortfolioCalculator {
await this.currentRateService.getValues({ await this.currentRateService.getValues({
currencies, currencies,
dataGatheringItems, dataGatheringItems,
// TODO Refactor in to lte & gte
dateQuery: { dateQuery: {
in: dates in: dates
}, },
@ -403,7 +402,6 @@ export class PortfolioCalculator {
} = await this.currentRateService.getValues({ } = await this.currentRateService.getValues({
currencies, currencies,
dataGatheringItems, dataGatheringItems,
// TODO Refactor to lte & gte
dateQuery: { dateQuery: {
in: dates in: dates
}, },

23
apps/api/src/helper/dateQueryHelper.ts

@ -0,0 +1,23 @@
import { resetHours } from '@ghostfolio/common/helper';
import { DateQuery } from '../app/portfolio/interfaces/date-query.interface';
import { addDays } from 'date-fns';
export class DateQueryHelper {
public handleDateQueryIn(dateQuery: DateQuery): {
query: DateQuery;
dates: Date[];
} {
let dates = [];
let query = dateQuery;
if (dateQuery.in?.length > 0) {
dates = dateQuery.in;
let end = Math.max(...dates.map((d) => d.getTime()));
let start = Math.min(...dates.map((d) => d.getTime()));
query = {
gte: resetHours(new Date(start)),
lt: resetHours(addDays(end, 1))
};
}
return { query, dates };
}
}

33
apps/api/src/services/data-provider/manual/manual.service.ts

@ -6,6 +6,7 @@ import {
} from '@ghostfolio/api/services/interfaces/interfaces'; } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { BatchPrismaClient } from '@ghostfolio/common/chunkhelper';
import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config';
import { import {
DATE_FORMAT, DATE_FORMAT,
@ -153,19 +154,25 @@ export class ManualService implements DataProviderInterface {
}) })
); );
const marketData = await this.prismaService.marketData.findMany({ const batch = new BatchPrismaClient(this.prismaService);
distinct: ['symbol'],
orderBy: { const marketData = await batch
date: 'desc' .over(symbols)
}, .with((prisma, _symbols) =>
take: symbols.length, prisma.marketData.findMany({
where: { distinct: ['symbol'],
symbol: { orderBy: {
// TODO Chunk! date: 'desc'
in: symbols },
} take: symbols.length,
} where: {
}); symbol: {
in: _symbols
}
}
})
)
.then((_result) => _result.flat());
for (const symbolProfile of symbolProfiles) { for (const symbolProfile of symbolProfiles) {
response[symbolProfile.symbol] = { response[symbolProfile.symbol] = {

62
apps/api/src/services/market-data/market-data.service.ts

@ -3,6 +3,7 @@ import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.i
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { resetHours } from '@ghostfolio/common/helper'; import { resetHours } from '@ghostfolio/common/helper';
import { BatchPrismaClient } from '@ghostfolio/common/chunkhelper';
import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import {
@ -11,11 +12,14 @@ import {
MarketDataState, MarketDataState,
Prisma Prisma
} from '@prisma/client'; } from '@prisma/client';
import { DateQueryHelper } from '@ghostfolio/api/helper/dateQueryHelper';
@Injectable() @Injectable()
export class MarketDataService { export class MarketDataService {
public constructor(private readonly prismaService: PrismaService) {} public constructor(private readonly prismaService: PrismaService) {}
private dateQueryHelper = new DateQueryHelper();
public async deleteMany({ dataSource, symbol }: UniqueAsset) { public async deleteMany({ dataSource, symbol }: UniqueAsset) {
return this.prismaService.marketData.deleteMany({ return this.prismaService.marketData.deleteMany({
where: { where: {
@ -64,30 +68,41 @@ export class MarketDataService {
dateQuery: DateQuery; dateQuery: DateQuery;
uniqueAssets: UniqueAsset[]; uniqueAssets: UniqueAsset[];
}): Promise<MarketData[]> { }): Promise<MarketData[]> {
return await this.prismaService.marketData.findMany({ const batch = new BatchPrismaClient(this.prismaService);
orderBy: [ let { query, dates } = this.dateQueryHelper.handleDateQueryIn(dateQuery);
{ let marketData = await batch
date: 'asc' .over(uniqueAssets)
}, .with((prisma, _assets) =>
{ prisma.marketData.findMany({
symbol: 'asc' orderBy: [
} {
], date: 'asc'
where: { },
//TODO Chunk! {
OR: uniqueAssets.map(({ dataSource, symbol }) => { symbol: 'asc'
return { }
AND: [ ],
{ where: {
dataSource, OR: _assets.map(({ dataSource, symbol }) => {
symbol, return {
date: dateQuery AND: [
} {
] dataSource,
}; symbol,
date: query
}
]
};
})
}
}) })
} )
}); .then((data) => data.flat());
return marketData.filter(
(m) =>
dates?.length === 0 ||
dates.some((d) => m.date.getTime() === d.getTime())
);
} }
public async marketDataItems(params: { public async marketDataItems(params: {
@ -98,7 +113,6 @@ export class MarketDataService {
orderBy?: Prisma.MarketDataOrderByWithRelationInput; orderBy?: Prisma.MarketDataOrderByWithRelationInput;
}): Promise<MarketData[]> { }): Promise<MarketData[]> {
const { skip, take, cursor, where, orderBy } = params; const { skip, take, cursor, where, orderBy } = params;
return this.prismaService.marketData.findMany({ return this.prismaService.marketData.findMany({
cursor, cursor,
orderBy, orderBy,

2
apps/api/src/services/symbol-profile/symbol-profile.service.ts

@ -58,7 +58,6 @@ export class SymbolProfileService {
SymbolProfileOverrides: true SymbolProfileOverrides: true
}, },
where: { where: {
// TODO: CHUNK !
OR: aUniqueAssets.map(({ dataSource, symbol }) => { OR: aUniqueAssets.map(({ dataSource, symbol }) => {
return { return {
dataSource, dataSource,
@ -84,7 +83,6 @@ export class SymbolProfileService {
}, },
where: { where: {
id: { id: {
//TODO CHUNK!!!!
in: symbolProfileIds.map((symbolProfileId) => { in: symbolProfileIds.map((symbolProfileId) => {
return symbolProfileId; return symbolProfileId;
}) })

46
libs/common/src/lib/chunkhelper.ts

@ -0,0 +1,46 @@
import { Prisma, PrismaClient } from '@prisma/client';
class Chunk<T> implements Iterable<T[] | undefined> {
protected constructor(
private readonly values: readonly T[],
private readonly size: number
) {}
*[Symbol.iterator]() {
const copy = [...this.values];
if (copy.length === 0) yield undefined;
while (copy.length) yield copy.splice(0, this.size);
}
map<U>(mapper: (items?: T[]) => U): U[] {
return Array.from(this).map((items) => mapper(items));
}
static of<U>(values: readonly U[]) {
return {
by: (size: number) => new Chunk(values, size)
};
}
}
export type Queryable<T, Result> = (
p: PrismaClient,
vs?: T[]
) => Prisma.PrismaPromise<Result>;
export class BatchPrismaClient {
constructor(
private readonly prisma: PrismaClient,
private readonly size = 32_000
) {}
over<T>(values: readonly T[]) {
return {
with: <Result>(queryable: Queryable<T, Result>) =>
this.prisma.$transaction(
Chunk.of(values)
.by(this.size)
.map((vs) => queryable(this.prisma, vs))
)
};
}
}
Loading…
Cancel
Save