Skip to content

Commit 4a43865

Browse files
committed
Added support for array accessors
1 parent 3d9ee04 commit 4a43865

File tree

7 files changed

+123
-19
lines changed

7 files changed

+123
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
## 4.2.0
44

5-
June 7, 2021
5+
June 8, 2021
66

77
#155 - Apex bind variable support is improved to allow parsing of more complex Apex.
88

9+
Review test cases 112 - 117 for examples of supported apex bind variables.
10+
911
## 4.1.1
1012

1113
June 6, 2021

src/models.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,31 +201,47 @@ export interface ExpressionContext {
201201
}
202202

203203
export interface ApexBindVariableExpressionContext {
204-
apex: (CstNode | IToken)[];
204+
apex: CstNode[];
205205
COLON: IToken[];
206206
DECIMAL?: IToken[];
207207
}
208208

209+
export interface ApexBindVariableIdentifierContext {
210+
Identifier: IToken[];
211+
apexBindVariableFunctionArrayAccessor?: CstNode[];
212+
}
213+
209214
export interface ApexBindVariableNewInstantiationContext {
210-
NEW: IToken[];
211-
FUNCTION: IToken[];
215+
new: IToken[];
216+
function: IToken[];
212217
apexBindVariableGeneric?: CstNode[];
213218
apexBindVariableFunctionParams: CstNode[];
219+
apexBindVariableFunctionArrayAccessor?: CstNode[];
214220
}
221+
215222
export interface ApexBindVariableFunctionCallContext {
216-
FUNCTION: IToken[];
223+
function: IToken[];
217224
apexBindVariableFunctionParams: CstNode[];
225+
apexBindVariableFunctionArrayAccessor?: CstNode[];
218226
}
227+
219228
export interface ApexBindVariableGenericContext {
220229
COMMA: IToken[];
221230
GREATER_THAN: IToken[];
222231
LESS_THAN: IToken[];
223-
PARAMETER: IToken[];
232+
parameter: IToken[];
224233
}
234+
225235
export interface ApexBindVariableFunctionParamsContext {
226236
L_PAREN: IToken[];
227237
R_PAREN: IToken[];
228-
PARAMETER?: IToken[];
238+
parameter?: IToken[];
239+
}
240+
241+
export interface ApexBindVariableFunctionArrayAccessorContext {
242+
L_SQUARE_BRACKET: IToken[];
243+
R_SQUARE_BRACKET: IToken[];
244+
value: IToken[];
229245
}
230246

231247
export interface ExpressionOperatorContext {

src/parser/lexer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ export const Comma = createToken({ name: 'COMMA', pattern: ',', categories: [Sym
750750
export const Asterisk = createToken({ name: 'ASTERISK', pattern: '*', categories: [SymbolIdentifier] });
751751
export const LParen = createToken({ name: 'L_PAREN', pattern: '(', categories: [SymbolIdentifier] });
752752
export const RParen = createToken({ name: 'R_PAREN', pattern: ')', categories: [SymbolIdentifier] });
753+
export const LSquareBracket = createToken({ name: 'L_SQUARE_BRACKET', pattern: '[', categories: [SymbolIdentifier] });
754+
export const RSquareBracket = createToken({ name: 'R_SQUARE_BRACKET', pattern: ']', categories: [SymbolIdentifier] });
753755
export const Plus = createToken({ name: 'PLUS', pattern: '+', categories: [SymbolIdentifier] });
754756
export const Minus = createToken({ name: 'MINUS', pattern: '-', categories: [SymbolIdentifier] });
755757

@@ -1037,6 +1039,8 @@ export const allTokens = [
10371039
Asterisk,
10381040
LParen,
10391041
RParen,
1042+
LSquareBracket,
1043+
RSquareBracket,
10401044
Plus,
10411045
Minus,
10421046
];

src/parser/parser.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -633,33 +633,40 @@ export class SoqlParser extends CstParser {
633633
this.$_apexBindVariableExpression ||
634634
(this.$_apexBindVariableExpression = [
635635
{ ALT: () => this.SUBRULE(this.apexBindVariableFunctionCall, { LABEL: 'apex' }) },
636-
{ ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'apex' }) },
636+
{ ALT: () => this.SUBRULE(this.apexBindVariableIdentifier, { LABEL: 'apex' }) },
637637
]),
638638
);
639639
},
640640
});
641641
});
642642

