@balena/odata-to-abstract-sql
Advanced tools
Comparing version 7.1.0 to 7.1.1-build-fix-order-by-null-values-493afb18521119726478b27c99090ce6c7aa6acf-1
import memoize from 'memoizee'; | ||
import type { AbstractSqlQuery, AbstractSqlModel, AbstractSqlTable, DurationNode, SelectNode, FromNode, WhereNode, OrderByNode, LimitNode, OffsetNode, NumberTypeNodes, FieldsNode, ValuesNode, ReferencedFieldNode, AliasNode, BooleanTypeNodes, SelectQueryNode, BindNode, TableNode, Definition as ModernDefinition, ResourceNode, UnionQueryNode, FromTypeNodes, FieldNode, CountNode, AddNode, InsertQueryNode, DeleteQueryNode, UpdateQueryNode, DurationTypeNodes, SubtractNode, MultiplyNode, DivideNode, NullNode, TextTypeNodes, AnyTypeNodes, StrictDateTypeNodes } from '@balena/abstract-sql-compiler'; | ||
import type { ODataBinds, ODataQuery, SupportedMethod, ExpandPropertyPath, ResourceOptions, OrderByOption, OrderByPropertyPath, FilterOption, BindReference } from '@balena/odata-parser'; | ||
import type { AbstractSqlQuery, AbstractSqlModel, AbstractSqlTable, DurationNode, SelectNode, FromNode, WhereNode, OrderByNode, LimitNode, OffsetNode, NumberTypeNodes, FieldsNode, ValuesNode, ReferencedFieldNode, AliasNode, BooleanTypeNodes, SelectQueryNode, BindNode, TableNode, Definition as ModernDefinition, ResourceNode, UnionQueryNode, FromTypeNodes, FieldNode, CountNode, AddNode, InsertQueryNode, DeleteQueryNode, UpdateQueryNode, DurationTypeNodes, SubtractNode, MultiplyNode, DivideNode, NullNode, TextTypeNodes, AnyTypeNodes, StrictDateTypeNodes, JoinTypeNodes } from '@balena/abstract-sql-compiler'; | ||
import type { ODataBinds, ODataQuery, SupportedMethod, ExpandPropertyPath, ResourceOptions, OrderByOption, OrderByPropertyPath, FilterOption, BindReference, GenericPropertyPath, PropertyPath } from '@balena/odata-parser'; | ||
export type { ODataBinds, ODataQuery, SupportedMethod }; | ||
type InternalSupportedMethod = Exclude<SupportedMethod, 'MERGE'> | 'PUT-INSERT'; | ||
type JoinType = 'Join' | 'LeftJoin' | 'RightJoin'; | ||
type RequiredAbstractSqlModelSubset = Pick<AbstractSqlModel, 'synonyms' | 'relationships' | 'tables'>; | ||
@@ -38,2 +39,3 @@ type Dictionary<T> = Record<string, T>; | ||
where: Array<WhereNode[1]>; | ||
joins: JoinTypeNodes[]; | ||
extras: Array<FieldsNode | ValuesNode | OrderByNode | LimitNode | OffsetNode>; | ||
@@ -45,2 +47,6 @@ merge(otherQuery: Query): void; | ||
}, bypassDefinition?: boolean, isModifyOperation?: boolean): void; | ||
joinResource(odataToAbstractSql: OData2AbstractSQL, resource: AliasedResource, type: JoinType, condition: BooleanTypeNodes, args?: { | ||
extraBindVars: ODataBinds; | ||
bindVarsLength: number; | ||
}, bypassDefinition?: boolean): void; | ||
addNestedFieldSelect(fieldName: string, fieldNameAlias: string): void; | ||
@@ -129,2 +135,4 @@ compile(queryType: 'SelectQuery'): SelectQueryNode; | ||
AddNavigation(query: Query, resource: Resource, extraResource: string): AliasedResource; | ||
AddJoins(query: Query, parentResource: Resource, match: GenericPropertyPath<PropertyPath> | Array<GenericPropertyPath<PropertyPath>>, joinType: JoinType): void; | ||
AddJoinNavigation(query: Query, resource: Resource, extraResource: string, joinType: JoinType): AliasedResource; | ||
reset(): void; | ||
@@ -131,0 +139,0 @@ putReset(): void; |
@@ -94,2 +94,3 @@ "use strict"; | ||
where = []; | ||
joins = []; | ||
extras = []; | ||
@@ -100,2 +101,3 @@ merge(otherQuery) { | ||
this.where = this.where.concat(otherQuery.where); | ||
this.joins = this.joins.concat(otherQuery.joins); | ||
this.extras = this.extras.concat(otherQuery.extras); | ||
@@ -107,2 +109,7 @@ } | ||
} | ||
joinResource(odataToAbstractSql, resource, type, condition, args = odataToAbstractSql, bypassDefinition) { | ||
const tableRef = odataToAbstractSql.getTableReference(resource, args.extraBindVars, args.bindVarsLength, bypassDefinition, resource.tableAlias, undefined); | ||
const joinNode = [type, tableRef, ['On', condition]]; | ||
this.joins.push(joinNode); | ||
} | ||
addNestedFieldSelect(fieldName, fieldNameAlias) { | ||
@@ -123,2 +130,5 @@ if (this.from.length !== 1) { | ||
}); | ||
this.joins.forEach((joinNode) => { | ||
compiled.push(joinNode); | ||
}); | ||
if (where.length > 0) { | ||
@@ -568,3 +578,3 @@ if (where.length > 1) { | ||
OrderBy(orderby, query, resource) { | ||
this.AddExtraFroms(query, resource, orderby.properties); | ||
this.AddJoins(query, resource, orderby.properties, 'LeftJoin'); | ||
query.extras.push([ | ||
@@ -1232,2 +1242,36 @@ 'OrderBy', | ||
} | ||
AddJoins(query, parentResource, match, joinType) { | ||
if (Array.isArray(match)) { | ||
match.forEach((v) => { | ||
this.AddJoins(query, parentResource, v, joinType); | ||
}); | ||
} | ||
else { | ||
let nextProp = match; | ||
let prop; | ||
while ((prop = nextProp) && | ||
prop.name && | ||
prop.property?.name) { | ||
nextProp = prop.property; | ||
const resourceAlias = this.resourceAliases[prop.name]; | ||
if (resourceAlias) { | ||
parentResource = resourceAlias; | ||
} | ||
else { | ||
parentResource = this.AddJoinNavigation(query, parentResource, prop.name, joinType); | ||
} | ||
} | ||
} | ||
} | ||
AddJoinNavigation(query, resource, extraResource, joinType) { | ||
const navigation = this.NavigateResources(resource, extraResource); | ||
if (!query.joins.some((join) => { | ||
const from = join[1]; | ||
return (((0, abstract_sql_compiler_1.isTableNode)(from) && from[1] === navigation.resource.tableAlias) || | ||
((0, abstract_sql_compiler_1.isAliasNode)(from) && from[2] === navigation.resource.tableAlias)); | ||
})) { | ||
query.joinResource(this, navigation.resource, joinType, navigation.where); | ||
} | ||
return navigation.resource; | ||
} | ||
reset() { | ||
@@ -1234,0 +1278,0 @@ this.putReset(); |
{ | ||
"name": "@balena/odata-to-abstract-sql", | ||
"version": "7.1.0", | ||
"version": "7.1.1-build-fix-order-by-null-values-493afb18521119726478b27c99090ce6c7aa6acf-1", | ||
"description": "A consumer of the OData parser, written in OMeta", | ||
@@ -57,4 +57,4 @@ "type": "commonjs", | ||
"versionist": { | ||
"publishedAt": "2025-02-11T14:42:53.172Z" | ||
"publishedAt": "2025-02-17T12:09:57.412Z" | ||
} | ||
} |
@@ -76,2 +76,3 @@ import _ from 'lodash'; | ||
EqualsAnyNode, | ||
JoinTypeNodes, | ||
} from '@balena/abstract-sql-compiler'; | ||
@@ -88,2 +89,4 @@ import type { | ||
BindReference, | ||
GenericPropertyPath, | ||
PropertyPath, | ||
} from '@balena/odata-parser'; | ||
@@ -94,2 +97,4 @@ export type { ODataBinds, ODataQuery, SupportedMethod }; | ||
type JoinType = 'Join' | 'LeftJoin' | 'RightJoin'; | ||
type RequiredAbstractSqlModelSubset = Pick< | ||
@@ -244,2 +249,3 @@ AbstractSqlModel, | ||
public where: Array<WhereNode[1]> = []; | ||
public joins: JoinTypeNodes[] = []; | ||
public extras: Array< | ||
@@ -253,2 +259,3 @@ FieldsNode | ValuesNode | OrderByNode | LimitNode | OffsetNode | ||
this.where = this.where.concat(otherQuery.where); | ||
this.joins = this.joins.concat(otherQuery.joins); | ||
this.extras = this.extras.concat(otherQuery.extras); | ||
@@ -276,2 +283,24 @@ } | ||
} | ||
joinResource( | ||
odataToAbstractSql: OData2AbstractSQL, | ||
resource: AliasedResource, | ||
type: JoinType, | ||
condition: BooleanTypeNodes, | ||
args: { | ||
extraBindVars: ODataBinds; | ||
bindVarsLength: number; | ||
} = odataToAbstractSql, | ||
bypassDefinition?: boolean, | ||
): void { | ||
const tableRef = odataToAbstractSql.getTableReference( | ||
resource, | ||
args.extraBindVars, | ||
args.bindVarsLength, | ||
bypassDefinition, | ||
resource.tableAlias, | ||
undefined, | ||
); | ||
const joinNode: JoinTypeNodes = [type, tableRef, ['On', condition]]; | ||
this.joins.push(joinNode); | ||
} | ||
addNestedFieldSelect(fieldName: string, fieldNameAlias: string): void { | ||
@@ -300,2 +329,5 @@ if (this.from.length !== 1) { | ||
}); | ||
this.joins.forEach((joinNode) => { | ||
compiled.push(joinNode); | ||
}); | ||
if (where.length > 0) { | ||
@@ -901,3 +933,3 @@ if (where.length > 1) { | ||
OrderBy(orderby: OrderByOption, query: Query, resource: Resource) { | ||
this.AddExtraFroms(query, resource, orderby.properties); | ||
this.AddJoins(query, resource, orderby.properties, 'LeftJoin'); | ||
query.extras.push([ | ||
@@ -1737,2 +1769,58 @@ 'OrderBy', | ||
} | ||
AddJoins( | ||
query: Query, | ||
parentResource: Resource, | ||
match: | ||
| GenericPropertyPath<PropertyPath> | ||
| Array<GenericPropertyPath<PropertyPath>>, | ||
joinType: JoinType, | ||
) { | ||
if (Array.isArray(match)) { | ||
match.forEach((v) => { | ||
this.AddJoins(query, parentResource, v, joinType); | ||
}); | ||
} else { | ||
let nextProp = match; | ||
let prop; | ||
while ( | ||
// tslint:disable-next-line:no-conditional-assignment | ||
(prop = nextProp) && | ||
prop.name && | ||
prop.property?.name | ||
) { | ||
nextProp = prop.property; | ||
const resourceAlias = this.resourceAliases[prop.name]; | ||
if (resourceAlias) { | ||
parentResource = resourceAlias; | ||
} else { | ||
parentResource = this.AddJoinNavigation( | ||
query, | ||
parentResource, | ||
prop.name, | ||
joinType, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
AddJoinNavigation( | ||
query: Query, | ||
resource: Resource, | ||
extraResource: string, | ||
joinType: JoinType, | ||
): AliasedResource { | ||
const navigation = this.NavigateResources(resource, extraResource); | ||
if ( | ||
!query.joins.some((join) => { | ||
const from = join[1]; | ||
return ( | ||
(isTableNode(from) && from[1] === navigation.resource.tableAlias) || | ||
(isAliasNode(from) && from[2] === navigation.resource.tableAlias) | ||
); | ||
}) | ||
) { | ||
query.joinResource(this, navigation.resource, joinType, navigation.where); | ||
} | ||
return navigation.resource; | ||
} | ||
@@ -1739,0 +1827,0 @@ reset() { |
@@ -16,2 +16,6 @@ // tslint:disable-next-line:no-namespace | ||
) => Assertion; | ||
leftJoin: ( | ||
join: [string | string[], any[]], | ||
...joins: Array<[string | string[], any[]]> | ||
) => Assertion; | ||
where: (clause: any[]) => Assertion; | ||
@@ -18,0 +22,0 @@ orderby: (...clause: any[]) => Assertion; |
@@ -43,2 +43,13 @@ import _ from 'lodash'; | ||
}; | ||
const binaryClause = (bodyType) => | ||
function (...bodyClauses) { | ||
const obj = utils.flag(this, 'object'); | ||
for (let i = 0; i < bodyClauses.length; i++) { | ||
expect(obj).to.contain.something.that.deep.equals( | ||
[bodyType, bodyClauses[i][0], bodyClauses[i][1]], | ||
bodyType + ' - ' + i, | ||
); | ||
} | ||
return this; | ||
}; | ||
@@ -73,2 +84,11 @@ const select = (function () { | ||
}); | ||
const leftJoinClause = binaryClause('LeftJoin'); | ||
utils.addMethod(assertionPrototype, 'leftJoin', function (...bodyClauses) { | ||
bodyClauses = bodyClauses.map(function ([v, condition]) { | ||
const resource = | ||
typeof v === 'string' ? ['Table', v] : ['Alias', ['Table', v[0]], v[1]]; | ||
return [resource, ['On', condition]]; | ||
}); | ||
return leftJoinClause.apply(this, bodyClauses); | ||
}); | ||
utils.addMethod(assertionPrototype, 'where', bodyClause('Where')); | ||
@@ -75,0 +95,0 @@ utils.addMethod(assertionPrototype, 'orderby', function (...bodyClauses) { |
@@ -53,36 +53,42 @@ import { expect } from 'chai'; | ||
test('/pilot?$orderby=licence/id asc', (result) => | ||
it('should order by licence/id asc', () => | ||
it('should order by licence/id asc', () => { | ||
expect(result) | ||
.to.be.a.query.that.selects(pilotFields) | ||
.from('pilot', ['licence', 'pilot.licence']) | ||
.where([ | ||
'Equals', | ||
['ReferencedField', 'pilot', 'licence'], | ||
['ReferencedField', 'pilot.licence', 'id'], | ||
.from('pilot') | ||
.leftJoin([ | ||
['licence', 'pilot.licence'], | ||
[ | ||
'Equals', | ||
['ReferencedField', 'pilot', 'licence'], | ||
['ReferencedField', 'pilot.licence', 'id'], | ||
], | ||
]) | ||
.orderby(['ASC', operandToAbstractSQL('licence/id')]))); | ||
.orderby(['ASC', operandToAbstractSQL('licence/id')]); | ||
})); | ||
test('/pilot?$orderby=can_fly__plane/plane/id asc', (result) => | ||
it('should order by can_fly__plane/plane/id asc', () => | ||
it('should order by can_fly__plane/plane/id asc', () => { | ||
expect(result) | ||
.to.be.a.query.that.selects(pilotFields) | ||
.from( | ||
'pilot', | ||
['pilot-can fly-plane', 'pilot.pilot-can fly-plane'], | ||
['plane', 'pilot.pilot-can fly-plane.plane'], | ||
) | ||
.where([ | ||
'And', | ||
.from('pilot') | ||
.leftJoin( | ||
[ | ||
'Equals', | ||
['ReferencedField', 'pilot', 'id'], | ||
['ReferencedField', 'pilot.pilot-can fly-plane', 'pilot'], | ||
['pilot-can fly-plane', 'pilot.pilot-can fly-plane'], | ||
[ | ||
'Equals', | ||
['ReferencedField', 'pilot', 'id'], | ||
['ReferencedField', 'pilot.pilot-can fly-plane', 'pilot'], | ||
], | ||
], | ||
[ | ||
'Equals', | ||
['ReferencedField', 'pilot.pilot-can fly-plane', 'can fly-plane'], | ||
['ReferencedField', 'pilot.pilot-can fly-plane.plane', 'id'], | ||
['plane', 'pilot.pilot-can fly-plane.plane'], | ||
[ | ||
'Equals', | ||
['ReferencedField', 'pilot.pilot-can fly-plane', 'can fly-plane'], | ||
['ReferencedField', 'pilot.pilot-can fly-plane.plane', 'id'], | ||
], | ||
], | ||
]) | ||
.orderby(['ASC', operandToAbstractSQL('can_fly__plane/plane/id')]))); | ||
) | ||
.orderby(['ASC', operandToAbstractSQL('can_fly__plane/plane/id')]); | ||
})); | ||
@@ -89,0 +95,0 @@ test.skip('/pilot?$orderby=favourite_colour/red', () => |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1034344
7312
2