Skip to content

Commit 893358b

Browse files
committed
UBERF-9560: Filter query fixes
Signed-off-by: Andrey Sobolev <[email protected]>
1 parent c5eb0ec commit 893358b

File tree

3 files changed

+121
-30
lines changed

3 files changed

+121
-30
lines changed

server/postgres/src/__tests__/conversion.spec.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { convertArrayParams, decodeArray } from '../utils'
1+
import { convertArrayParams, decodeArray, filterProjection } from '../utils'
22

33
describe('array conversion', () => {
44
it('should handle undefined parameters', () => {
@@ -55,3 +55,47 @@ describe('array decoding', () => {
5555
expect(decodeArray('{"first \\"quote\\"","second \\"quote\\""}')).toEqual(['first "quote"', 'second "quote"'])
5656
})
5757
})
58+
59+
describe('projection', () => {
60+
it('mixin query projection', () => {
61+
const data = {
62+
'638611f18894c91979399ef3': {
63+
Источник_6386125d8894c91979399eff: 'Workable'
64+
},
65+
attachments: 1,
66+
avatar: null,
67+
avatarProps: null,
68+
avatarType: 'color',
69+
channels: 3,
70+
city: 'Poland',
71+
docUpdateMessages: 31,
72+
name: 'Mulkuha,Muklyi',
73+
'notification:mixin:Collaborators': {
74+
collaborators: []
75+
},
76+
'recruit:mixin:Candidate': {
77+
Title_63f38419efefd99805238bbd: 'Backend-RoR',
78+
Trash_64493626f9b50e77bf82d231: 'Нет',
79+
__mixin: 'true',
80+
applications: 1,
81+
onsite: null,
82+
remote: null,
83+
skills: 18,
84+
title: '',
85+
Опытработы_63860d5c8894c91979399e73: '2018',
86+
Уровеньанглийского_63860d038894c91979399e6f: 'UPPER'
87+
}
88+
}
89+
const projected = filterProjection<any>(data, {
90+
'recruit:mixin:Candidate.Уровеньанглийского_63860d038894c91979399e6f': 1,
91+
_class: 1,
92+
space: 1,
93+
modifiedOn: 1
94+
})
95+
expect(projected).toEqual({
96+
'recruit:mixin:Candidate': {
97+
Уровеньанглийского_63860d038894c91979399e6f: 'UPPER'
98+
}
99+
})
100+
})
101+
})

server/postgres/src/storage.ts

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,11 @@ abstract class PostgresAdapterBase implements DbAdapter {
13041304
private translateQueryValue (vars: ValuesVariables, tkey: string, value: any, type: ValueType): string | undefined {
13051305
const tkeyData = tkey.includes('data') && (tkey.includes('->') || tkey.includes('#>>'))
13061306
if (tkeyData && (Array.isArray(value) || (typeof value !== 'object' && typeof value !== 'string'))) {
1307-
value = Array.isArray(value) ? value.map((it) => (it == null ? null : `${it}`)) : `${value}`
1307+
value = Array.isArray(value)
1308+
? value.map((it) => (it == null ? null : `${it}`))
1309+
: value == null
1310+
? null
1311+
: `${value}`
13081312
}
13091313

13101314
if (value === null) {
@@ -1315,76 +1319,91 @@ abstract class PostgresAdapterBase implements DbAdapter {
13151319
for (const operator in value) {
13161320
let val = value[operator]
13171321
if (tkeyData && (Array.isArray(val) || (typeof val !== 'object' && typeof val !== 'string'))) {
1318-
val = Array.isArray(val) ? val.map((it) => (it == null ? null : `${it}`)) : `${val}`
1322+
val = Array.isArray(val) ? val.map((it) => (it == null ? null : `${it}`)) : val == null ? null : `${val}`
13191323
}
1324+
1325+
const valType = inferType(val)
1326+
const arrowCount = (tkey.match(/->/g) ?? []).length
1327+
// We need to convert to type without array if pressent
1328+
let tlkey = arrowCount > 0 ? `(${tkey})${valType.replace('[]', '')}` : tkey
1329+
1330+
if (arrowCount > 1) {
1331+
// We need to replace only the last -> to ->>
1332+
tlkey = tlkey.replace(/->(?!.*->)/, '->>')
1333+
}
1334+
13201335
switch (operator) {
13211336
case '$ne':
1322-
if (val === null) {
1323-
res.push(`${tkey} IS NOT NULL`)
1337+
if (val == null) {
1338+
res.push(`${tlkey} IS NOT NULL`)
13241339
} else {
1325-
res.push(`${tkey} != ${vars.add(val, inferType(val))}`)
1340+
res.push(`${tlkey} != ${vars.add(val, valType)}`)
13261341
}
13271342
break
13281343
case '$gt':
1329-
res.push(`${tkey} > ${vars.add(val, inferType(val))}`)
1344+
res.push(`${tlkey} > ${vars.add(val, valType)}`)
13301345
break
13311346
case '$gte':
1332-
res.push(`${tkey} >= ${vars.add(val, inferType(val))}`)
1347+
res.push(`${tlkey} >= ${vars.add(val, valType)}`)
13331348
break
13341349
case '$lt':
1335-
res.push(`${tkey} < ${vars.add(val, inferType(val))}`)
1350+
res.push(`${tlkey} < ${vars.add(val, valType)}`)
13361351
break
13371352
case '$lte':
1338-
res.push(`${tkey} <= ${vars.add(val, inferType(val))}`)
1353+
res.push(`${tlkey} <= ${vars.add(val, valType)}`)
13391354
break
13401355
case '$in':
13411356
switch (type) {
13421357
case 'common':
13431358
if (Array.isArray(val) && val.includes(null)) {
1344-
const vv = vars.addArray(val, inferType(val))
1345-
res.push(`(${tkey} = ANY(${vv}) OR ${tkey} IS NULL)`)
1359+
const vv = vars.addArray(val, valType)
1360+
res.push(`(${tlkey} = ANY(${vv}) OR ${tkey} IS NULL)`)
13461361
} else {
13471362
if (val.length > 0) {
1348-
res.push(`${tkey} = ANY(${vars.addArray(val, inferType(val))})`)
1363+
res.push(`${tlkey} = ANY(${vars.addArray(val, valType)})`)
13491364
} else {
1350-
res.push(`${tkey} IN ('NULL')`)
1365+
res.push(`${tlkey} IN ('NULL')`)
13511366
}
13521367
}
13531368
break
13541369
case 'array':
13551370
{
1356-
const vv = vars.addArrayI(val, inferType(val))
1371+
const vv = vars.addArrayI(val, valType)
13571372
res.push(`${tkey} && ${vv}`)
13581373
}
13591374
break
13601375
case 'dataArray':
13611376
{
1362-
const vv = vars.addArrayI(val, inferType(val))
1377+
const vv = vars.addArrayI(val, valType)
13631378
res.push(`${tkey} ?| ${vv}`)
13641379
}
13651380
break
13661381
}
13671382
break
13681383
case '$nin':
13691384
if (Array.isArray(val) && val.includes(null)) {
1370-
res.push(`(${tkey} != ALL(${vars.addArray(val, inferType(val))}) AND ${tkey} IS NOT NULL)`)
1385+
res.push(`(${tlkey} != ALL(${vars.addArray(val, valType)}) AND ${tkey} IS NOT NULL)`)
13711386
} else if (Array.isArray(val) && val.length > 0) {
1372-
res.push(`${tkey} != ALL(${vars.addArray(val, inferType(val))})`)
1387+
res.push(`${tlkey} != ALL(${vars.addArray(val, valType)})`)
13731388
}
13741389
break
13751390
case '$like':
1376-
res.push(`${tkey} ILIKE ${vars.add(val, inferType(val))}`)
1391+
res.push(`${tlkey} ILIKE ${vars.add(val, valType)}`)
13771392
break
13781393
case '$exists':
1379-
res.push(`${tkey} IS ${val === true || val === 'true' ? 'NOT NULL' : 'NULL'}`)
1394+
res.push(`${tlkey} IS ${val === true || val === 'true' ? 'NOT NULL' : 'NULL'}`)
13801395
break
13811396
case '$regex':
1382-
res.push(`${tkey} SIMILAR TO ${vars.add(val, inferType(val))}`)
1397+
res.push(`${tlkey} SIMILAR TO ${vars.add(val, valType)}`)
13831398
break
13841399
case '$options':
13851400
break
13861401
case '$all':
1387-
res.push(`${tkey} @> ${vars.addArray(value, inferType(value))}`)
1402+
if (arrowCount > 0) {
1403+
res.push(`${tkey} @> '${JSON.stringify(val)}'::jsonb`)
1404+
} else {
1405+
res.push(`${tkey} @> ${vars.addArray(val, inferType(val))}`)
1406+
}
13881407
break
13891408
default:
13901409
res.push(`${tkey} @> '[${JSON.stringify(value)}]'`)
@@ -1394,8 +1413,16 @@ abstract class PostgresAdapterBase implements DbAdapter {
13941413
return res.length === 0 ? undefined : res.join(' AND ')
13951414
}
13961415

1416+
const valType = inferType(value)
1417+
const arrowCount = (tkey.match(/->/g) ?? []).length
1418+
// We need to convert to type without array if pressent
1419+
let tlkey = arrowCount > 0 ? `(${tkey})${valType.replace('[]', '')}` : tkey
1420+
if (arrowCount > 1) {
1421+
// We need to replace only the last -> to ->>
1422+
tlkey = tlkey.replace(/->(?!.*->)/, '->>')
1423+
}
13971424
return type === 'common'
1398-
? `${tkey} = ${vars.add(value, inferType(value))}`
1425+
? `${tlkey} = ${vars.add(value, valType)}`
13991426
: type === 'array'
14001427
? `${tkey} @> '${typeof value === 'string' ? '{"' + value + '"}' : value}'`
14011428
: `${tkey} @> '${typeof value === 'string' ? '"' + value + '"' : value}'`

server/postgres/src/utils.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,30 @@ export function convertArrayParams (parameters?: ParameterOrJSON<any>[]): any[]
556556
})
557557
}
558558

559+
export function filterProjection<T extends Doc> (data: any, projection: Projection<T> | undefined): any {
560+
for (const key in data) {
561+
if (!Object.prototype.hasOwnProperty.call(projection, key) || (projection as any)[key] === 0) {
562+
// check nested projections in case of object
563+
let value = data[key]
564+
if (typeof value === 'object' && !Array.isArray(value) && value != null) {
565+
// We need to filter projection for nested objects
566+
const innerP = Object.entries(projection as any)
567+
.filter((it) => it[0].startsWith(key))
568+
.map((it) => [it[0].substring(key.length + 1), it[1]])
569+
if (innerP.length > 0) {
570+
value = filterProjection(value, Object.fromEntries(innerP))
571+
data[key] = value
572+
continue
573+
}
574+
}
575+
576+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
577+
delete data[key]
578+
}
579+
}
580+
return data
581+
}
582+
559583
export function parseDocWithProjection<T extends Doc> (
560584
doc: DBDoc,
561585
domain: string,
@@ -577,16 +601,12 @@ export function parseDocWithProjection<T extends Doc> (
577601
;(rest as any)[key] = decodeArray((rest as any)[key])
578602
}
579603
}
604+
let resultData = data
580605
if (projection !== undefined) {
581-
for (const key in data) {
582-
if (!Object.prototype.hasOwnProperty.call(projection, key) || (projection as any)[key] === 0) {
583-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
584-
delete data[key]
585-
}
586-
}
606+
resultData = filterProjection(data, projection)
587607
}
588608
const res = {
589-
...data,
609+
...resultData,
590610
...rest
591611
} as any as T
592612

0 commit comments

Comments
 (0)