Skip to content
This repository has been archived by the owner on Mar 10, 2024. It is now read-only.

Commit

Permalink
Fix wrong quoting characters for SOSL FIND queries
Browse files Browse the repository at this point in the history
  • Loading branch information
nawforce committed Oct 19, 2022
1 parent 4cd39f8 commit 6f67271
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 87 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ import 'CommonTokenStream' & 'ParseTreeWalker' from 'apex-parser' instead of fro
import { CommonTokenStream} from "apex-parser";
import { ParseTreeWalker } from "apex-parser";

### SOSL FIND quoting
SOSL FIND uses ' as a quoting character when embedded in Apex, in the API braces are used:

Find {something} RETURNING Account

To parse the API format there is an alternative parser rule, soslLiteralAlt, that you can use instead of soslLiteral. See SOSLParserTest for some examples of how these differ.

### Packages

Maven
Expand Down
17 changes: 16 additions & 1 deletion antlr/ApexLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ RETURNING : 'returning';
LISTVIEW : 'listview';

FindLiteral
: '[' WS? 'find' WS '{' FindCharacters? '}'
: '[' WS? 'find' WS '\'' FindCharacters? '\''
;

fragment
Expand All @@ -253,6 +253,21 @@ FindCharacters

fragment
FindCharacter
: ~['\\]
| FindEscapeSequence
;
FindLiteralAlt
: '[' WS? 'find' WS '{' FindCharactersAlt? '}'
;
fragment
FindCharactersAlt
: FindCharacterAlt+
;
fragment
FindCharacterAlt
: ~[}\\]
| FindEscapeSequence
;
Expand Down
4 changes: 4 additions & 0 deletions antlr/ApexParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,10 @@ soslLiteral
| LBRACK FIND boundExpression soslClauses RBRACK
;

soslLiteralAlt
: FindLiteralAlt soslClauses RBRACK
;

soslClauses
: (IN searchGroup)?
(RETURNING fieldSpecList)?
Expand Down
41 changes: 1 addition & 40 deletions jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
package com.nawforce.apexparser;

import org.antlr.v4.runtime.*;
import org.junit.jupiter.api.Test;

import javafx.util.Pair;

import static com.nawforce.apexparser.SyntaxErrorCounter.createParser;
import static org.junit.jupiter.api.Assertions.*;

public class ApexParserTest {

public static class SyntaxErrorCounter extends BaseErrorListener {
private int numErrors = 0;

@Override
public void syntaxError(
Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
this.numErrors += 1;
}

public int getNumErrors() {
return this.numErrors;
}
}

Pair<ApexParser, SyntaxErrorCounter> createParser(String input) {
ApexLexer lexer = new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(input)));
CommonTokenStream tokens = new CommonTokenStream(lexer);
ApexParser parser = new ApexParser(tokens);

parser.removeErrorListeners();
SyntaxErrorCounter errorCounter = new SyntaxErrorCounter();
parser.addErrorListener(errorCounter);

return new Pair<>(parser, errorCounter);
}

@Test
void testBooleanLiteral() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("true");
Expand Down Expand Up @@ -111,14 +80,6 @@ void testSOQL() {
assertEquals(0, parserAndCounter.getValue().getNumErrors());
}

@Test
void testSOSL() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find {something} RETURNING Account]");
ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral();
assertNotNull(context);
assertEquals(0, parserAndCounter.getValue().getNumErrors());
}

@Test
void testCurrencyLiteral() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser(
Expand Down
50 changes: 50 additions & 0 deletions jvm/src/test/java/com/nawforce/apexparser/SOSLParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.nawforce.apexparser;

import org.junit.jupiter.api.Test;

import javafx.util.Pair;

import static com.nawforce.apexparser.SyntaxErrorCounter.createParser;
import static org.junit.jupiter.api.Assertions.*;

public class SOSLParserTest {
@Test
void testBasicQuery() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find 'something' RETURNING Account]");
ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral();
assertNotNull(context);
assertEquals(0, parserAndCounter.getValue().getNumErrors());
}

@Test
void testEmbeddedQuote() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find 'some\\'thing' RETURNING Account]");
ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral();
assertNotNull(context);
assertEquals(0, parserAndCounter.getValue().getNumErrors());
}

@Test
void testBracesFail() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find {something} RETURNING Account]");
ApexParser.SoslLiteralContext context = parserAndCounter.getKey().soslLiteral();
assertNotNull(context);
assertEquals(1, parserAndCounter.getValue().getNumErrors());
}

@Test
void testBracesOnAltFormat() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find {something} RETURNING Account]");
ApexParser.SoslLiteralAltContext context = parserAndCounter.getKey().soslLiteralAlt();
assertNotNull(context);
assertEquals(0, parserAndCounter.getValue().getNumErrors());
}

@Test
void testQuotesFailOnAltFormat() {
Pair<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("[Find 'something' RETURNING Account]");
ApexParser.SoslLiteralAltContext context = parserAndCounter.getKey().soslLiteralAlt();
assertNotNull(context);
assertEquals(1, parserAndCounter.getValue().getNumErrors());
}
}
35 changes: 35 additions & 0 deletions jvm/src/test/java/com/nawforce/apexparser/SyntaxErrorCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.nawforce.apexparser;

import javafx.util.Pair;
import org.antlr.v4.runtime.*;

public class SyntaxErrorCounter extends BaseErrorListener {
private int numErrors = 0;

@Override
public void syntaxError(
Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e) {
this.numErrors += 1;
}

public int getNumErrors() {
return this.numErrors;
}

public static Pair<ApexParser, SyntaxErrorCounter> createParser(String input) {
ApexLexer lexer = new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(input)));
CommonTokenStream tokens = new CommonTokenStream(lexer);
ApexParser parser = new ApexParser(tokens);

