Skip to content

Commit 59b7537

Browse files
committed
Date functions should be allowed in ORDER BY clause #139
Added date functions to order by clause Modified names of functions / types (internal) Removed improper import of `isString` from node utils #139 (version 2.5.6)
1 parent d2d867f commit 59b7537

File tree

5 files changed

+251
-25
lines changed

5 files changed

+251
-25
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 2.5.6
4+
5+
March 6, 2021
6+
7+
1. Date functions were not properly parsed when used in order by clauses. (#139)
8+
2. Modified names of functions / types (internal)
9+
3. Removed improper import of `isString` from node utils
10+
311
## 2.5.5
412

513
Aug 23, 2020

src/models.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,16 @@ export interface OrderByExpressionContext extends WithIdentifier {
135135
nulls?: IToken[];
136136
}
137137

138-
export interface OrderByFunctionExpressionContext extends WithIdentifier {
138+
export interface OrderByGroupingFunctionExpressionContext extends WithIdentifier {
139139
fn: IToken[];
140140
order?: IToken[];
141141
nulls?: IToken[];
142142
}
143143

144-
export interface orderByAggregateOrLocationExpressionContext {
145-
locationFunction?: CstNode[];
144+
export interface OrderBySpecialFunctionExpressionContext {
146145
aggregateFunction?: CstNode[];
146+
dateFunction?: CstNode[];
147+
locationFunction?: CstNode[];
147148
order?: IToken[];
148149
nulls?: IToken[];
149150
}

src/parser/parser.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ export class SoqlParser extends CstParser {
327327
SEP: lexer.Comma,
328328
DEF: () => {
329329
this.OR([
330-
{ ALT: () => this.SUBRULE(this.orderByFunctionExpression, { LABEL: 'orderByExpressionOrFn' }) },
331-
{ ALT: () => this.SUBRULE(this.orderByAggregateOrLocationExpression, { LABEL: 'orderByExpressionOrFn' }) },
330+
{ ALT: () => this.SUBRULE(this.orderByGroupingFunctionExpression, { LABEL: 'orderByExpressionOrFn' }) },
331+
{ ALT: () => this.SUBRULE(this.orderBySpecialFunctionExpression, { LABEL: 'orderByExpressionOrFn' }) },
332332
{ ALT: () => this.SUBRULE(this.orderByExpression, { LABEL: 'orderByExpressionOrFn' }) },
333333
]);
334334
},
@@ -346,13 +346,17 @@ export class SoqlParser extends CstParser {
346346
});
347347
});
348348

349-
private orderByFunctionExpression = this.RULE('orderByFunctionExpression', () => {
349+
private orderByGroupingFunctionExpression = this.RULE('orderByGroupingFunctionExpression', () => {
350350
this.CONSUME(lexer.Grouping, { LABEL: 'fn' });
351351
this.SUBRULE(this.functionExpression);
352352
});
353353

354-
private orderByAggregateOrLocationExpression = this.RULE('orderByAggregateOrLocationExpression', () => {
355-
this.OR([{ ALT: () => this.SUBRULE(this.locationFunction) }, { ALT: () => this.SUBRULE(this.aggregateFunction) }]);
354+
private orderBySpecialFunctionExpression = this.RULE('orderBySpecialFunctionExpression', () => {
355+
this.OR([
356+
{ ALT: () => this.SUBRULE(this.aggregateFunction) },
357+
{ ALT: () => this.SUBRULE(this.dateFunction) },
358+
{ ALT: () => this.SUBRULE(this.locationFunction) },
359+
]);
356360
this.OPTION(() => {
357361
this.OR1([{ ALT: () => this.CONSUME(lexer.Asc, { LABEL: 'order' }) }, { ALT: () => this.CONSUME(lexer.Desc, { LABEL: 'order' }) }]);
358362
});

src/parser/visitor.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { IToken } from 'chevrotain';
12
import {
23
Condition,
3-
ValueQuery,
44
DateLiteral,
55
DateNLiteral,
6+
Field,
67
FieldFunctionExpression,
78
FieldRelationship,
89
FieldSubquery,
@@ -19,9 +20,9 @@ import {
1920
OrderByCriterion,
2021
Query,
2122
Subquery,
23+
ValueQuery,
2224
WhereClause,
2325
WithDataCategoryCondition,
24-
Field,
2526
} from '../api/api-models';
2627
import {
2728
ApexBindVariableExpressionContext,
@@ -36,15 +37,20 @@ import {
3637
FieldFunctionContext,
3738
FromClauseContext,
3839
FunctionExpressionContext,
40+
GeoLocationFunctionContext,
3941
GroupByClauseContext,
42+
GroupByFieldListContext,
4043
HavingClauseContext,
4144
LiteralTypeWithSubquery,
45+
LocationFunctionContext,
4246
OperatorContext,
4347
OrderByClauseContext,
4448
OrderByExpressionContext,
45-
OrderByFunctionExpressionContext,
49+
OrderByGroupingFunctionExpressionContext,
50+
OrderBySpecialFunctionExpressionContext,
4651
SelectClauseContext,
4752
SelectClauseFunctionIdentifierContext,
53+
SelectClauseIdentifierContext,
4854
SelectClauseSubqueryIdentifierContext,
4955
SelectClauseTypeOfContext,
5056
SelectClauseTypeOfElseContext,
@@ -56,16 +62,9 @@ import {
5662
WhereClauseSubqueryContext,
5763
WithClauseContext,
5864
WithDateCategoryContext,
59-
LocationFunctionContext,
60-
GeoLocationFunctionContext,
61-
orderByAggregateOrLocationExpressionContext,
62-
GroupByFieldListContext,
63-
SelectClauseIdentifierContext,
6465
} from '../models';
65-
import { isSubqueryFromFlag, isToken } from '../utils';
66+
import { isString, isSubqueryFromFlag, isToken } from '../utils';
6667
import { parse, ParseQueryConfig, SoqlParser } from './parser';
67-
import { isString, isNull } from 'util';
68-
import { IToken } from 'chevrotain';
6968

7069
const parser = new SoqlParser();
7170

@@ -460,7 +459,7 @@ class SOQLVisitor extends BaseSoqlVisitor {
460459
return orderByClause;
461460
}
462461

463-
orderByFunctionExpression(ctx: OrderByFunctionExpressionContext): OrderByClause {
462+
orderByGroupingFunctionExpression(ctx: OrderByGroupingFunctionExpressionContext): OrderByClause {
464463
const orderByClause: OrderByClause = {
465464
fn: this.$_getFieldFunction(ctx, false, false),
466465
};
@@ -473,12 +472,14 @@ class SOQLVisitor extends BaseSoqlVisitor {
473472
return orderByClause;
474473
}
475474

476-
orderByAggregateOrLocationExpression(ctx: orderByAggregateOrLocationExpressionContext): OrderByClause {
475+
orderBySpecialFunctionExpression(ctx: OrderBySpecialFunctionExpressionContext): OrderByClause {
477476
const orderByClause: OrderByClause = {};
478-
if (ctx.locationFunction) {
479-
orderByClause.fn = this.visit(ctx.locationFunction, { includeType: false });
480-
} else {
477+
if (ctx.aggregateFunction) {
481478
orderByClause.fn = this.visit(ctx.aggregateFunction, { includeType: false });
479+
} else if (ctx.dateFunction) {
480+
orderByClause.fn = this.visit(ctx.dateFunction, { includeType: false });
481+
} else if (ctx.locationFunction) {
482+
orderByClause.fn = this.visit(ctx.locationFunction, { includeType: false });
482483
}
483484
if (ctx.order && ctx.order[0]) {
484485
orderByClause.order = ctx.order[0].tokenType.name as OrderByCriterion;
@@ -708,7 +709,7 @@ class SOQLVisitor extends BaseSoqlVisitor {
708709
const arrayValues: ArrayExpressionWithType[] = this.visit(ctx.arrayExpression);
709710
value = arrayValues.map((item: any) => item.value);
710711
const dateLiteralTemp = arrayValues.map((item: any) => item.variable || null);
711-
const hasDateLiterals = dateLiteralTemp.some(item => !isNull(item));
712+
const hasDateLiterals = dateLiteralTemp.some(item => item !== null);
712713
if (new Set(arrayValues.map((item: any) => item.type)).size === 1) {
713714
literalType = this.$_getLiteralTypeFromTokenType(arrayValues[0].type);
714715
} else {

test/test-cases.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,218 @@ export const testCases: TestCase[] = [
19171917
},
19181918
},
19191919
},
1920+
// {
1921+
// testCase: 103,
1922+
// soql: 'SELECT AnnualRevenue FROM Account WHERE NOT (AnnualRevenue > 0 AND AnnualRevenue < 200000)',
1923+
// output: {
1924+
// fields: [{ type: 'Field', field: 'AnnualRevenue' }],
1925+
// sObject: 'Account',
1926+
// where: {
1927+
// left: null,
1928+
// operator: 'NOT',
1929+
// right: {
1930+
// left: {
1931+
// field: 'AnnualRevenue',
1932+
// openParen: 1,
1933+
// operator: '>',
1934+
// value: '0',
1935+
// literalType: 'INTEGER',
1936+
// },
1937+
// operator: 'AND',
1938+
// right: {
1939+
// left: {
1940+
// field: 'AnnualRevenue',
1941+
// closeParen: 1,
1942+
// operator: '<',
1943+
// value: '200000',
1944+
// literalType: 'INTEGER',
1945+
// },
1946+
// },
1947+
// },
1948+
// },
1949+
// },
1950+
// },
1951+
// {
1952+
// testCase: 104,
1953+
// soql: 'SELECT AnnualRevenue FROM Account WHERE ((NOT AnnualRevenue > 0) AND AnnualRevenue < 200000)',
1954+
// output: {
1955+
// fields: [{ type: 'Field', field: 'AnnualRevenue' }],
1956+
// sObject: 'Account',
1957+
// where: {
1958+
// left: {
1959+
// openParen: 2,
1960+
// },
1961+
// operator: 'NOT',
1962+
// right: {
1963+
// left: {
1964+
// field: 'AnnualRevenue',
1965+
// closeParen: 1,
1966+
// operator: '>',
1967+
// value: '0',
1968+
// literalType: 'INTEGER',
1969+
// },
1970+
// operator: 'AND',
1971+
// right: {
1972+
// left: {
1973+
// field: 'AnnualRevenue',
1974+
// closeParen: 1,
1975+
// operator: '<',
1976+
// value: '200000',
1977+
// literalType: 'INTEGER',
1978+
// },
1979+
// },
1980+
// },
1981+
// },
1982+
// },
1983+
// },
1984+
// {
1985+
// testCase: 105,
1986+
// soql: `SELECT Id FROM Account WHERE NOT Id = '2'`,
1987+
// output: {
1988+
// fields: [
1989+
// {
1990+
// type: 'Field',
1991+
// field: 'Id',
1992+
// },
1993+
// ],
1994+
// sObject: 'Account',
1995+
// where: {
1996+
// left: null,
1997+
// operator: 'NOT',
1998+
// right: {
1999+
// left: {
2000+
// field: 'Id',
2001+
// operator: '=',
2002+
// value: "'2'",
2003+
// literalType: 'STRING',
2004+
// },
2005+
// },
2006+
// },
2007+
// },
2008+
// },
2009+
{
2010+
testCase: 106,
2011+
soql: `SELECT WEEK_IN_YEAR(CloseDate), SUM(amount) FROM Opportunity GROUP BY WEEK_IN_YEAR(CloseDate) ORDER BY WEEK_IN_YEAR(CloseDate)`,
2012+
output: {
2013+
fields: [
2014+
{
2015+
type: 'FieldFunctionExpression',
2016+
functionName: 'WEEK_IN_YEAR',
2017+
parameters: ['CloseDate'],
2018+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2019+
},
2020+
{
2021+
type: 'FieldFunctionExpression',
2022+
functionName: 'SUM',
2023+
parameters: ['amount'],
2024+
isAggregateFn: true,
2025+
rawValue: 'SUM(amount)',
2026+
},
2027+
],
2028+
sObject: 'Opportunity',
2029+
groupBy: {
2030+
fn: {
2031+
functionName: 'WEEK_IN_YEAR',
2032+
parameters: ['CloseDate'],
2033+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2034+
},
2035+
},
2036+
orderBy: {
2037+
fn: {
2038+
functionName: 'WEEK_IN_YEAR',
2039+
parameters: ['CloseDate'],
2040+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2041+
},
2042+
},
2043+
},
2044+
},
2045+
{
2046+
testCase: 107,
2047+
soql: `SELECT WEEK_IN_YEAR(CloseDate), SUM(amount) FROM Opportunity GROUP BY WEEK_IN_YEAR(CloseDate) ORDER BY WEEK_IN_YEAR(CloseDate) DESC NULLS FIRST`,
2048+
output: {
2049+
fields: [
2050+
{
2051+
type: 'FieldFunctionExpression',
2052+
functionName: 'WEEK_IN_YEAR',
2053+
parameters: ['CloseDate'],
2054+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2055+
},
2056+
{
2057+
type: 'FieldFunctionExpression',
2058+
functionName: 'SUM',
2059+
parameters: ['amount'],
2060+
isAggregateFn: true,
2061+
rawValue: 'SUM(amount)',
2062+
},
2063+
],
2064+
sObject: 'Opportunity',
2065+
groupBy: {
2066+
fn: {
2067+
functionName: 'WEEK_IN_YEAR',
2068+
parameters: ['CloseDate'],
2069+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2070+
},
2071+
},
2072+
orderBy: {
2073+
fn: {
2074+
functionName: 'WEEK_IN_YEAR',
2075+
parameters: ['CloseDate'],
2076+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2077+
},
2078+
order: 'DESC',
2079+
nulls: 'FIRST',
2080+
},
2081+
},
2082+
},
2083+
{
2084+
testCase: 108,
2085+
soql: `SELECT WEEK_IN_YEAR(CloseDate), SUM(amount) FROM Opportunity GROUP BY WEEK_IN_YEAR(CloseDate) ORDER BY WEEK_IN_YEAR(CloseDate) DESC NULLS LAST, SUM(amount) ASC NULLS LAST`,
2086+
output: {
2087+
fields: [
2088+
{
2089+
type: 'FieldFunctionExpression',
2090+
functionName: 'WEEK_IN_YEAR',
2091+
parameters: ['CloseDate'],
2092+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2093+
},
2094+
{
2095+
type: 'FieldFunctionExpression',
2096+
functionName: 'SUM',
2097+
parameters: ['amount'],
2098+
isAggregateFn: true,
2099+
rawValue: 'SUM(amount)',
2100+
},
2101+
],
2102+
sObject: 'Opportunity',
2103+
groupBy: {
2104+
fn: {
2105+
functionName: 'WEEK_IN_YEAR',
2106+
parameters: ['CloseDate'],
2107+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2108+
},
2109+
},
2110+
orderBy: [
2111+
{
2112+
fn: {
2113+
functionName: 'WEEK_IN_YEAR',
2114+
parameters: ['CloseDate'],
2115+
rawValue: 'WEEK_IN_YEAR(CloseDate)',
2116+
},
2117+
order: 'DESC',
2118+
nulls: 'LAST',
2119+
},
2120+
{
2121+
fn: {
2122+
functionName: 'SUM',
2123+
parameters: ['amount'],
2124+
rawValue: 'SUM(amount)',
2125+
},
2126+
order: 'ASC',
2127+
nulls: 'LAST',
2128+
},
2129+
],
2130+
},
2131+
},
19202132
];
19212133

19222134
export default testCases;

0 commit comments

Comments
 (0)