From d05b57f27c3a1c7d27bc64190c370291f0cadc23 Mon Sep 17 00:00:00 2001 From: Benjamin Hutchins Date: Thu, 28 Mar 2024 15:06:02 -0400 Subject: [PATCH] fix: querying maps when the attribute and property names don't match Fixes #678 --- src/query/expression.spec.ts | 7 ++-- src/query/expression.ts | 5 +-- src/query/search.spec.ts | 11 +++++++ src/setup-tests.spec.ts | 8 +++++ src/tables/schema.ts | 64 ++++++++++++++++++++++++------------ 5 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/query/expression.spec.ts b/src/query/expression.spec.ts index 7a4b5c47..f40ea79e 100644 --- a/src/query/expression.spec.ts +++ b/src/query/expression.spec.ts @@ -38,8 +38,9 @@ describe('query/expression', () => { public someStrings: Set @Dyngoose.Attribute.Map({ + name: 'someTestMap', attributes: { - first: Dyngoose.Attribute.String(), + first: Dyngoose.Attribute.String({ name: 'someFirst' }), second: Dyngoose.Attribute.String(), }, }) @@ -106,8 +107,8 @@ describe('query/expression', () => { })).to.deep.equal({ FilterExpression: '#a00.#a01 = :v0', ExpressionAttributeNames: { - '#a00': 'someMap', - '#a01': 'first', + '#a00': 'someTestMap', + '#a01': 'someFirst', }, ExpressionAttributeValues: { ':v0': { S: 'bobby' }, diff --git a/src/query/expression.ts b/src/query/expression.ts index 283db5fd..cd18b439 100644 --- a/src/query/expression.ts +++ b/src/query/expression.ts @@ -105,8 +105,9 @@ class FilterExpressionQuery { } } - private handleFilter(attrName: string, value: any, push = true): QueryFilterQuery { - const attribute = this.schema.getAttributeByPropertyName(attrName) + private handleFilter(propertyName: string, value: any, push = true): QueryFilterQuery { + const attribute = this.schema.getAttributeByPropertyName(propertyName) + const attrName = this.schema.transformPropertyPathToAttributePath(propertyName) let filter: Filter diff --git a/src/query/search.spec.ts b/src/query/search.spec.ts index 0f2417fc..f4fce594 100644 --- a/src/query/search.spec.ts +++ b/src/query/search.spec.ts @@ -122,6 +122,17 @@ describe('Query/Search', () => { expect(result.count).to.eq(3) }) + it('should support filtering on children of maps', async () => { + const search = new MagicSearch(TestableTable) + .filter('testMap', 'property1').eq('test') + const input = search.getInput() + expect(input.FilterExpression).to.eq('#a00.#a01 = :v0') + expect(input.ExpressionAttributeNames).to.deep.eq({ + '#a00': 'someMap', + '#a01': 'someProperty1', + }) + }) + it('ConsistentRead defaults to false', async () => { const search = new MagicSearch(TestableTable) const input = search.getInput() diff --git a/src/setup-tests.spec.ts b/src/setup-tests.spec.ts index 3f6a8687..0fbf503a 100644 --- a/src/setup-tests.spec.ts +++ b/src/setup-tests.spec.ts @@ -82,6 +82,14 @@ export class TestableTable extends Dyngoose.Table { @Dyngoose.Attribute.String({ name: 'testAttributeNameNotMatchingPropertyName' }) public testAttributeNaming: string + + @Dyngoose.Attribute.Map({ + name: 'someMap', + attributes: { + property1: Dyngoose.Attribute.String({ name: 'someProperty1' }), + }, + }) + public testMap?: { property1?: string } } before(async () => { diff --git a/src/tables/schema.ts b/src/tables/schema.ts index a1a92d25..93245c92 100644 --- a/src/tables/schema.ts +++ b/src/tables/schema.ts @@ -7,6 +7,7 @@ import type * as Metadata from '../metadata' import * as Query from '../query' import { type ITable, type Table } from '../table' import { createTableInput } from './create-table-input' +import { last } from 'lodash' export class Schema { public isDyngoose = true @@ -172,25 +173,42 @@ export class Schema { } public getAttributeByPropertyName(propertyName: string): Attribute { - let attribute: Attribute | undefined + const attributes = this.getAttributePathByPropertyName(propertyName) + const attribute = last(attributes) + + if (attribute == null) { + throw new SchemaError(`Schema for ${this.name} has no attribute by property name ${propertyName}`) + } else { + return attribute + } + } + + public transformPropertyPathToAttributePath(propertyName: string): string { + const attributes = this.getAttributePathByPropertyName(propertyName) + const segments = attributes.map(attribute => attribute.name) + return segments.join('.') + } + + public getAttributePathByPropertyName(propertyName: string): Array> { + const attributes: Array> = [] if (propertyName.includes('.')) { const nameSegments = propertyName.split('.') - const firstSegment = nameSegments.shift() + const firstSegment = nameSegments.shift()! + const newSegments: string[] = [] + let attribute = this.findAttributeByPropertyName(firstSegment) - if (firstSegment != null) { - for (const attr of this.attributes.values()) { - if (attr.propertyName === firstSegment) { - attribute = attr - } - } + if (attribute != null) { + attributes.push(attribute) + newSegments.push(attribute.name) for (const nameSegment of nameSegments) { if (attribute != null) { - const mapAttributes = (attribute.type as MapAttributeType).attributes - mapAttributesFor: for (const mapAttribute of Object.values(mapAttributes)) { - if (mapAttribute.propertyName === nameSegment) { - attribute = mapAttribute + const children: Record> = (attribute.type as MapAttributeType).attributes + mapAttributesFor: for (const childAttribute of Object.values(children)) { + if (childAttribute.propertyName === nameSegment) { + attribute = childAttribute + attributes.push(childAttribute) break mapAttributesFor } } @@ -198,18 +216,14 @@ export class Schema { } } } else { - for (const attr of this.attributes.values()) { - if (attr.propertyName === propertyName) { - attribute = attr - } + const attribute = this.findAttributeByPropertyName(propertyName) + + if (attribute != null) { + attributes.push(attribute) } } - if (attribute == null) { - throw new SchemaError(`Schema for ${this.name} has no attribute by property name ${propertyName}`) - } else { - return attribute - } + return attributes } public addAttribute(attribute: Attribute): Attribute { @@ -268,4 +282,12 @@ export class Schema { return attributeMap } + + private findAttributeByPropertyName(propertyName: string): Attribute | undefined { + for (const attribute of this.attributes.values()) { + if (attribute.propertyName === propertyName) { + return attribute + } + } + } }