From d55ff22376626c5491f9bba5d91b3b205bbdbf25 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Wed, 30 Aug 2023 23:56:48 +0200 Subject: [PATCH] fix(orm): support deep paths for index signature properties in query filter --- .../bson/src/bson-deserializer-templates.ts | 4 +-- packages/bson/src/bson-serializer.ts | 2 +- packages/core/src/compiler.ts | 10 ++++++++ packages/postgres/tests/postgres.spec.ts | 25 ++++++++++++++++++- packages/type/src/change-detector.ts | 2 +- packages/type/src/path.ts | 18 +++++++++++-- packages/type/src/serializer.ts | 22 ++++++---------- 7 files changed, 62 insertions(+), 21 deletions(-) diff --git a/packages/bson/src/bson-deserializer-templates.ts b/packages/bson/src/bson-deserializer-templates.ts index 44359b99b..b14e392ca 100644 --- a/packages/bson/src/bson-deserializer-templates.ts +++ b/packages/bson/src/bson-deserializer-templates.ts @@ -757,7 +757,7 @@ export function deserializeObjectLiteral(type: TypeClass | TypeObjectLiteral, st for (const signature of signatures) { const check = isOptional(signature.type) ? `` : `elementType !== ${BSONType.UNDEFINED} &&`; - signatureLines.push(`else if (${check} ${getIndexCheck(state, i, signature.index)}) { + signatureLines.push(`else if (${check} ${getIndexCheck(state.compilerContext, i, signature.index)}) { ${executeTemplates(state.fork(`${object}[${i}]`).extendPath(new RuntimeCode(i)).forPropertyName(new RuntimeCode(i)), signature.type)} continue; }`); @@ -931,7 +931,7 @@ export function bsonTypeGuardObjectLiteral(type: TypeClass | TypeObjectLiteral, sortSignatures(signatures); for (const signature of signatures) { - signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)}) { + signatureLines.push(`else if (${getIndexCheck(state.compilerContext, i, signature.index)}) { ${executeTemplates(state.fork(valid).extendPath(new RuntimeCode(i)).forPropertyName(new RuntimeCode(i)), signature.type)} if (!${valid}) break; diff --git a/packages/bson/src/bson-serializer.ts b/packages/bson/src/bson-serializer.ts index 535ff6f1c..d65f9526a 100644 --- a/packages/bson/src/bson-serializer.ts +++ b/packages/bson/src/bson-serializer.ts @@ -783,7 +783,7 @@ function handleObjectLiteral( ? executeTemplates(propertyState.fork().forPropertyName(new RuntimeCode(i)), { kind: ReflectionKind.undefined }) : isNullable(signature.type) ? executeTemplates(propertyState.fork().forPropertyName(new RuntimeCode(i)), { kind: ReflectionKind.null }) : ''; - signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)}) { + signatureLines.push(`else if (${getIndexCheck(state.compilerContext, i, signature.index)}) { if (${accessor} === undefined) { ${setUndefined} } else { diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index a619dba02..5034625d8 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -9,6 +9,7 @@ */ // @ts-ignore import { indent } from './indent.js'; +import { hasProperty } from './core.js'; export class CompilerContext { public readonly context = new Map(); @@ -45,6 +46,15 @@ export class CompilerContext { throw new Error(`Too many context variables (max ${this.maxReservedVariable})`); } + set(values: { [name: string]: any }) { + for (const i in values) { + if (!hasProperty(values, i)) { + continue; + } + this.context.set(i, values[i]); + } + } + /** * Returns always the same variable name for the same value. * The variable name should not be set afterwards. diff --git a/packages/postgres/tests/postgres.spec.ts b/packages/postgres/tests/postgres.spec.ts index 6b6eb0122..ff9a9f80e 100644 --- a/packages/postgres/tests/postgres.spec.ts +++ b/packages/postgres/tests/postgres.spec.ts @@ -1,4 +1,4 @@ -import { AutoIncrement, entity, PrimaryKey } from '@deepkit/type'; +import { AutoIncrement, cast, entity, PrimaryKey } from '@deepkit/type'; import { expect, test } from '@jest/globals'; import pg from 'pg'; import { databaseFactory } from './factory.js'; @@ -158,3 +158,26 @@ test('for update/share', async () => { const items = await database.query(Model).forUpdate().find(); expect(items).toHaveLength(2); }); + +test('json field and query', async () => { + @entity.name('product').collection('products') + class Product { + id: number & PrimaryKey & AutoIncrement = 0; + raw?: { [key: string]: any }; + } + + const database = await databaseFactory([Product]); + + await database.persist(cast({ raw: { productId: 1, name: 'first' } })); + await database.persist(cast({ raw: { productId: 2, name: 'second' } })); + + { + const res = await database.query(Product).filter({ 'raw.productId': 1 }).find(); + expect(res).toMatchObject([{ id: 1, raw: { productId: 1, name: 'first' } }]); + } + + { + const res = await database.query(Product).filter({ 'raw.productId': 2 }).find(); + expect(res).toMatchObject([{ id: 2, raw: { productId: 2, name: 'second' } }]); + } +}); diff --git a/packages/type/src/change-detector.ts b/packages/type/src/change-detector.ts index c9e51f314..ba65e461a 100644 --- a/packages/type/src/change-detector.ts +++ b/packages/type/src/change-detector.ts @@ -222,7 +222,7 @@ function createJITChangeDetectorForSnapshot(schema: ReflectionClass, stateI const itemAccessor = new ContainerAccessor('item', i); for (const signature of signatures) { - signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)}) { + signatureLines.push(`else if (${getIndexCheck(state.compilerContext, i, signature.index)}) { ${getComparator(signature.type, lastAccessor, currentAccessor, itemAccessor, i, '', state)} }`); } diff --git a/packages/type/src/path.ts b/packages/type/src/path.ts index e34205765..0704843a4 100644 --- a/packages/type/src/path.ts +++ b/packages/type/src/path.ts @@ -1,7 +1,7 @@ import { getTypeJitContainer, ReflectionKind, Type } from './reflection/type.js'; import { CompilerContext, toFastProperties } from '@deepkit/core'; import { ReceiveType, resolveReceiveType } from './reflection/reflection.js'; -import { JitStack } from './serializer.js'; +import { getIndexCheck, JitStack } from './serializer.js'; export type Resolver = (path: string) => Type | undefined; @@ -31,7 +31,9 @@ function pathResolverCode(type: Type, compilerContext: CompilerContext, jitStack } export function resolvePath(path: string, type?: ReceiveType): Type { - const t = pathResolver(resolveReceiveType(type))(path); + const resolver = pathResolver(resolveReceiveType(type)); + debugger; + const t = resolver(path); if (!t) throw new Error(`No type found for path ${path}`); return t; } @@ -44,6 +46,7 @@ export function pathResolver(type?: ReceiveType, jitStack: JitStack = new if (type.kind === ReflectionKind.objectLiteral || type.kind === ReflectionKind.class) { const compilerContext = new CompilerContext(); const lines: string[] = []; + const defaultCase: string[] = []; for (const member of type.types) { if (member.kind === ReflectionKind.propertySignature || member.kind === ReflectionKind.property) { @@ -53,6 +56,14 @@ export function pathResolver(type?: ReceiveType, jitStack: JitStack = new if (path === '') return ${compilerContext.reserveVariable('type', member)}; ${pathResolverCode(member.type, compilerContext, jitStack)} }`); + } else if (member.kind === ReflectionKind.indexSignature) { + const checkValid = compilerContext.reserveName('check'); + defaultCase.push(`else if (${getIndexCheck(compilerContext, 'pathName', member.index)}) { + let ${checkValid} = false; + if (!${checkValid}) { + ${pathResolverCode(member.type, compilerContext, jitStack)} + } + }`); } } @@ -63,6 +74,9 @@ export function pathResolver(type?: ReceiveType, jitStack: JitStack = new switch(pathName) { ${lines.join('\n')} + default: { + if (false) {} ${defaultCase.join('\n')} + } } `; diff --git a/packages/type/src/serializer.ts b/packages/type/src/serializer.ts index dc3c8936f..4314f49b7 100644 --- a/packages/type/src/serializer.ts +++ b/packages/type/src/serializer.ts @@ -510,13 +510,7 @@ export class TemplateState { } setContext(values: { [name: string]: any }) { - for (const i in values) { - if (!hasProperty(values, i)) { - console.log('hasProperty is false: ', i, values[i], hasProperty(values, i)); - continue; - } - this.compilerContext.context.set(i, values[i]); - } + this.compilerContext.set(values); } addCode(code: string) { @@ -924,20 +918,20 @@ export function deserializeEmbedded(type: TypeClass | TypeObjectLiteral, state: `; } -export function getIndexCheck(state: TemplateState, i: string, type: Type): string { +export function getIndexCheck(context: CompilerContext, i: string, type: Type): string { if (type.kind === ReflectionKind.number) { - state.setContext({ isNumeric: isNumeric }); + context.set({ isNumeric: isNumeric }); return `isNumeric(${i})`; } else if (type.kind === ReflectionKind.string || type.kind === ReflectionKind.any) { return `'string' === typeof ${i}`; } else if (type.kind === ReflectionKind.symbol) { return `'symbol' === typeof ${i}`; } else if (type.kind === ReflectionKind.templateLiteral) { - state.setContext({ extendTemplateLiteral: extendTemplateLiteral }); - const typeVar = state.setVariable('type', type); + context.set({ extendTemplateLiteral: extendTemplateLiteral }); + const typeVar = context.reserveVariable('type', type); return `'string' === typeof ${i} && extendTemplateLiteral({kind: ${ReflectionKind.literal}, literal: ${i}}, ${typeVar})`; } else if (type.kind === ReflectionKind.union) { - return '(' + type.types.map(v => getIndexCheck(state, i, v)).join(' || ') + ')'; + return '(' + type.types.map(v => getIndexCheck(context, i, v)).join(' || ') + ')'; } return ''; } @@ -1154,7 +1148,7 @@ export function serializeObjectLiteral(type: TypeObjectLiteral | TypeClass, stat sortSignatures(signatures); for (const signature of signatures) { - signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)} && ${groupFilter(signature.type)}) { + signatureLines.push(`else if (${getIndexCheck(state.compilerContext, i, signature.index)} && ${groupFilter(signature.type)}) { ${createConverterJSForMember(signature, state.fork(new ContainerAccessor(v, i), new ContainerAccessor(state.accessor, i)).extendPath(new RuntimeCode(i)))} }`); } @@ -1321,7 +1315,7 @@ export function typeGuardObjectLiteral(type: TypeObjectLiteral | TypeClass, stat for (const signature of signatures) { const checkValid = state.compilerContext.reserveName('check'); const checkTemplate = executeTemplates(state.fork(checkValid, new ContainerAccessor(state.accessor, i)).extendPath(new RuntimeCode(i)), signature.type).trim(); - signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)}) { + signatureLines.push(`else if (${getIndexCheck(state.compilerContext, i, signature.index)}) { let ${checkValid} = false; ${checkTemplate || `// no template found for signature.type.kind=${signature.type.kind}`} if (!${checkValid}) ${state.setter} = false;