Skip to content

Commit

Permalink
Merge pull request #154 from balena-io-modules/patch-delete-use-db-id…
Browse files Browse the repository at this point in the history
…-wo-translating

Support PUT/PATCH/DELETE requests on resources with a translated ID field
  • Loading branch information
thgreasi authored Aug 26, 2024
2 parents 7a8d667 + 65b0676 commit 5e9bacd
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 12 deletions.
61 changes: 60 additions & 1 deletion src/odata-to-abstract-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import stringHash = require('string-hash');
import {
isAliasNode,
isFromNode,
isSelectNode,
isSelectQueryNode,
isTableNode,
} from '@balena/abstract-sql-compiler';
import type {
Expand Down Expand Up @@ -69,6 +71,7 @@ import type {
IsNotDistinctFromNode,
IsDistinctFromNode,
UnknownTypeNodes,
FromTypeNode,
} from '@balena/abstract-sql-compiler';
import type {
ODataBinds,
Expand Down Expand Up @@ -176,6 +179,54 @@ const containsQueryOption = (opts?: object): boolean => {
return false;
};

const addNestedFieldSelect = (
selectNode: SelectNode[1],
fromNode: FromNode[1],
fieldName: string,
fieldNameAlias: string,
) => {
let aliasName: string | undefined;
let tableOrSubqueryNode: FromTypeNode[keyof FromTypeNode];
if (isAliasNode(fromNode)) {
tableOrSubqueryNode = fromNode[1];
aliasName = fromNode[2];
} else {
tableOrSubqueryNode = fromNode;
}
if (isTableNode(tableOrSubqueryNode)) {
selectNode.push([
'Alias',
['ReferencedField', aliasName ?? tableOrSubqueryNode[1], fieldName],
fieldNameAlias,
]);
return;
}
if (!isSelectQueryNode(tableOrSubqueryNode)) {
throw new Error(
`Adding a nested field select to a subquery containing a ${tableOrSubqueryNode[0]} is not supported`,
);
}
if (aliasName == null) {
// This should never happen but we are checking it to make TS happy.
throw new Error('Found unaliased SelectQueryNode');
}
const nestedSelectNode = tableOrSubqueryNode.find(isSelectNode);
if (nestedSelectNode == null) {
throw new Error(`Cannot find SelectNode in subquery`);
}
const nestedFromNode = tableOrSubqueryNode.find(isFromNode);
if (nestedFromNode == null) {
throw new Error(`Cannot find FromNode in subquery`);
}
addNestedFieldSelect(
nestedSelectNode[1],
nestedFromNode[1],
fieldName,
fieldNameAlias,
);
selectNode.push(['ReferencedField', aliasName, fieldNameAlias]);
};

class Query {
public select: Array<
| ReferencedFieldNode
Expand Down Expand Up @@ -215,6 +266,14 @@ class Query {
);
this.from.push(tableRef);
}
addNestedFieldSelect(fieldName: string, fieldNameAlias: string): void {
if (this.from.length !== 1) {
throw new Error(
`Adding nested field SELECTs is only supported for queries with exactly 1 FROM clause. Found ${this.from.length}`,
);
}
addNestedFieldSelect(this.select, this.from[0], fieldName, fieldNameAlias);
}
compile(queryType: 'SelectQuery'): SelectQueryNode;
compile(queryType: 'InsertQuery'): InsertQueryNode;
compile(queryType: 'UpdateQuery'): UpdateQueryNode;
Expand Down Expand Up @@ -717,8 +776,8 @@ export class OData2AbstractSQL {
) {
// For update/delete statements we need to use a style query
const subQuery = new Query();
subQuery.select.push(referencedIdField);
subQuery.fromResource(this, resource);
subQuery.addNestedFieldSelect(resource.idField, '$modifyid');
if (hasQueryOpts) {
this.AddQueryOptions(resource, path, subQuery);
}
Expand Down
86 changes: 77 additions & 9 deletions test/filterby.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ run(function () {
['ReferencedField', 'pilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'pilot', 'id']]],
['Select', [['Alias', ['ReferencedField', 'pilot', 'id'], '$modifyid']]],
['From', ['Table', 'pilot']],
[
'From',
Expand Down Expand Up @@ -542,7 +542,7 @@ run(function () {
it('that inserts', () => {
insertTest(result[1]);
});
return it('and updates', () =>
it('and updates', () => {
expect(result[2])
.to.be.a.query.that.updates.fields(
'created at',
Expand Down Expand Up @@ -573,11 +573,12 @@ run(function () {
'Default',
)
.from('pilot')
.where(updateWhere));
.where(updateWhere);
});
}),
);

return test('/pilot?$filter=' + odata, 'DELETE', (result) =>
test('/pilot?$filter=' + odata, 'DELETE', (result) =>
it('should delete from pilot where "' + odata + '"', () => {
expect(result)
.to.be.a.query.that.deletes.from('pilot')
Expand All @@ -586,7 +587,10 @@ run(function () {
['ReferencedField', 'pilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'pilot', 'id']]],
[
'Select',
[['Alias', ['ReferencedField', 'pilot', 'id'], '$modifyid']],
],
['From', ['Table', 'pilot']],
[
'From',
Expand Down Expand Up @@ -721,7 +725,10 @@ run([['Number', 1]], function () {
['ReferencedField', 'pilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'pilot', 'id']]],
[
'Select',
[['Alias', ['ReferencedField', 'pilot', 'id'], '$modifyid']],
],
['From', ['Table', 'pilot']],
['Where', abstractsql],
],
Expand All @@ -744,14 +751,14 @@ run([['Number', 1]], function () {
}),
);

return test('/pilot(1)?$filter=' + odata, 'PUT', { name }, (result) =>
test('/pilot(1)?$filter=' + odata, 'PUT', { name }, (result) =>
describe('should upsert the pilot with id 1', function () {
it('should be an upsert', () =>
expect(result).to.be.a.query.that.upserts);
it('that inserts', () => {
insertTest(result[1]);
});
return it('and updates', () => {
it('and updates', () => {
expect(result[2])
.to.be.a.query.that.updates.fields(
'created at',
Expand Down Expand Up @@ -1511,7 +1518,7 @@ test(
['ReferencedField', 'copilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'copilot', 'id']]],
['Select', [['ReferencedField', 'copilot', '$modifyid']]],
[
'From',
[
Expand All @@ -1524,6 +1531,11 @@ test(
['Field', '*'],
['Alias', ['Boolean', false], 'is blocked'],
['Alias', ['Text', 'Junior'], 'rank'],
[
'Alias',
['ReferencedField', 'copilot', 'id'],
'$modifyid',
],
],
],
['From', ['Table', 'copilot']],
Expand All @@ -1547,3 +1559,59 @@ test(
]);
}),
);

test(
`/copilot?$select=id,rank&$filter=rank eq 'major'`,
'DELETE',
{ assists__pilot: 1 },
(result) =>
it(`should DELETE copilot based on filtered computed field rank`, () => {
expect(result).to.be.a.query.to.deep.equal([
'DeleteQuery',
['From', ['Table', 'copilot']],
[
'Where',
[
'In',
['ReferencedField', 'copilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'copilot', '$modifyid']]],
[
'From',
[
'Alias',
[
'SelectQuery',
[
'Select',
[
['Field', '*'],
['Alias', ['Boolean', false], 'is blocked'],
['Alias', ['Text', 'Junior'], 'rank'],
[
'Alias',
['ReferencedField', 'copilot', 'id'],
'$modifyid',
],
],
],
['From', ['Table', 'copilot']],
],
'copilot',
],
],
[
'Where',
[
'IsNotDistinctFrom',
['ReferencedField', 'copilot', 'rank'],
['Bind', 0],
],
],
],
],
],
]);
}),
);
10 changes: 8 additions & 2 deletions test/paging.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ test('/pilot?$top=5&$skip=100', 'PATCH', { name }, (result) =>
['ReferencedField', 'pilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'pilot', 'id']]],
[
'Select',
[['Alias', ['ReferencedField', 'pilot', 'id'], '$modifyid']],
],
['From', ['Table', 'pilot']],
['Limit', ['Number', 5]],
['Offset', ['Number', 100]],
Expand All @@ -52,7 +55,10 @@ test('/pilot?$top=5&$skip=100', 'DELETE', (result) =>
['ReferencedField', 'pilot', 'id'],
[
'SelectQuery',
['Select', [['ReferencedField', 'pilot', 'id']]],
[
'Select',
[['Alias', ['ReferencedField', 'pilot', 'id'], '$modifyid']],
],
['From', ['Table', 'pilot']],
['Limit', ['Number', 5]],
['Offset', ['Number', 100]],
Expand Down

0 comments on commit 5e9bacd

Please sign in to comment.