Skip to content

Commit

Permalink
Merge pull request #7 from openfoodfacts:select-service
Browse files Browse the repository at this point in the history
Add select service for testing
  • Loading branch information
john-gom authored Oct 9, 2023
2 parents 43abcb7 + c24d55b commit cf19c21
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ COMPOSE_PATH_SEPARATOR=;
TAG=latest
QUERY_PORT=127.0.0.1:5511
POSTGRES_HOST=localhost
POSTGRES_PORT=127.0.0.1:5512
POSTGRES_PORT=5512
POSTGRES_DB=query
POSTGRES_USER=productopener
POSTGRES_PASSWORD=productopener
Expand Down
9 changes: 7 additions & 2 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Post, Query, All } from '@nestjs/common';
import { ImportService } from './domain/services/import.service';
import { QueryService } from './domain/services/query.service';

Expand Down Expand Up @@ -27,8 +27,13 @@ export class AppController {
return await this.queryService.aggregate(body);
}

@Post('count')
@All('count')
async count(@Body() body: any) {
return await this.queryService.count(body);
}

@Post('select')
async select(@Body() body: any) {
return await this.queryService.select(body);
}
}
27 changes: 26 additions & 1 deletion src/domain/services/query.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ describe('count', () => {
expect(response).toBe(1);
});
});
it('should cope with no filters', async () => {
await createTestingModule([DomainModule], async (app) => {
const { originValue, aminoValue, neucleotideValue } =
await createTestTags(app);
const queryService = app.get(QueryService);
const response = await queryService.count(null);
expect(response).toBeGreaterThan(2);
});
});
});

describe('aggregate', () => {
Expand Down Expand Up @@ -172,6 +181,22 @@ describe('aggregate', () => {
});
});

describe('select', () => {
it('should return matching products', async () =>{
await createTestingModule([DomainModule], async (app) => {
const { originValue, aminoValue, neucleotideValue, product1, product2, product3 } =
await createTestTags(app);
const queryService = app.get(QueryService);
const response = await queryService.select({
amino_acids_tags: aminoValue,
});
expect(response).toHaveLength(2);
const p1 = response.find((r) => r.code === product1.code);
expect(p1).toBeTruthy();
});
});
});

async function createTestTags(app) {
const em = app.get(EntityManager);
// Create some dummy products with a specific tag
Expand Down Expand Up @@ -221,5 +246,5 @@ async function createTestTags(app) {
});

await em.flush();
return { originValue, aminoValue, neucleotideValue };
return { originValue, aminoValue, neucleotideValue, product1, product2, product3 };
}
49 changes: 34 additions & 15 deletions src/domain/services/query.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class QueryService {
return results;
}

private addMatches(match: any, qb: QueryBuilder<object>) {
private addMatches(match: any, qb: QueryBuilder<object>, parentKey = 'pt.product_id') {
const whereLog = [];
for (const [matchTag, matchValue] of Object.entries(match)) {
let whereValue = matchValue;
Expand All @@ -76,7 +76,7 @@ export class QueryService {
const qbWhere = this.em
.createQueryBuilder(matchEntity, 'pt2')
.select('*')
.where(`pt2.product_id = pt.product_id and pt2.${matchColumn} = ?`, [
.where(`pt2.product_id = ${parentKey} and pt2.${matchColumn} = ?`, [
whereValue,
]);
qb.andWhere(`${not ? 'NOT ' : ''}EXISTS (${qbWhere.getKnexQuery()})`);
Expand All @@ -89,37 +89,56 @@ export class QueryService {
const start = Date.now();
this.logger.debug(body);

const tags = Object.keys(body);
const tag = tags[0];
const tags = Object.keys(body ?? {});
const tag = tags?.[0];
const { entity, column } = this.getEntityAndColumn(tag);
const qb = this.em.createQueryBuilder(entity, 'pt');
qb.select(`count(*) count`);
qb.where('not pt.obsolete');

let matchValue = body[tag];
const not = matchValue?.['$ne'];
if (not) {
matchValue = not;
let whereLog = [];
if (tag) {
let matchValue = body[tag];
const not = matchValue?.['$ne'];
whereLog.push(`${tag} ${not ? '!=' : '=='} ${matchValue}`);
if (not) {
matchValue = not;
}
qb.andWhere(`${not ? 'NOT ' : ''}pt.${column} = ?`, [matchValue]);
delete body[tag];
whereLog.push(...this.addMatches(body, qb));
}
qb.andWhere(`${not ? 'NOT ' : ''}pt.${column} = ?`, [matchValue]);
delete body[tag];
const whereLog = this.addMatches(body, qb);

this.logger.debug(qb.getFormattedQuery());
const results = await qb.execute();
const response = results[0].count;
this.logger.log(
`Processed ${tag} ${not ? '!=' : '=='} ${matchValue}${
whereLog.length ? ` and ${whereLog.join(' and ')}` : ''
} in ${Date.now() - start} ms. Count: ${response}`,
`Processed ${whereLog.join(' and ')} in ${Date.now() - start} ms. Count: ${response}`,
);
return parseInt(response);
}

async select(body: any) {
const start = Date.now();
this.logger.debug(body);

const tags = Object.keys(body);
let entity: EntityName<object> = Product;
const qb = this.em.createQueryBuilder(entity, 'p');
qb.select(`*`);
qb.where('not p.obsolete');

const whereLog = this.addMatches(body, qb, 'p.id');

this.logger.debug(qb.getFormattedQuery());
const results = await qb.execute();
return results;
}

private getEntityAndColumn(tag: any) {
let entity: EntityName<object>;
let column = 'value';
if (MAPPED_FIELDS.includes(tag)) {
if (!tag || MAPPED_FIELDS.includes(tag)) {
entity = Product;
column = tag;
} else {
Expand Down

0 comments on commit cf19c21

Please sign in to comment.