Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export class StubPostgresDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
): 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})`);
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,11 @@ export class PostgresEntityDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -28,7 +28,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter<
fetchResults = [],
fetchOneResult = null,
insertResults = [],
updateResults = [],
updateResults = { updatedRowCount: 0 },
fetchEqualityConditionResults = [],
fetchRawWhereResults = [],
fetchSQLFragmentResults = [],
Expand All @@ -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[];
Expand Down Expand Up @@ -116,7 +116,7 @@ class TestEntityDatabaseAdapter extends BasePostgresEntityDatabaseAdapter<
_tableIdField: string,
_id: any,
_object: object,
): Promise<object[]> {
): Promise<{ updatedRowCount: number }> {
return this.updateResults;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export class StubPostgresDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
): 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})`);
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class InMemoryDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
): 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})`);
Expand All @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions packages/entity-testing-utils/src/StubDatabaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class StubDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
): 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})`);
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
17 changes: 5 additions & 12 deletions packages/entity/src/EntityDatabaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,46 +202,39 @@ 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<K extends keyof TFields>(
queryContext: EntityQueryContext,
idField: K,
id: any,
object: Readonly<Partial<TFields>>,
): Promise<Readonly<TFields>> {
): Promise<void> {
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,
id,
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(
Expand All @@ -250,7 +243,7 @@ export abstract class EntityDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]>;
): Promise<{ updatedRowCount: number }>;

/**
* Delete an object by ID.
Expand Down
19 changes: 9 additions & 10 deletions packages/entity/src/__tests__/EntityDatabaseAdapter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ class TestEntityDatabaseAdapter extends EntityDatabaseAdapter<TestFields, 'custo
private readonly fetchResults: object[];
private readonly fetchOneResult: object | null;
private readonly insertResults: object[];
private readonly updateResults: object[];
private readonly updateResults: { updatedRowCount: number };
private readonly deleteCount: number;

constructor({
fetchResults = [],
fetchOneResult = null,
insertResults = [],
updateResults = [],
updateResults = { updatedRowCount: 0 },
deleteCount = 0,
}: {
fetchResults?: object[];
fetchOneResult?: object | null;
insertResults?: object[];
updateResults?: object[];
updateResults?: { updatedRowCount: number };
deleteCount?: number;
}) {
super(testEntityConfiguration);
Expand Down Expand Up @@ -83,7 +83,7 @@ class TestEntityDatabaseAdapter extends EntityDatabaseAdapter<TestFields, 'custo
_tableIdField: string,
_id: any,
_object: object,
): Promise<object[]> {
): Promise<{ updatedRowCount: number }> {
return this.updateResults;
}

Expand Down Expand Up @@ -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,
);
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class StubDatabaseAdapter<
tableIdField: string,
id: any,
object: object,
): Promise<object[]> {
): 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})`);
Expand All @@ -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(
Expand Down
Loading