Skip to content

Commit

Permalink
fix: querying maps when the attribute and property names don't match
Browse files Browse the repository at this point in the history
Fixes #678
  • Loading branch information
benhutchins committed Mar 28, 2024
1 parent ad569aa commit d05b57f
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 26 deletions.
7 changes: 4 additions & 3 deletions src/query/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ describe('query/expression', () => {
public someStrings: Set<string>

@Dyngoose.Attribute.Map<ISomeMap>({
name: 'someTestMap',
attributes: {
first: Dyngoose.Attribute.String(),
first: Dyngoose.Attribute.String({ name: 'someFirst' }),
second: Dyngoose.Attribute.String(),
},
})
Expand Down Expand Up @@ -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' },
Expand Down
5 changes: 3 additions & 2 deletions src/query/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ class FilterExpressionQuery<T extends Table> {
}
}

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<any>

Expand Down
11 changes: 11 additions & 0 deletions src/query/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>(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>(TestableTable)
const input = search.getInput()
Expand Down
8 changes: 8 additions & 0 deletions src/setup-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
64 changes: 43 additions & 21 deletions src/tables/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -172,44 +173,57 @@ export class Schema {
}

public getAttributeByPropertyName(propertyName: string): Attribute<any> {
let attribute: Attribute<any> | 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<Attribute<any>> {
const attributes: Array<Attribute<any>> = []

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<any>).attributes
mapAttributesFor: for (const mapAttribute of Object.values(mapAttributes)) {
if (mapAttribute.propertyName === nameSegment) {
attribute = mapAttribute
const children: Record<string, Attribute<any>> = (attribute.type as MapAttributeType<any>).attributes
mapAttributesFor: for (const childAttribute of Object.values(children)) {
if (childAttribute.propertyName === nameSegment) {
attribute = childAttribute
attributes.push(childAttribute)
break mapAttributesFor
}
}
}
}
}
} 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<any>): Attribute<any> {
Expand Down Expand Up @@ -268,4 +282,12 @@ export class Schema {

return attributeMap
}

private findAttributeByPropertyName(propertyName: string): Attribute<any> | undefined {
for (const attribute of this.attributes.values()) {
if (attribute.propertyName === propertyName) {
return attribute
}
}
}
}

0 comments on commit d05b57f

Please sign in to comment.