643+
private apexBindVariableIdentifier = this.RULE('apexBindVariableIdentifier', () => {
644+
this.CONSUME(lexer.Identifier);
645+
this.OPTION(() => this.SUBRULE(this.apexBindVariableFunctionArrayAccessor));
646+
});
647+
643648
private apexBindVariableNewInstantiation = this.RULE('apexBindVariableNewInstantiation', () => {
644-
this.CONSUME(lexer.ApexNew, { LABEL: 'NEW' });
645-
this.CONSUME(lexer.Identifier, { LABEL: 'FUNCTION' });
649+
this.CONSUME(lexer.ApexNew, { LABEL: 'new' });
650+
this.CONSUME(lexer.Identifier, { LABEL: 'function' });
646651
this.OPTION(() => {
647652
this.SUBRULE(this.apexBindVariableGeneric);
648653
});
649654
this.SUBRULE(this.apexBindVariableFunctionParams);
655+
this.OPTION1(() => this.SUBRULE(this.apexBindVariableFunctionArrayAccessor));
650656
});
651657

652658
private apexBindVariableFunctionCall = this.RULE('apexBindVariableFunctionCall', () => {
653-
this.CONSUME(lexer.Identifier, { LABEL: 'FUNCTION' });
659+
this.CONSUME(lexer.Identifier, { LABEL: 'function' });
654660
this.SUBRULE(this.apexBindVariableFunctionParams);
661+
this.OPTION(() => this.SUBRULE(this.apexBindVariableFunctionArrayAccessor));
655662
});
656663

