diff --git a/packages/entity-database-adapter-knex-testing-utils/src/StubPostgresDatabaseAdapter.ts b/packages/entity-database-adapter-knex-testing-utils/src/StubPostgresDatabaseAdapter.ts index b090275db..718b85c11 100644 --- a/packages/entity-database-adapter-knex-testing-utils/src/StubPostgresDatabaseAdapter.ts +++ b/packages/entity-database-adapter-knex-testing-utils/src/StubPostgresDatabaseAdapter.ts @@ -253,7 +253,7 @@ export class StubPostgresDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { // SQL does not support empty updates, mirror behavior here for better test simulation if (Object.keys(object).length === 0) { throw new Error(`Empty update (${tableIdField} = ${id})`); @@ -268,14 +268,14 @@ export class StubPostgresDatabaseAdapter< // SQL updates to a nonexistent row succeed but affect 0 rows, // mirror that behavior here for better test simulation if (objectIndex < 0) { - return []; + return { updatedRowCount: 0 }; } objectCollection[objectIndex] = { ...objectCollection[objectIndex], ...object, }; - return [objectCollection[objectIndex]]; + return { updatedRowCount: 1 }; } protected async deleteInternalAsync( diff --git a/packages/entity-database-adapter-knex-testing-utils/src/__tests__/StubPostgresDatabaseAdapter-test.ts b/packages/entity-database-adapter-knex-testing-utils/src/__tests__/StubPostgresDatabaseAdapter-test.ts index 39e912c17..c3c42c9c9 100644 --- a/packages/entity-database-adapter-knex-testing-utils/src/__tests__/StubPostgresDatabaseAdapter-test.ts +++ b/packages/entity-database-adapter-knex-testing-utils/src/__tests__/StubPostgresDatabaseAdapter-test.ts @@ -573,13 +573,9 @@ describe(StubPostgresDatabaseAdapter, () => { ]), ), ); - const result = await databaseAdapter.updateAsync(queryContext, 'customIdField', 'hello', { + await databaseAdapter.updateAsync(queryContext, 'customIdField', 'hello', { stringField: 'b', }); - expect(result).toMatchObject({ - stringField: 'b', - testIndexedField: 'h1', - }); }); it('throws error when empty update to match common DBMS behavior', async () => { diff --git a/packages/entity-database-adapter-knex/src/PostgresEntityDatabaseAdapter.ts b/packages/entity-database-adapter-knex/src/PostgresEntityDatabaseAdapter.ts index d03e37eec..60746c225 100644 --- a/packages/entity-database-adapter-knex/src/PostgresEntityDatabaseAdapter.ts +++ b/packages/entity-database-adapter-knex/src/PostgresEntityDatabaseAdapter.ts @@ -257,10 +257,11 @@ export class PostgresEntityDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { - return await wrapNativePostgresCallAsync(() => - queryInterface.update(object).into(tableName).where(tableIdField, id).returning('*'), + ): Promise<{ updatedRowCount: number }> { + const updatedRowCount = await wrapNativePostgresCallAsync(() => + queryInterface.update(object).into(tableName).where(tableIdField, id), ); + return { updatedRowCount }; } protected async deleteInternalAsync( diff --git a/packages/entity-database-adapter-knex/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts b/packages/entity-database-adapter-knex/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts index 77312bc86..8ce78a54d 100644 --- a/packages/entity-database-adapter-knex/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +++ b/packages/entity-database-adapter-knex/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts @@ -18,7 +18,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter< private readonly fetchResults: object[]; private readonly fetchOneResult: object | null; private readonly insertResults: object[]; - private readonly updateResults: object[]; + private readonly updateResults: { updatedRowCount: number }; private readonly fetchEqualityConditionResults: object[]; private readonly fetchRawWhereResults: object[]; private readonly fetchSQLFragmentResults: object[]; @@ -28,7 +28,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter< fetchResults = [], fetchOneResult = null, insertResults = [], - updateResults = [], + updateResults = { updatedRowCount: 0 }, fetchEqualityConditionResults = [], fetchRawWhereResults = [], fetchSQLFragmentResults = [], @@ -37,7 +37,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter< fetchResults?: object[]; fetchOneResult?: object | null; insertResults?: object[]; - updateResults?: object[]; + updateResults?: { updatedRowCount: number }; fetchEqualityConditionResults?: object[]; fetchRawWhereResults?: object[]; fetchSQLFragmentResults?: object[]; @@ -116,7 +116,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter< _tableIdField: string, _id: any, _object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { return this.updateResults; } diff --git a/packages/entity-database-adapter-knex/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts b/packages/entity-database-adapter-knex/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts index 9085c75c7..52a1c9347 100644 --- a/packages/entity-database-adapter-knex/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +++ b/packages/entity-database-adapter-knex/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts @@ -254,7 +254,7 @@ export class StubPostgresDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { // SQL does not support empty updates, mirror behavior here for better test simulation if (Object.keys(object).length === 0) { throw new Error(`Empty update (${tableIdField} = ${id})`); @@ -269,14 +269,14 @@ export class StubPostgresDatabaseAdapter< // SQL updates to a nonexistent row succeed but affect 0 rows, // mirror that behavior here for better test simulation if (objectIndex < 0) { - return []; + return { updatedRowCount: 0 }; } objectCollection[objectIndex] = { ...objectCollection[objectIndex], ...object, }; - return [objectCollection[objectIndex]]; + return { updatedRowCount: 1 }; } protected async deleteInternalAsync( diff --git a/packages/entity-example/src/adapters/InMemoryDatabaseAdapter.ts b/packages/entity-example/src/adapters/InMemoryDatabaseAdapter.ts index d0a5c589c..73c00f332 100644 --- a/packages/entity-example/src/adapters/InMemoryDatabaseAdapter.ts +++ b/packages/entity-example/src/adapters/InMemoryDatabaseAdapter.ts @@ -85,7 +85,7 @@ class InMemoryDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { // SQL does not support empty updates, mirror behavior here for better test simulation if (Object.keys(object).length === 0) { throw new Error(`Empty update (${tableIdField} = ${id})`); @@ -98,14 +98,14 @@ class InMemoryDatabaseAdapter< // SQL updates to a nonexistent row succeed but affect 0 rows, // mirror that behavior here for better test simulation if (objectIndex < 0) { - return []; + return { updatedRowCount: 0 }; } dbObjects[objectIndex] = { ...dbObjects[objectIndex], ...object, }; - return [dbObjects[objectIndex]]; + return { updatedRowCount: 1 }; } protected async deleteInternalAsync( diff --git a/packages/entity-testing-utils/src/StubDatabaseAdapter.ts b/packages/entity-testing-utils/src/StubDatabaseAdapter.ts index 513856157..5d8f141ee 100644 --- a/packages/entity-testing-utils/src/StubDatabaseAdapter.ts +++ b/packages/entity-testing-utils/src/StubDatabaseAdapter.ts @@ -135,7 +135,7 @@ export class StubDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { // SQL does not support empty updates, mirror behavior here for better test simulation if (Object.keys(object).length === 0) { throw new Error(`Empty update (${tableIdField} = ${id})`); @@ -150,14 +150,14 @@ export class StubDatabaseAdapter< // SQL updates to a nonexistent row succeed but affect 0 rows, // mirror that behavior here for better test simulation if (objectIndex < 0) { - return []; + return { updatedRowCount: 0 }; } objectCollection[objectIndex] = { ...objectCollection[objectIndex], ...object, }; - return [objectCollection[objectIndex]]; + return { updatedRowCount: 1 }; } protected async deleteInternalAsync( diff --git a/packages/entity-testing-utils/src/__tests__/StubDatabaseAdapter-test.ts b/packages/entity-testing-utils/src/__tests__/StubDatabaseAdapter-test.ts index ec00aea15..68d0b4a33 100644 --- a/packages/entity-testing-utils/src/__tests__/StubDatabaseAdapter-test.ts +++ b/packages/entity-testing-utils/src/__tests__/StubDatabaseAdapter-test.ts @@ -279,13 +279,9 @@ describe(StubDatabaseAdapter, () => { ]), ), ); - const result = await databaseAdapter.updateAsync(queryContext, 'customIdField', 'hello', { + await databaseAdapter.updateAsync(queryContext, 'customIdField', 'hello', { stringField: 'b', }); - expect(result).toMatchObject({ - stringField: 'b', - testIndexedField: 'h1', - }); }); it('throws error when empty update to match common DBMS behavior', async () => { diff --git a/packages/entity/src/EntityDatabaseAdapter.ts b/packages/entity/src/EntityDatabaseAdapter.ts index cc6250a37..68babcf12 100644 --- a/packages/entity/src/EntityDatabaseAdapter.ts +++ b/packages/entity/src/EntityDatabaseAdapter.ts @@ -202,21 +202,20 @@ export abstract class EntityDatabaseAdapter< * @param idField - the field in the object that is the ID * @param id - the value of the ID field in the object * @param object - the object to update - * @returns the updated object */ async updateAsync( queryContext: EntityQueryContext, idField: K, id: any, object: Readonly>, - ): Promise> { + ): Promise { const idColumn = getDatabaseFieldForEntityField(this.entityConfiguration, idField); const dbObject = transformFieldsToDatabaseObject( this.entityConfiguration, this.fieldTransformerMap, object, ); - const results = await this.updateInternalAsync( + const { updatedRowCount } = await this.updateInternalAsync( queryContext.getQueryInterface(), this.entityConfiguration.tableName, idColumn, @@ -224,24 +223,18 @@ export abstract class EntityDatabaseAdapter< dbObject, ); - if (results.length > 1) { + if (updatedRowCount > 1) { // This should never happen with a properly implemented database adapter unless the underlying table has a non-unique // primary key column. throw new EntityDatabaseAdapterExcessiveUpdateResultError( `Excessive results from database adapter update: ${this.entityConfiguration.tableName}(id = ${id})`, ); - } else if (results.length === 0) { + } else if (updatedRowCount === 0) { // This happens when the object to update does not exist. It may have been deleted by another process. throw new EntityDatabaseAdapterEmptyUpdateResultError( `Empty results from database adapter update: ${this.entityConfiguration.tableName}(id = ${id})`, ); } - - return transformDatabaseObjectToFields( - this.entityConfiguration, - this.fieldTransformerMap, - results[0]!, - ); } protected abstract updateInternalAsync( @@ -250,7 +243,7 @@ export abstract class EntityDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise; + ): Promise<{ updatedRowCount: number }>; /** * Delete an object by ID. diff --git a/packages/entity/src/__tests__/EntityDatabaseAdapter-test.ts b/packages/entity/src/__tests__/EntityDatabaseAdapter-test.ts index e4603d571..cc50e4b5b 100644 --- a/packages/entity/src/__tests__/EntityDatabaseAdapter-test.ts +++ b/packages/entity/src/__tests__/EntityDatabaseAdapter-test.ts @@ -23,20 +23,20 @@ class TestEntityDatabaseAdapter extends EntityDatabaseAdapter { + ): Promise<{ updatedRowCount: number }> { return this.updateResults; } @@ -256,16 +256,15 @@ describe(EntityDatabaseAdapter, () => { }); describe('updateAsync', () => { - it('transforms object', async () => { + it('succeeds when one row updated', async () => { const queryContext = instance(mock(EntityQueryContext)); - const adapter = new TestEntityDatabaseAdapter({ updateResults: [{ string_field: 'hello' }] }); - const result = await adapter.updateAsync(queryContext, 'customIdField', 'wat', {}); - expect(result).toEqual({ stringField: 'hello' }); + const adapter = new TestEntityDatabaseAdapter({ updateResults: { updatedRowCount: 1 } }); + await adapter.updateAsync(queryContext, 'customIdField', 'wat', {}); }); it('throws when update result count zero', async () => { const queryContext = instance(mock(EntityQueryContext)); - const adapter = new TestEntityDatabaseAdapter({ updateResults: [] }); + const adapter = new TestEntityDatabaseAdapter({ updateResults: { updatedRowCount: 0 } }); await expect(adapter.updateAsync(queryContext, 'customIdField', 'wat', {})).rejects.toThrow( EntityDatabaseAdapterEmptyUpdateResultError, ); @@ -274,7 +273,7 @@ describe(EntityDatabaseAdapter, () => { it('throws when update result count greater than 1', async () => { const queryContext = instance(mock(EntityQueryContext)); const adapter = new TestEntityDatabaseAdapter({ - updateResults: [{ string_field: 'hello' }, { string_field: 'hello2' }], + updateResults: { updatedRowCount: 2 }, }); await expect(adapter.updateAsync(queryContext, 'customIdField', 'wat', {})).rejects.toThrow( EntityDatabaseAdapterExcessiveUpdateResultError, diff --git a/packages/entity/src/utils/__testfixtures__/StubDatabaseAdapter.ts b/packages/entity/src/utils/__testfixtures__/StubDatabaseAdapter.ts index 6f32b3488..25b86c5a1 100644 --- a/packages/entity/src/utils/__testfixtures__/StubDatabaseAdapter.ts +++ b/packages/entity/src/utils/__testfixtures__/StubDatabaseAdapter.ts @@ -135,7 +135,7 @@ export class StubDatabaseAdapter< tableIdField: string, id: any, object: object, - ): Promise { + ): Promise<{ updatedRowCount: number }> { // SQL does not support empty updates, mirror behavior here for better test simulation if (Object.keys(object).length === 0) { throw new Error(`Empty update (${tableIdField} = ${id})`); @@ -150,14 +150,14 @@ export class StubDatabaseAdapter< // SQL updates to a nonexistent row succeed but affect 0 rows, // mirror that behavior here for better test simulation if (objectIndex < 0) { - return []; + return { updatedRowCount: 0 }; } objectCollection[objectIndex] = { ...objectCollection[objectIndex], ...object, }; - return [objectCollection[objectIndex]]; + return { updatedRowCount: 1 }; } protected async deleteInternalAsync(