parser.removeErrorListeners();
SyntaxErrorCounter errorCounter = new SyntaxErrorCounter();
parser.addErrorListener(errorCounter);

return new Pair<>(parser, errorCounter);
}
}
19 changes: 13 additions & 6 deletions npm/jestconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
{
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"testRegex": "/__tests__/.*Test.js$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
}
43 changes: 3 additions & 40 deletions npm/src/__tests__/ApexParserTest.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,9 @@
import { ApexLexer } from "../ApexLexer";
import {
ApexParser, LiteralContext, Arth1ExpressionContext, CompilationUnitContext,
QueryContext, SoslLiteralContext, StatementContext, TriggerUnitContext
LiteralContext, Arth1ExpressionContext, CompilationUnitContext,
QueryContext, StatementContext, TriggerUnitContext
} from "../ApexParser";
import { CaseInsensitiveInputStream } from "../CaseInsensitiveInputStream"
import { ANTLRErrorListener, CommonTokenStream, RecognitionException, Recognizer, Token } from 'antlr4ts';
import { ThrowingErrorListener, SyntaxException } from "../ThrowingErrorListener";

class SyntaxErrorCounter implements ANTLRErrorListener<Token> {
numErrors = 0

syntaxError(recognizer: Recognizer<Token, any>,
offendingSymbol: Token, line: number, charPositionInLine: number, msg: string,
e: RecognitionException | undefined): any {
this.numErrors += 1;
}

getNumErrors(): number {
return this.numErrors;
}
}

function createParser(userData: string, input: string): [ApexParser, SyntaxErrorCounter] {
const lexer = new ApexLexer(new CaseInsensitiveInputStream(userData, input))
const tokens = new CommonTokenStream(lexer);
const parser = new ApexParser(tokens)

parser.removeErrorListeners()
const errorCounter = new SyntaxErrorCounter();
parser.addErrorListener(errorCounter);

return [parser, errorCounter];
}
import { createParser } from "./SyntaxErrorCounter";

test('Boolean Literal', () => {

Expand Down Expand Up @@ -136,15 +108,6 @@ test('SOQL Query Using Field function', () => {
expect(errorCounter.getNumErrors()).toEqual(0)
})

test('SOSL Query', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find {something} RETURNING Account]")

const context = parser.soslLiteral()

expect(context).toBeInstanceOf(SoslLiteralContext)
expect(errorCounter.getNumErrors()).toEqual(0)
})

test('CurrencyLiteral', () => {
const [parser, errorCounter] = createParser("test.soql", "SELECT Id FROM Account WHERE Amount > USD100.01 AND Amount < USD200")

Expand Down
50 changes: 50 additions & 0 deletions npm/src/__tests__/SOSLParserTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
SoslLiteralAltContext,
SoslLiteralContext
} from "../ApexParser";
import { createParser } from "./SyntaxErrorCounter";

test('testBasicQuery', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find 'something' RETURNING Account]")

const context = parser.soslLiteral()

expect(context).toBeInstanceOf(SoslLiteralContext)
expect(errorCounter.getNumErrors()).toEqual(0)
})

test('testEmbeddedQuote', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find 'some\\'thing' RETURNING Account]")

const context = parser.soslLiteral()

expect(context).toBeInstanceOf(SoslLiteralContext)
expect(errorCounter.getNumErrors()).toEqual(0)
})

test('testBracesFail', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find {something} RETURNING Account]")

const context = parser.soslLiteral()

expect(context).toBeInstanceOf(SoslLiteralContext)
expect(errorCounter.getNumErrors()).toEqual(1)
})

test('testBracesOnAltFormat', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find {something} RETURNING Account]")

const context = parser.soslLiteralAlt()

expect(context).toBeInstanceOf(SoslLiteralAltContext)
expect(errorCounter.getNumErrors()).toEqual(0)
})

test('testQuotesFailOnAltFormat', () => {
const [parser, errorCounter] = createParser("test.sosl", "[Find 'something' RETURNING Account]")

const context = parser.soslLiteralAlt()

expect(context).toBeInstanceOf(SoslLiteralAltContext)
expect(errorCounter.getNumErrors()).toEqual(1)
})
30 changes: 30 additions & 0 deletions npm/src/__tests__/SyntaxErrorCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ANTLRErrorListener, CommonTokenStream, RecognitionException, Recognizer, Token } from "antlr4ts";
import { ApexLexer } from "../ApexLexer";
import { ApexParser } from "../ApexParser";
import { CaseInsensitiveInputStream } from "../CaseInsensitiveInputStream";

export class SyntaxErrorCounter implements ANTLRErrorListener<Token> {
numErrors = 0

syntaxError(recognizer: Recognizer<Token, any>,
offendingSymbol: Token, line: number, charPositionInLine: number, msg: string,
e: RecognitionException | undefined): any {
this.numErrors += 1;
}

getNumErrors(): number {
return this.numErrors;
}
}

export function createParser(userData: string, input: string): [ApexParser, SyntaxErrorCounter] {
const lexer = new ApexLexer(new CaseInsensitiveInputStream(userData, input))
const tokens = new CommonTokenStream(lexer);
const parser = new ApexParser(tokens)

parser.removeErrorListeners()
const errorCounter = new SyntaxErrorCounter();
parser.addErrorListener(errorCounter);

return [parser, errorCounter];
}

0 comments on commit 6f67271

Please sign in to comment.