diff --git a/src/batch-get.spec.ts b/src/batch-get.spec.ts index 17de393a..0a88df9f 100644 --- a/src/batch-get.spec.ts +++ b/src/batch-get.spec.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai' +import { expect, should } from 'chai' import { sortBy } from 'lodash' import { BatchGet } from './batch-get' import { PrimaryKey } from './query/primary-key' @@ -143,6 +143,25 @@ describe('BatchGet', () => { expect(results[0].id).eq(1) }) + it('should not return records when aborted', async () => { + const abortController = new AbortController() + const batch = new BatchGet() + batch.get(TestTable1.primaryKey.fromKey(1)) + batch.get(TestTable1.primaryKey.fromKey(42)) + abortController.abort() + + let exception + try { + await batch.retrieve({ + abortSignal: abortController.signal, + }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) + it('should accept projection expressions', async () => { const batch = new BatchGet() const item = TestTable1.primaryKey.fromKey(1) diff --git a/src/query/global-secondary-index.spec.ts b/src/query/global-secondary-index.spec.ts index 67a1d90a..462c626e 100644 --- a/src/query/global-secondary-index.spec.ts +++ b/src/query/global-secondary-index.spec.ts @@ -90,6 +90,23 @@ describe('Query/GlobalSecondaryIndex', () => { expect(res.records[1].id).to.eq(11) }) + it('should not return items when aborted', async () => { + const abortController = new AbortController() + await Card.new({ id: 10, title: 'abc' }).save() + await Card.new({ id: 11, title: 'abd' }).save() + await Card.new({ id: 12, title: 'abd' }).save() + abortController.abort() + + let exception + try { + await Card.hashTitleIndex.query({ title: 'abd' }, undefined, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) + it('should return an empty array when no items match', async () => { const res = await Card.hashTitleIndex.query({ title: '404' }) expect(res[0]).to.not.eq(null) @@ -174,6 +191,20 @@ describe('Query/GlobalSecondaryIndex', () => { expect(cardIds).to.include.members(res2.records.map((r) => r.id)) expect(cardIds).to.include.members(res3.records.map((r) => r.id)) }) + + it('should not return results when aborted', async () => { + const abortController = new AbortController() + abortController.abort() + + let exception + try { + await Card.hashTitleIndex.scan(null, undefined, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) }) }) diff --git a/src/query/local-secondary-index.spec.ts b/src/query/local-secondary-index.spec.ts index 83f3b07f..112766c9 100644 --- a/src/query/local-secondary-index.spec.ts +++ b/src/query/local-secondary-index.spec.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai' +import { expect, should } from 'chai' import * as Decorator from '../decorator' import { DocumentClient } from '../document-client' import { Table } from '../table' @@ -54,5 +54,31 @@ describe('Query/LocalSecondaryIndex', () => { expect(res.records[0].count).to.eq(4) expect(res.records[1].count).to.eq(3) }) + + it('should not find items when aborted', async () => { + const abortController = new AbortController() + await Card.documentClient.batchPut([ + Card.new({ id: 10, title: 'a', count: 4 }), + Card.new({ id: 10, title: 'b', count: 3 }), + Card.new({ id: 10, title: 'c', count: 2 }), + Card.new({ id: 10, title: 'd', count: 1 }), + ]) + + abortController.abort() + + let exception + try { + await Card.countIndex.query({ + id: 10, + count: ['>', 2], + }, { + rangeOrder: 'DESC', + }, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) }) }) diff --git a/src/query/primary-key.spec.ts b/src/query/primary-key.spec.ts index 2792698a..4518dcb1 100644 --- a/src/query/primary-key.spec.ts +++ b/src/query/primary-key.spec.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai' +import { expect, should } from 'chai' import { Table } from '../table' import { PrimaryKey } from './primary-key' @@ -74,6 +74,22 @@ describe('Query/PrimaryKey', () => { expect(item).to.eq(undefined) }) + it('should not find item using a query filter object when aborted', async () => { + const abortController = new AbortController() + + await Card.new({ id: 10, title: 'abc' }).save() + abortController.abort() + + let exception + try { + await primaryKey.get({ id: 10, title: 'abc' }, undefined, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) + it('should find item using a query filter object', async () => { await Card.new({ id: 10, title: 'abc' }).save() const item = await primaryKey.get({ id: 10, title: 'abc' }) @@ -94,6 +110,22 @@ describe('Query/PrimaryKey', () => { } }) + it('should not find item using hash and range arguments when aborted', async () => { + const abortController = new AbortController() + await Card.new({ id: 10, title: 'abc' }).save() + + abortController.abort() + + let exception + try { + await primaryKey.get(10, 'abc', undefined, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) + it('should allow date type to be the range', async () => { const now = new Date() await TableWithDateRange.new({ id: 1, date: now }).save() @@ -188,6 +220,26 @@ describe('Query/PrimaryKey', () => { expect(res.records[0].title).to.eq('aba') expect(res.records[1].title).to.eq('abc') }) + + it('should not find items when aborted', async () => { + const abortController = new AbortController() + + await Card.new({ id: 10, title: 'abc' }).save() + await Card.new({ id: 10, title: 'abd' }).save() + await Card.new({ id: 10, title: 'aba' }).save() + abortController.abort() + + let exception + try { + await primaryKey.scan(null, { + limit: 2, + }, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) }) describe('#update', () => { diff --git a/src/query/primary-key.ts b/src/query/primary-key.ts index e8d76452..2e21f3af 100644 --- a/src/query/primary-key.ts +++ b/src/query/primary-key.ts @@ -170,14 +170,14 @@ export class PrimaryKey>, requestOptions?: IRequestOptions): Promise { + public async batchGet(inputs: Array>): Promise { const batch = new BatchGet() for (const input of inputs) { batch.get(this.fromKey(input[0], input[1])) } - return await batch.retrieve(requestOptions) + return await batch.retrieve() } /** diff --git a/src/query/search.spec.ts b/src/query/search.spec.ts index c95b45fd..ea025534 100644 --- a/src/query/search.spec.ts +++ b/src/query/search.spec.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai' +import { expect, should } from 'chai' import { TestableTable } from '../setup-tests.spec' import { MagicSearch } from './search' @@ -27,6 +27,24 @@ describe('Query/Search', () => { expect(result.records[0].lowercaseString).to.eq('table search 0') }) + it('should not search when aborted', async () => { + const abortController = new AbortController() + + const search = new MagicSearch(TestableTable, { title: 'Table.search 0' }) + const input = search.getInput() + expect(input.IndexName).to.eq('titleIndex') + abortController.abort() + + let exception + try { + await search.exec({ abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) + it('should ignore index if you are using a special condition', async () => { const search = new MagicSearch(TestableTable, { title: ['contains', 'Table.search'], @@ -237,6 +255,26 @@ describe('Query/Search', () => { const output = await search.all() expect(output.length).to.eq(8) }) + + it('should not return results when aborted', async () => { + const abortController = new AbortController() + const search = new MagicSearch(TestableTable) + .filter('title').contains('Table.search') + .limit(2) + + // we set a limit and then called .all(), so it should page automatically until all results are found + // this is stupid and slow, it would be faster to remove the limit, but we are testing the paging logic of .all + abortController.abort() + + let exception + try { + await search.all({ abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) }) describe('#minimum', () => { @@ -249,5 +287,24 @@ describe('Query/Search', () => { const output = await search.minimum(5) expect(output.length).to.be.at.least(5) }) + + it('should not return results when aborted', async () => { + const abortController = new AbortController() + + const search = new MagicSearch(TestableTable) + .filter('title').contains('Table.search') + .limit(2) + + abortController.abort() + + let exception + try { + await search.minimum(5, { abortSignal: abortController.signal }) + } catch (ex) { + exception = ex + } + + should().exist(exception) + }) }) }) diff --git a/src/table.ts b/src/table.ts index 71ed888f..89c5d629 100644 --- a/src/table.ts +++ b/src/table.ts @@ -21,6 +21,7 @@ import { migrateTable } from './tables/migrate-table' import { type TablePropertyValue, type TableProperties, type TableProperty } from './tables/properties' import { Schema } from './tables/schema' import { isTrulyEmpty } from './utils/truly-empty' +import { type IRequestOptions } from './connections' type StaticThis = new() => T @@ -142,8 +143,8 @@ export class Table { return await deleteTable(this.schema) } - public static async describeTable(): Promise { - return await describeTable(this.schema) + public static async describeTable(requestOptions?: IRequestOptions): Promise { + return await describeTable(this.schema, requestOptions) } // #endregion static methods // #endregion static