Skip to content

Commit

Permalink
🔧 Remove SqonBuilder library and use homemade sqon utils
Browse files Browse the repository at this point in the history
  • Loading branch information
evans-g-crsj committed Jan 29, 2025
1 parent bb01110 commit ae11aeb
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 129 deletions.
38 changes: 4 additions & 34 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"@aws-sdk/client-s3": "^3.689.0",
"@aws-sdk/s3-request-presigner": "^3.689.0",
"@elastic/elasticsearch": "^7.11.0",
"@overture-stack/sqon-builder": "^1.1.0",
"ajv": "^8.17.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
Expand Down
5 changes: 3 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { injectBodyHttpHeaders } from './middleware/injectBodyHttpHeaders';
import { resolveSetIdMiddleware } from './middleware/resolveSetIdInSqon';
import { sqonContainsSet } from './sqon/manipulateSqon';
import { resolveSetsInSqon } from './sqon/resolveSetInSqon';
import { Sqon } from './sqon/types';

export default (keycloak: Keycloak, getProject: (projectId: string) => ArrangerProject): Express => {
const app = express();
Expand Down Expand Up @@ -211,12 +212,12 @@ export default (keycloak: Keycloak, getProject: (projectId: string) => ArrangerP
try {
if ([2, 3].includes(req.body?.sqons?.length)) {
// Convert sqon(s) with set_id if exists to intelligible sqon for ES query translation.
const sqons: string[] = [];
const sqons: Sqon[] = [];
for (const s of req.body.sqons) {
if (sqonContainsSet(s)) {
const accessToken = req.headers.authorization;
const r = await resolveSetsInSqon(s, null, accessToken);
sqons.push(JSON.stringify(r));
sqons.push(r);
} else {
sqons.push(s);
}
Expand Down
149 changes: 69 additions & 80 deletions src/endpoints/venn/venn.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { buildQuery } from '@arranger/middleware';
import { default as SQONBuilder, SQON as v3SQON } from '@overture-stack/sqon-builder';

import EsInstance from '../../ElasticSearchClientInstance';
import { getNestedFieldsForIndex } from '../../sqon/getNestedFieldsForIndex';
import { renameFieldNameToField, renameFieldToFieldName } from '../../sqon/manipulateSqon';
import { and, not } from '../../sqon/manipulateSqon';
import { Sqon } from '../../sqon/types';

type OutputElement = {
operation: string;
Expand All @@ -16,88 +16,77 @@ type Output = {
operations: OutputElement[];
};

const builder = SQONBuilder;

const fromV3BackToV2 = x => ({ ...x, sqon: renameFieldNameToField(x.sqon) });

const setFormulasDuo = (s1: v3SQON, s2: v3SQON) =>
[
{
operation: 'Q₁',
sqon: builder.from(s1),
},
{
operation: 'Q₂',
sqon: builder.from(s2),
},
{
operation: 'Q₁-Q₂',
sqon: builder.from(s1).not(builder.from(s2)),
},
{
operation: 'Q₂-Q₁',
sqon: builder.from(s2).not(builder.from(s1)),
},
{
operation: 'Q₁∩Q₂',
sqon: builder.and([s1, s2]),
},
].map(fromV3BackToV2);

const setFormulasTrio = (s1: v3SQON, s2: v3SQON, s3: v3SQON) =>
[
{
operation: 'Q₁',
sqon: builder.from(s1),
},
{
operation: 'Q₂',
sqon: builder.from(s2),
},
{
operation: 'Q₃',
sqon: builder.from(s3),
},
{
operation: 'Q₁-(Q₂∩Q₃)',
sqon: builder.from(s1).not(builder.and([s2, s3])),
},
{
operation: 'Q₂-(Q₁∩Q₃)',
sqon: builder.from(s2).not(builder.and([s1, s3])),
},
{
operation: 'Q₃-(Q₁∩Q₂)',
sqon: builder.from(s3).not(builder.and([s1, s2])),
},
{
operation: '(Q₁∩Q₂)-Q₃',
sqon: builder.and([s1, s2]).not(s3),
},
{
operation: '(Q₂∩Q₃)-Q₁',
sqon: builder.and([s2, s3]).not(s1),
},
{
operation: '(Q₁∩Q₃)-Q₂',
sqon: builder.and([s1, s3]).not(s2),
},
{
operation: 'Q₁∩Q₂∩Q₃',
sqon: builder.and([s1, s2, s3]),
},
].map(fromV3BackToV2);
const setFormulasDuo = (s1: Sqon, s2: Sqon) => [
{
operation: 'Q₁',
sqon: s1,
},
{
operation: 'Q₂',
sqon: s2,
},
{
operation: 'Q₁-Q₂',
sqon: not(s1, s2),
},
{
operation: 'Q₂-Q₁',
sqon: not(s2, s1),
},
{
operation: 'Q₁∩Q₂',
sqon: and(s1, [s2]),
},
];

const setFormulasTrio = (s1: Sqon, s2: Sqon, s3: Sqon) => [
{
operation: 'Q₁',
sqon: s1,
},
{
operation: 'Q₂',
sqon: s2,
},
{
operation: 'Q₃',
sqon: s3,
},
{
operation: 'Q₁-(Q₂∩Q₃)',
sqon: not(s1, and(s2, [s3])),
},
{
operation: 'Q₂-(Q₁∩Q₃)',
sqon: not(s2, and(s1, [s3])),
},
{
operation: 'Q₃-(Q₁∩Q₂)',
sqon: not(s3, and(s1, [s2])),
},
{
operation: '(Q₁∩Q₂)-Q₃',
sqon: not(and(s1, [s2]), s3),
},
{
operation: '(Q₂∩Q₃)-Q₁',
sqon: not(and(s2, [s3]), s1),
},
{
operation: '(Q₁∩Q₃)-Q₂',
sqon: not(and(s1, [s3]), s2),
},
{
operation: 'Q₁∩Q₂∩Q₃',
sqon: and(s1, [s2, s3]),
},
];

let nestedFields: string[] = null;

export const venn = async (sqons: string[]): Promise<Output> => {
// (Arranger v2 vs v3): SqonBuilder uses property "fieldName" (v3) but we use "field" (v2)
const v3Sqons: v3SQON[] = sqons.map(s => renameFieldToFieldName(s));

export const venn = async (sqons: Sqon[]): Promise<Output> => {
const setFormulas =
sqons.length === 2
? setFormulasDuo(v3Sqons[0], v3Sqons[1])
: setFormulasTrio(v3Sqons[0], v3Sqons[1], v3Sqons[2]);
sqons.length === 2 ? setFormulasDuo(sqons[0], sqons[1]) : setFormulasTrio(sqons[0], sqons[1], sqons[2]);

const client = EsInstance.getInstance();
const needToFetchMapping = !nestedFields || nestedFields.length === 0;
Expand Down
50 changes: 38 additions & 12 deletions src/sqon/manipulateSqon.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SQON as v3SQON } from '@overture-stack/sqon-builder';

import { SetSqon } from '../endpoints/sets/setsTypes';
import { Sqon } from './types';

export const addSqonToSetSqon = (receivingSqon: SetSqon, donorSqon: SetSqon): SetSqon =>
({
Expand All @@ -19,16 +18,43 @@ export const removeSqonToSetSqon = (setSqon: SetSqon, sqonToRemove: SetSqon): Se
} as SetSqon;
};

const isString = (x: unknown): boolean => typeof x === 'string';
export const sqonContainsSet = (s: string) => JSON.stringify(s).includes('"set_id:');

const renameFieldCore = (toV3: boolean) => (sqon: v3SQON | string) => {
const s = (isString(sqon) ? sqon : JSON.stringify(sqon)) as string;
const updated = toV3 ? s.replace(/"field":/g, '"fieldName":') : s.replace(/"fieldName":/g, '"field":');
return JSON.parse(updated);
// Taken as-is from "@overture-stack/sqon-builder": "^1.1.0" but zod Types were removed
const combine = (op, sqon, content, pivot) => {
const output = { op, content: [] };
if (sqon.op === op && sqon.pivot === pivot) {
// provided sqon has same operation as requested:
// don't add wrapper, insert new content into existing sqon
output.content = output.content.concat(sqon.content, content);
} else {
output.content = output.content.concat(sqon, content);
}

if (pivot !== undefined) {
// dont automatically assign pivot: undefined to an operator, it will then appear in every output and that is not desired
// optionally assigning pivot only if it has a value avoids this

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
output.pivot = pivot;
}
return output;
};

export const renameFieldNameToField = renameFieldCore(false);

export const renameFieldToFieldName = renameFieldCore(true);

export const sqonContainsSet = (s: string) => JSON.stringify(s).includes('"set_id:');
const asArray = input => (Array.isArray(input) ? input : [input]);

export const and = (x: Sqon, xs: Sqon | Sqon[]): Sqon => combine('and', x, xs, undefined);
export const or = (x: Sqon, xs: Sqon | Sqon[]): Sqon => combine('or', x, xs, undefined);

export const not = (x: Sqon, xs: Sqon | Sqon[]): Sqon =>
combine(
'and',
x,
{
op: 'not',
content: asArray(xs),
pivot: undefined,
},
undefined,
);

0 comments on commit ae11aeb

Please sign in to comment.