diff --git a/antlr/ApexLexer.g4 b/antlr/ApexLexer.g4 index bf30419..785ed53 100644 --- a/antlr/ApexLexer.g4 +++ b/antlr/ApexLexer.g4 @@ -171,6 +171,7 @@ STANDARD : 'standard'; DISTANCE : 'distance'; GEOLOCATION : 'geolocation'; GROUPING : 'grouping'; +CONVERT_CURRENCY : 'convertcurrency'; // used in both SOQL and SOSL // SOQL Date functions CALENDAR_MONTH : 'calendar_month'; diff --git a/antlr/ApexParser.g4 b/antlr/ApexParser.g4 index 56d2840..a320aaf 100644 --- a/antlr/ApexParser.g4 +++ b/antlr/ApexParser.g4 @@ -620,7 +620,7 @@ soqlFunction | MAX LPAREN fieldName RPAREN | SUM LPAREN fieldName RPAREN | TOLABEL LPAREN fieldName RPAREN - | FORMAT LPAREN fieldName RPAREN + | FORMAT LPAREN ( fieldName | soqlFunction ) RPAREN | CALENDAR_MONTH LPAREN dateFieldName RPAREN | CALENDAR_QUARTER LPAREN dateFieldName RPAREN | CALENDAR_YEAR LPAREN dateFieldName RPAREN @@ -637,6 +637,7 @@ soqlFunction | FIELDS LPAREN soqlFieldsParameter RPAREN | DISTANCE LPAREN locationValue COMMA locationValue COMMA StringLiteral RPAREN | GROUPING LPAREN fieldName RPAREN + | CONVERT_CURRENCY LPAREN fieldName RPAREN ; dateFieldName @@ -865,7 +866,9 @@ fieldSpec fieldList : soslId (COMMA fieldList)* - | TOLABEL LPAREN soslId RPAREN + | TOLABEL LPAREN soslId RPAREN soslId? + | CONVERT_CURRENCY LPAREN soslId RPAREN soslId? + | FORMAT LPAREN (soslId | soqlFunction) RPAREN soslId? ; updateList @@ -965,6 +968,7 @@ id | DISTANCE | GEOLOCATION | GROUPING + | CONVERT_CURRENCY // SOQL date functions | CALENDAR_MONTH | CALENDAR_QUARTER @@ -1164,6 +1168,7 @@ anyId | DISTANCE | GEOLOCATION | GROUPING + | CONVERT_CURRENCY // SOQL date functions | CALENDAR_MONTH | CALENDAR_QUARTER diff --git a/jvm/src/test/java/io/github/apexdevtools/apexparser/SOQLParserTest.java b/jvm/src/test/java/io/github/apexdevtools/apexparser/SOQLParserTest.java index ba62131..1ec46c5 100644 --- a/jvm/src/test/java/io/github/apexdevtools/apexparser/SOQLParserTest.java +++ b/jvm/src/test/java/io/github/apexdevtools/apexparser/SOQLParserTest.java @@ -130,4 +130,38 @@ void testGroupingFunction() { assertNotNull(context); assertEquals(0, parserAndCounter.getValue().getNumErrors()); } + + @Test + void testConvertCurrency() { + Map.Entry parserAndCounter = createParser( + "[ SELECT convertCurrency(Amount) FROM Opportunity ]" + ); + ApexParser.SoqlLiteralContext context = parserAndCounter.getKey().soqlLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } + + @Test + void testConvertCurrencyWithFormat() { + Map.Entry parserAndCounter = createParser( + "[\n" + + "SELECT Amount, FORMAT(amount) Amt, convertCurrency(amount) convertedAmount,\n" + + " FORMAT(convertCurrency(amount)) convertedCurrency\n" + + "FROM Opportunity where id = '006R00000024gDtIAI'\n" + + "]" + ); + ApexParser.SoqlLiteralContext context = parserAndCounter.getKey().soqlLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } + + @Test + void testFormatWithAggregate() { + Map.Entry parserAndCounter = createParser( + "[ SELECT FORMAT(MIN(closedate)) Amt FROM opportunity ]" + ); + ApexParser.SoqlLiteralContext context = parserAndCounter.getKey().soqlLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } } diff --git a/jvm/src/test/java/io/github/apexdevtools/apexparser/SOSLParserTest.java b/jvm/src/test/java/io/github/apexdevtools/apexparser/SOSLParserTest.java index a272c81..879e6ad 100644 --- a/jvm/src/test/java/io/github/apexdevtools/apexparser/SOSLParserTest.java +++ b/jvm/src/test/java/io/github/apexdevtools/apexparser/SOSLParserTest.java @@ -84,4 +84,43 @@ void testToLabel() { assertNotNull(context); assertEquals(0, parserAndCounter.getValue().getNumErrors()); } + + @Test + void testToLabelWithAlias() { + Map.Entry parserAndCounter = createParser( + "[FIND :searchTerm IN ALL FIELDS RETURNING Account(Id, toLabel(Name) AliasName) LIMIT 10]"); + ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } + + @Test + void testConvertCurrency() { + Map.Entry parserAndCounter = createParser( + "[ FIND 'test' RETURNING Opportunity(Name, convertCurrency(Amount), convertCurrency(Amount) AliasCurrency) ]" + ); + ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } + + @Test + void testConvertCurrencyWithFormat() { + Map.Entry parserAndCounter = createParser( + "[ FIND 'Acme' RETURNING Account(AnnualRevenue, FORMAT(convertCurrency(AnnualRevenue)) convertedCurrency) ]" + ); + ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } + + @Test + void testFormatWithAggregate() { + Map.Entry parserAndCounter = createParser( + "[ FIND 'Acme' RETURNING Account(AnnualRevenue, FORMAT(MIN(CloseDate))) ]" + ); + ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral(); + assertNotNull(context); + assertEquals(0, parserAndCounter.getValue().getNumErrors()); + } } diff --git a/npm/src/__tests__/SOQLParserTest.ts b/npm/src/__tests__/SOQLParserTest.ts index d8cb00e..0bfe787 100644 --- a/npm/src/__tests__/SOQLParserTest.ts +++ b/npm/src/__tests__/SOQLParserTest.ts @@ -11,7 +11,7 @@ 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. */ -import { QueryContext, StatementContext } from "../ApexParser"; +import { QueryContext, StatementContext, SoqlLiteralContext } from "../ApexParser"; import { createParser } from "./SyntaxErrorCounter"; test("SOQL Query", () => { @@ -138,3 +138,37 @@ test("Grouping function", () => { expect(context).toBeInstanceOf(QueryContext); expect(errorCounter.getNumErrors()).toEqual(0); }); + +test("Convert Currency function", () => { + const [parser, errorCounter] = createParser( + '[ SELECT convertCurrency(Amount) FROM Opportunity ]' + ); + const context = parser.soqlLiteral(); + + expect(context).toBeInstanceOf(SoqlLiteralContext); + expect(errorCounter.getNumErrors()).toEqual(0); +}); + +test("Convert Currency with format", () => { + const [parser, errorCounter] = createParser( + `[ + SELECT Amount, FORMAT(amount) Amt, convertCurrency(amount) convertedAmount, + FORMAT(convertCurrency(amount)) convertedCurrency + FROM Opportunity where id = '006R00000024gDtIAI' + ]` + ); + const context = parser.soqlLiteral(); + + expect(context).toBeInstanceOf(SoqlLiteralContext); + expect(errorCounter.getNumErrors()).toEqual(0); +}); + +test("Format function with aggregate", () => { + const [parser, errorCounter] = createParser( + '[ SELECT FORMAT(MIN(closedate)) Amt FROM opportunity ]' + ) + const context = parser.soqlLiteral(); + + expect(context).toBeInstanceOf(SoqlLiteralContext); + expect(errorCounter.getNumErrors()).toEqual(0); +}); diff --git a/npm/src/__tests__/SOSLParserTest.ts b/npm/src/__tests__/SOSLParserTest.ts index 1f15193..14e1ba8 100644 --- a/npm/src/__tests__/SOSLParserTest.ts +++ b/npm/src/__tests__/SOSLParserTest.ts @@ -88,3 +88,51 @@ test('testToLabel', () => { expect(context).toBeInstanceOf(SoslLiteralContext) expect(errorCounter.getNumErrors()).toEqual(0) }) + +test('testToLabelWithAlias', () => { + const [parser, errorCounter] = createParser( + "[FIND :searchTerm IN ALL FIELDS RETURNING Account(Id, toLabel(Name) AliasName) LIMIT 10]") + const context = parser.soslLiteral() + + expect(context).toBeInstanceOf(SoslLiteralContext) + expect(errorCounter.getNumErrors()).toEqual(0) +}) + +test('testConvertCurrency', () => { + const [parser, errorCounter] = createParser( + `[ + FIND 'test' RETURNING Opportunity( + Name, + convertCurrency(Amount), + convertCurrency(Amount) AliasCurrency + ) + ]`) + const context = parser.soslLiteral() + + expect(context).toBeInstanceOf(SoslLiteralContext) + expect(errorCounter.getNumErrors()).toEqual(0) +}) + +test('testConvertCurrencyWithFormat', () => { + const [parser, errorCounter] = createParser( + `[ + FIND 'Acme' RETURNING Account( + AnnualRevenue, + FORMAT(convertCurrency(AnnualRevenue)) convertedCurrency + ) + ]`) + const context = parser.soslLiteral() + + expect(context).toBeInstanceOf(SoslLiteralContext) + expect(errorCounter.getNumErrors()).toEqual(0) +}) + +test('testFormatWithAggregate', () => { + const [parser, errorCounter] = createParser( + "[ FIND 'Acme' RETURNING Account(AnnualRevenue, FORMAT(MIN(CloseDate))) ]" + ) + const context = parser.soslLiteral() + + expect(context).toBeInstanceOf(SoslLiteralContext) + expect(errorCounter.getNumErrors()).toEqual(0) +})