657664
private apexBindVariableGeneric = this.RULE('apexBindVariableGeneric', () => {
658665
this.CONSUME(lexer.LessThan);
659666
this.AT_LEAST_ONE_SEP({
660667
SEP: lexer.Comma,
661668
DEF: () => {
662-
this.CONSUME(lexer.Identifier, { LABEL: 'PARAMETER' });
669+
this.CONSUME(lexer.Identifier, { LABEL: 'parameter' });
663670
},
664671
});
665672
this.CONSUME(lexer.GreaterThan);
@@ -670,12 +677,22 @@ export class SoqlParser extends CstParser {
670677
this.MANY_SEP({
671678
SEP: lexer.Comma,
672679
DEF: () => {
673-
this.CONSUME(lexer.Identifier, { LABEL: 'PARAMETER' });
680+
this.CONSUME(lexer.Identifier, { LABEL: 'parameter' });
674681
},
675682
});
676683
this.CONSUME(lexer.RParen);
677684
});
678685

686+
// foo[3] or foo[somIntVariable]
687+
private apexBindVariableFunctionArrayAccessor = this.RULE('apexBindVariableFunctionArrayAccessor', () => {
688+
this.CONSUME(lexer.LSquareBracket);
689+
this.OR([
690+
{ ALT: () => this.CONSUME(lexer.UnsignedInteger, { LABEL: 'value' }) },
691+
{ ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'value' }) },
692+
]);
693+
this.CONSUME(lexer.RSquareBracket);
694+
});
695+
679696
private arrayExpression = this.RULE('arrayExpression', () => {
680697
this.CONSUME(lexer.LParen);
681698
this.AT_LEAST_ONE_SEP({

src/parser/visitor.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ import {
3434
} from '../api/api-models';
3535
import {
3636
ApexBindVariableExpressionContext,
37+
ApexBindVariableFunctionArrayAccessorContext,
3738
ApexBindVariableFunctionCallContext,
3839
ApexBindVariableFunctionParamsContext,
3940
ApexBindVariableGenericContext,
41+
ApexBindVariableIdentifierContext,
4042
ApexBindVariableNewInstantiationContext,
4143
ArrayExpressionWithType,
4244
AtomicExpressionContext,
@@ -795,31 +797,50 @@ class SOQLVisitor extends BaseSoqlVisitor {
795797
}
796798

797799
apexBindVariableExpression(ctx: ApexBindVariableExpressionContext): string {
798-
return ctx.apex.map(item => (isToken(item) ? item.image : this.visit(item))).join('.');
800+
return ctx.apex.map(item => this.visit(item)).join('.');
801+
}
802+
803+
apexBindVariableIdentifier(ctx: ApexBindVariableIdentifierContext): string {
804+
let output = ctx.Identifier[0].image;
805+
if (ctx.apexBindVariableFunctionArrayAccessor) {
806+
output += this.visit(ctx.apexBindVariableFunctionArrayAccessor[0]);
807+
}
808+
return output;
799809
}
800810

801811
apexBindVariableNewInstantiation(ctx: ApexBindVariableNewInstantiationContext): string {
802-
let output = `new ${ctx.FUNCTION[0].image}`;
812+
let output = `new ${ctx.function[0].image}`;
803813
if (ctx.apexBindVariableGeneric) {
804814
output += this.visit(ctx.apexBindVariableGeneric[0]);
805815
}
806816
output += this.visit(ctx.apexBindVariableFunctionParams[0]);
817+
if (ctx.apexBindVariableFunctionArrayAccessor) {
818+
output += this.visit(ctx.apexBindVariableFunctionArrayAccessor[0]);
819+
}
807820
return output;
808821
}
809822

810823
apexBindVariableFunctionCall(ctx: ApexBindVariableFunctionCallContext): string {
811-
return `${ctx.FUNCTION[0].image}${this.visit(ctx.apexBindVariableFunctionParams[0])}`;
824+
let output = `${ctx.function[0].image}${this.visit(ctx.apexBindVariableFunctionParams[0])}`;
825+
if (ctx.apexBindVariableFunctionArrayAccessor) {
826+
output += this.visit(ctx.apexBindVariableFunctionArrayAccessor[0]);
827+
}
828+
return output;
812829
}
813830

814831
apexBindVariableGeneric(ctx: ApexBindVariableGenericContext): string {
815-
return `<${ctx.PARAMETER.map(item => item.image).join(', ')}>`;
832+
return `<${ctx.parameter.map(item => item.image).join(', ')}>`;
816833
}
817834

818835
apexBindVariableFunctionParams(ctx: ApexBindVariableFunctionParamsContext): string {
819-
const params = Array.isArray(ctx.PARAMETER) ? ctx.PARAMETER : [];
836+
const params = Array.isArray(ctx.parameter) ? ctx.parameter : [];
820837
return `(${params.map(item => item.image).join(', ')})`;
821838
}
822839

840+
apexBindVariableFunctionArrayAccessor(ctx: ApexBindVariableFunctionArrayAccessorContext): string {
841+
return `[${ctx.value[0].image}]`;
842+
}
843+
823844
arrayExpression(ctx: ValueContext): ArrayExpressionWithType[] {
824845
return ctx.value.map((item: any) => {
825846
if (isToken(item)) {

test/test-cases.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,6 +2346,50 @@ export const testCases: TestCase[] = [
23462346
},
23472347
},
23482348
},
2349+
{
2350+
testCase: 116,
2351+
options: { allowApexBindVariables: true },
2352+
soql: `SELECT Id FROM SBQQ__QuoteTerm__c WHERE SBQQ__StandardTerm__c = :CPQ_Hard_Coded_Ids__c.getInstance().Standard_Quote_Term_Id__c`,
2353+
output: {
2354+
fields: [
2355+
{
2356+
type: 'Field',
2357+
field: 'Id',
2358+
},
2359+
],
2360+
sObject: 'SBQQ__QuoteTerm__c',
2361+
where: {
2362+
left: {
2363+
field: 'SBQQ__StandardTerm__c',
2364+
literalType: 'APEX_BIND_VARIABLE',
2365+
operator: '=',
2366+
value: 'CPQ_Hard_Coded_Ids__c.getInstance().Standard_Quote_Term_Id__c',
2367+
},
2368+
},
2369+
},
2370+
},
2371+
{
2372+
testCase: 117,
2373+
options: { allowApexBindVariables: true },
2374+
soql: `SELECT Id FROM Opportunity WHERE SBQQ__StandardTerm__c = :quotes[3].SBQQ__QuoteLine__r[0].Term__c`,
2375+
output: {
2376+
fields: [
2377+
{
2378+
type: 'Field',
2379+
field: 'Id',
2380+
},
2381+
],
2382+
sObject: 'Opportunity',
2383+
where: {
2384+
left: {
2385+
field: 'SBQQ__StandardTerm__c',
2386+
literalType: 'APEX_BIND_VARIABLE',
2387+
operator: '=',
2388+
value: 'quotes[3].SBQQ__QuoteLine__r[0].Term__c',
2389+
},
2390+
},
2391+
},
2392+
},
23492393
];
23502394

23512395
export default testCases;

test/test.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const replacements = [{ matching: / last /i, replace: ' LAST ' }];
1212
// Uncomment these to easily test one specific query - useful for troubleshooting/bugfixing
1313

1414
// describe.only('parse queries', () => {
15-
// const testCase = testCases.find(tc => tc.testCase === 62);
15+
// const testCase = testCases.find(tc => tc.testCase === 117);
1616
// it(`should correctly parse test case ${testCase.testCase} - ${testCase.soql}`, () => {
1717
// const soqlQuery = parseQuery(testCase.soql, testCase.options);
1818
// console.log(soqlQuery);

0 commit comments

Comments
 (0)